diff --git a/.buildkite/ftr_configs.yml b/.buildkite/ftr_configs.yml index 4adb920c81e43..1400d1fdee4cf 100644 --- a/.buildkite/ftr_configs.yml +++ b/.buildkite/ftr_configs.yml @@ -359,4 +359,5 @@ enabled: - x-pack/performance/journeys/ecommerce_dashboard_saved_search_only.ts - x-pack/performance/journeys/ecommerce_dashboard_tsvb_gauge_only.ts - x-pack/performance/journeys/dashboard_listing_page.ts + - x-pack/performance/journeys/cloud_security_dashboard.ts - x-pack/test/custom_branding/config.ts diff --git a/.buildkite/scripts/steps/functional/apm_cypress.sh b/.buildkite/scripts/steps/functional/apm_cypress.sh index ff14df87377cd..987d9de577c8b 100755 --- a/.buildkite/scripts/steps/functional/apm_cypress.sh +++ b/.buildkite/scripts/steps/functional/apm_cypress.sh @@ -12,8 +12,8 @@ APM_CYPRESS_RECORD_KEY="$(retry 5 5 vault read -field=CYPRESS_RECORD_KEY secret/ export JOB=kibana-apm-cypress IS_FLAKY_TEST_RUNNER=${CLI_COUNT:-0} -# Disable parallel tests and dashboard recording when running them in the flaky test runner -if [[ "$IS_FLAKY_TEST_RUNNER" -ne 1 ]]; then +#Enabling cypress dashboard recording when PR is labeled with `apm:cypress-record` and we are not using the flaky test runner +if [[ "$IS_FLAKY_TEST_RUNNER" -ne 1 ]] && is_pr_with_label "apm:cypress-record"; then CYPRESS_ARGS="--record --key "$APM_CYPRESS_RECORD_KEY" --parallel --ci-build-id "${BUILDKITE_BUILD_ID}"" else CYPRESS_ARGS="" diff --git a/.buildkite/scripts/steps/storybooks/build_and_upload.ts b/.buildkite/scripts/steps/storybooks/build_and_upload.ts index fbe8daee88370..949cb0a0ff534 100644 --- a/.buildkite/scripts/steps/storybooks/build_and_upload.ts +++ b/.buildkite/scripts/steps/storybooks/build_and_upload.ts @@ -19,7 +19,7 @@ const STORYBOOKS = [ 'cloud_chat', 'coloring', 'chart_icons', - 'content_management_plugin', + 'content_management_examples', 'controls', 'custom_integrations', 'dashboard_enhanced', diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 40677dc5a498e..c30906c56c2ac 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -19,6 +19,7 @@ x-pack/examples/alerting_example @elastic/response-ops x-pack/test/functional_with_es_ssl/plugins/alerts @elastic/response-ops x-pack/plugins/alerting @elastic/response-ops packages/kbn-alerts @elastic/security-solution +packages/kbn-alerts-as-data-utils @elastic/response-ops x-pack/test/alerting_api_integration/common/plugins/alerts_restricted @elastic/response-ops packages/kbn-alerts-ui-shared @elastic/response-ops packages/kbn-ambient-common-types @elastic/kibana-operations @@ -82,6 +83,7 @@ packages/kbn-config-mocks @elastic/kibana-core packages/kbn-config-schema @elastic/kibana-core src/plugins/console @elastic/platform-deployment-management packages/content-management/content_editor @elastic/appex-sharedux +examples/content_management_examples @elastic/appex-sharedux src/plugins/content_management @elastic/appex-sharedux packages/content-management/table_list @elastic/appex-sharedux examples/controls_example @elastic/kibana-presentation @@ -336,6 +338,7 @@ x-pack/test/encrypted_saved_objects_api_integration/plugins/api_consumer_plugin src/plugins/event_annotation @elastic/kibana-visualizations x-pack/test/plugin_api_integration/plugins/event_log @elastic/response-ops x-pack/plugins/event_log @elastic/response-ops +packages/kbn-expandable-flyout @elastic/security-threat-hunting-investigations packages/kbn-expect @elastic/kibana-operations x-pack/examples/exploratory_view_example @elastic/uptime src/plugins/expression_error @elastic/kibana-presentation @@ -1155,12 +1158,15 @@ x-pack/test/threat_intelligence_cypress @elastic/protections-experience /x-pack/plugins/security_solution/public/detection_engine/rule_response_actions @elastic/security-defend-workflows /x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions @elastic/security-defend-workflows +# Cloud Defend +/x-pack/plugins/cloud_defend/ @elastic/sec-cloudnative-integrations +/x-pack/plugins/security_solution/public/cloud_defend @elastic/sec-cloudnative-integrations + # Cloud Security Posture /x-pack/plugins/security_solution/public/cloud_security_posture @elastic/kibana-cloud-security-posture /x-pack/test/api_integration/apis/cloud_security_posture/ @elastic/kibana-cloud-security-posture /x-pack/test/cloud_security_posture_functional/ @elastic/kibana-cloud-security-posture - # Security Solution onboarding tour /x-pack/plugins/security_solution/public/common/components/guided_onboarding @elastic/security-threat-hunting-explore /x-pack/plugins/security_solution/cypress/e2e/guided_onboarding @elastic/security-threat-hunting-explore diff --git a/.github/workflows/skip-failed-test.yml b/.github/workflows/skip-failed-test.yml index e892582951adc..a6535b106c728 100644 --- a/.github/workflows/skip-failed-test.yml +++ b/.github/workflows/skip-failed-test.yml @@ -26,6 +26,7 @@ jobs: uses: ./actions/permission-check with: permission: admin + teams: appex-qa token: ${{secrets.GITHUB_TOKEN}} - name: Checkout kibana-operations diff --git a/.i18nrc.json b/.i18nrc.json index d8a6b4689f78e..b7d7432551faa 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -25,6 +25,7 @@ "embeddableExamples": "examples/embeddable_examples", "esQuery": "packages/kbn-es-query/src", "esUi": "src/plugins/es_ui_shared", + "expandableFlyout": "packages/kbn-expandable-flyout", "expressionError": "src/plugins/expression_error", "expressionGauge": "src/plugins/chart_expressions/expression_gauge", "expressionHeatmap": "src/plugins/chart_expressions/expression_heatmap", diff --git a/api_docs/actions.mdx b/api_docs/actions.mdx index 786d5ef0ebff9..d7024294a9f2e 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: 2023-02-27 +date: 2023-03-02 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 6b5482bad9600..9cd1578b94fd8 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: 2023-02-27 +date: 2023-03-02 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 6bf96b8b294b8..90e10a1574cbb 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: 2023-02-27 +date: 2023-03-02 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 0a9c5b5718503..016e965013dfe 100644 --- a/api_docs/alerting.devdocs.json +++ b/api_docs/alerting.devdocs.json @@ -867,6 +867,69 @@ } ], "functions": [ + { + "parentPluginId": "alerting", + "id": "def-server.getComponentTemplate", + "type": "Function", + "tags": [], + "label": "getComponentTemplate", + "description": [], + "signature": [ + "(fieldMap: ", + { + "pluginId": "@kbn/alerts-as-data-utils", + "scope": "common", + "docId": "kibKbnAlertsAsDataUtilsPluginApi", + "section": "def-common.FieldMap", + "text": "FieldMap" + }, + ", context?: string | undefined) => ", + "ClusterPutComponentTemplateRequest" + ], + "path": "x-pack/plugins/alerting/server/alerts_service/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "alerting", + "id": "def-server.getComponentTemplate.$1", + "type": "Object", + "tags": [], + "label": "fieldMap", + "description": [], + "signature": [ + { + "pluginId": "@kbn/alerts-as-data-utils", + "scope": "common", + "docId": "kibKbnAlertsAsDataUtilsPluginApi", + "section": "def-common.FieldMap", + "text": "FieldMap" + } + ], + "path": "x-pack/plugins/alerting/server/alerts_service/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "alerting", + "id": "def-server.getComponentTemplate.$2", + "type": "string", + "tags": [], + "label": "context", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "x-pack/plugins/alerting/server/alerts_service/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "alerting", "id": "def-server.getEsErrorMessage", @@ -3239,6 +3302,33 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "alerting", + "id": "def-server.ECS_COMPONENT_TEMPLATE_NAME", + "type": "string", + "tags": [], + "label": "ECS_COMPONENT_TEMPLATE_NAME", + "description": [], + "path": "x-pack/plugins/alerting/server/alerts_service/alerts_service.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "alerting", + "id": "def-server.ECS_CONTEXT", + "type": "string", + "tags": [], + "label": "ECS_CONTEXT", + "description": [], + "signature": [ + "\"ecs\"" + ], + "path": "x-pack/plugins/alerting/server/alerts_service/alerts_service.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "alerting", "id": "def-server.ExecutorType", @@ -3938,6 +4028,42 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "alerting", + "id": "def-common.getComponentTemplateFromFieldMap", + "type": "Function", + "tags": [], + "label": "getComponentTemplateFromFieldMap", + "description": [], + "signature": [ + "({ name, fieldMap, fieldLimit, }: ", + "GetComponentTemplateFromFieldMapOpts", + ") => ", + "ClusterPutComponentTemplateRequest" + ], + "path": "x-pack/plugins/alerting/common/alert_schema/field_maps/component_template_from_field_map.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "alerting", + "id": "def-common.getComponentTemplateFromFieldMap.$1", + "type": "Object", + "tags": [], + "label": "{\n name,\n fieldMap,\n fieldLimit,\n}", + "description": [], + "signature": [ + "GetComponentTemplateFromFieldMapOpts" + ], + "path": "x-pack/plugins/alerting/common/alert_schema/field_maps/component_template_from_field_map.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "alerting", "id": "def-common.getDurationNumberInItsUnit", @@ -4052,6 +4178,69 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "alerting", + "id": "def-common.mappingFromFieldMap", + "type": "Function", + "tags": [], + "label": "mappingFromFieldMap", + "description": [], + "signature": [ + "(fieldMap: ", + { + "pluginId": "@kbn/alerts-as-data-utils", + "scope": "common", + "docId": "kibKbnAlertsAsDataUtilsPluginApi", + "section": "def-common.FieldMap", + "text": "FieldMap" + }, + ", dynamic: boolean | \"strict\") => ", + "MappingTypeMapping" + ], + "path": "x-pack/plugins/alerting/common/alert_schema/field_maps/mapping_from_field_map.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "alerting", + "id": "def-common.mappingFromFieldMap.$1", + "type": "Object", + "tags": [], + "label": "fieldMap", + "description": [], + "signature": [ + { + "pluginId": "@kbn/alerts-as-data-utils", + "scope": "common", + "docId": "kibKbnAlertsAsDataUtilsPluginApi", + "section": "def-common.FieldMap", + "text": "FieldMap" + } + ], + "path": "x-pack/plugins/alerting/common/alert_schema/field_maps/mapping_from_field_map.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "alerting", + "id": "def-common.mappingFromFieldMap.$2", + "type": "CompoundType", + "tags": [], + "label": "dynamic", + "description": [], + "signature": [ + "boolean | \"strict\"" + ], + "path": "x-pack/plugins/alerting/common/alert_schema/field_maps/mapping_from_field_map.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "alerting", "id": "def-common.parseDuration", diff --git a/api_docs/alerting.mdx b/api_docs/alerting.mdx index be7ba2627c4d1..105f01841d43c 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'alerting'] --- import alertingObj from './alerting.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-o | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 487 | 1 | 476 | 40 | +| 497 | 1 | 486 | 41 | ## Client diff --git a/api_docs/apm.mdx b/api_docs/apm.mdx index 1be070da0fd6c..176505db76893 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: 2023-02-27 +date: 2023-03-02 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 404ec374f5d93..4ae0e43fab501 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: 2023-02-27 +date: 2023-03-02 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 5c2d6f7fd9abf..349c11aa7f818 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: 2023-02-27 +date: 2023-03-02 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 7caa2bc00fb8b..14f5425378cb5 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: 2023-02-27 +date: 2023-03-02 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 3ab0fa50805ff..589bde7d8583c 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: 2023-02-27 +date: 2023-03-02 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 122899a005d34..6f96f0c9560c1 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: 2023-02-27 +date: 2023-03-02 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 b4e6fecfc3cca..d8983278042a2 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: 2023-02-27 +date: 2023-03-02 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 a470cd5439021..a4343827f5b7e 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudChat'] --- import cloudChatObj from './cloud_chat.devdocs.json'; diff --git a/api_docs/cloud_data_migration.mdx b/api_docs/cloud_data_migration.mdx index a901c6b14ae16..cb9fb0099f829 100644 --- a/api_docs/cloud_data_migration.mdx +++ b/api_docs/cloud_data_migration.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudDataMigration title: "cloudDataMigration" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudDataMigration plugin -date: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudDataMigration'] --- import cloudDataMigrationObj from './cloud_data_migration.devdocs.json'; diff --git a/api_docs/cloud_defend.devdocs.json b/api_docs/cloud_defend.devdocs.json index f155214541b73..40015d4edbbb1 100644 --- a/api_docs/cloud_defend.devdocs.json +++ b/api_docs/cloud_defend.devdocs.json @@ -2,10 +2,188 @@ "id": "cloudDefend", "client": { "classes": [], - "functions": [], - "interfaces": [], + "functions": [ + { + "parentPluginId": "cloudDefend", + "id": "def-public.getSecuritySolutionLink", + "type": "Function", + "tags": [], + "label": "getSecuritySolutionLink", + "description": [ + "\nGets the cloud_defend link properties of a Cloud Defend page for navigation in the security solution." + ], + "signature": [ + "(cloudDefendPage: \"policies\") => CloudDefendLinkItem" + ], + "path": "x-pack/plugins/cloud_defend/public/common/navigation/security_solution_links.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "cloudDefend", + "id": "def-public.getSecuritySolutionLink.$1", + "type": "string", + "tags": [], + "label": "cloudDefendPage", + "description": [ + "the name of the cloud defend page." + ], + "signature": [ + "\"policies\"" + ], + "path": "x-pack/plugins/cloud_defend/public/common/navigation/security_solution_links.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "cloudDefend", + "id": "def-public.getSecuritySolutionNavTab", + "type": "Function", + "tags": [], + "label": "getSecuritySolutionNavTab", + "description": [ + "\nGets the link properties of a Cloud Defend page for navigation in the old security solution navigation." + ], + "signature": [ + "(cloudDefendPage: \"policies\", basePath: string) => CloudDefendNavTab" + ], + "path": "x-pack/plugins/cloud_defend/public/common/navigation/security_solution_links.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "cloudDefend", + "id": "def-public.getSecuritySolutionNavTab.$1", + "type": "string", + "tags": [], + "label": "cloudDefendPage", + "description": [ + "the name of the cloud defend page." + ], + "signature": [ + "\"policies\"" + ], + "path": "x-pack/plugins/cloud_defend/public/common/navigation/security_solution_links.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "cloudDefend", + "id": "def-public.getSecuritySolutionNavTab.$2", + "type": "string", + "tags": [], + "label": "basePath", + "description": [ + "the base path for links." + ], + "signature": [ + "string" + ], + "path": "x-pack/plugins/cloud_defend/public/common/navigation/security_solution_links.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + } + ], + "interfaces": [ + { + "parentPluginId": "cloudDefend", + "id": "def-public.CloudDefendSecuritySolutionContext", + "type": "Interface", + "tags": [], + "label": "CloudDefendSecuritySolutionContext", + "description": [], + "path": "x-pack/plugins/cloud_defend/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "cloudDefend", + "id": "def-public.CloudDefendSecuritySolutionContext.getFiltersGlobalComponent", + "type": "Function", + "tags": [], + "label": "getFiltersGlobalComponent", + "description": [ + "Gets the `FiltersGlobal` component for embedding a filter bar in the security solution application." + ], + "signature": [ + "() => React.ComponentType<{ children: React.ReactNode; }>" + ], + "path": "x-pack/plugins/cloud_defend/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "cloudDefend", + "id": "def-public.CloudDefendSecuritySolutionContext.getSpyRouteComponent", + "type": "Function", + "tags": [], + "label": "getSpyRouteComponent", + "description": [ + "Gets the `SpyRoute` component for navigation highlighting and breadcrumbs." + ], + "signature": [ + "() => React.ComponentType<{ pageName: \"cloud_defend-policies\"; state?: Record | undefined; }>" + ], + "path": "x-pack/plugins/cloud_defend/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + } + ], + "initialIsOpen": false + } + ], "enums": [], - "misc": [], + "misc": [ + { + "parentPluginId": "cloudDefend", + "id": "def-public.CLOUD_DEFEND_BASE_PATH", + "type": "string", + "tags": [], + "label": "CLOUD_DEFEND_BASE_PATH", + "description": [ + "The base path for all cloud defend pages." + ], + "signature": [ + "\"/cloud_defend\"" + ], + "path": "x-pack/plugins/cloud_defend/public/common/navigation/constants.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "cloudDefend", + "id": "def-public.CloudDefendPageId", + "type": "Type", + "tags": [], + "label": "CloudDefendPageId", + "description": [ + "\nAll the IDs for the cloud defend pages.\nThis needs to match the cloud defend page entries in `SecurityPageName` in `x-pack/plugins/security_solution/common/constants.ts`." + ], + "signature": [ + "\"cloud_defend-policies\"" + ], + "path": "x-pack/plugins/cloud_defend/public/common/navigation/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], "objects": [], "setup": { "parentPluginId": "cloudDefend", @@ -13,7 +191,9 @@ "type": "Interface", "tags": [], "label": "CloudDefendPluginSetup", - "description": [], + "description": [ + "\ncloud_defend plugin types" + ], "path": "x-pack/plugins/cloud_defend/public/types.ts", "deprecated": false, "trackAdoption": false, @@ -31,7 +211,28 @@ "path": "x-pack/plugins/cloud_defend/public/types.ts", "deprecated": false, "trackAdoption": false, - "children": [], + "children": [ + { + "parentPluginId": "cloudDefend", + "id": "def-public.CloudDefendPluginStart.getCloudDefendRouter", + "type": "Function", + "tags": [], + "label": "getCloudDefendRouter", + "description": [ + "Gets the cloud defend router component for embedding in the security solution." + ], + "signature": [ + "() => React.ComponentType<", + "CloudDefendRouterProps", + ">" + ], + "path": "x-pack/plugins/cloud_defend/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + } + ], "lifecycle": "start", "initialIsOpen": true } @@ -42,7 +243,35 @@ "interfaces": [], "enums": [], "misc": [], - "objects": [] + "objects": [], + "setup": { + "parentPluginId": "cloudDefend", + "id": "def-server.CloudDefendPluginSetup", + "type": "Interface", + "tags": [], + "label": "CloudDefendPluginSetup", + "description": [], + "path": "x-pack/plugins/cloud_defend/server/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "lifecycle": "setup", + "initialIsOpen": true + }, + "start": { + "parentPluginId": "cloudDefend", + "id": "def-server.CloudDefendPluginStart", + "type": "Interface", + "tags": [], + "label": "CloudDefendPluginStart", + "description": [], + "path": "x-pack/plugins/cloud_defend/server/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "lifecycle": "start", + "initialIsOpen": true + } }, "common": { "classes": [], diff --git a/api_docs/cloud_defend.mdx b/api_docs/cloud_defend.mdx index a75eaeb7a2b0b..a1d62d83decbc 100644 --- a/api_docs/cloud_defend.mdx +++ b/api_docs/cloud_defend.mdx @@ -8,12 +8,12 @@ slug: /kibana-dev-docs/api/cloudDefend title: "cloudDefend" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudDefend plugin -date: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudDefend'] --- import cloudDefendObj from './cloud_defend.devdocs.json'; -Defend for Containers +Defend for containers (D4C) Contact [@elastic/sec-cloudnative-integrations](https://github.com/orgs/elastic/teams/sec-cloudnative-integrations) for questions regarding this plugin. @@ -21,7 +21,7 @@ Contact [@elastic/sec-cloudnative-integrations](https://github.com/orgs/elastic/ | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 2 | 0 | 2 | 0 | +| 15 | 0 | 4 | 1 | ## Client @@ -31,3 +31,20 @@ Contact [@elastic/sec-cloudnative-integrations](https://github.com/orgs/elastic/ ### Start +### Functions + + +### Interfaces + + +### Consts, variables and types + + +## Server + +### Setup + + +### Start + + diff --git a/api_docs/cloud_experiments.mdx b/api_docs/cloud_experiments.mdx index ea6376cd43bee..f2d8d364706e8 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: 2023-02-27 +date: 2023-03-02 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 f065f7c6053c8..1c9cfd48c4b54 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: 2023-02-27 +date: 2023-03-02 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 e008179a519e2..077424419ae67 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'console'] --- import consoleObj from './console.devdocs.json'; diff --git a/api_docs/content_management.devdocs.json b/api_docs/content_management.devdocs.json index 896f476aaffb2..0b70a55a41998 100644 --- a/api_docs/content_management.devdocs.json +++ b/api_docs/content_management.devdocs.json @@ -1,11 +1,1050 @@ { "id": "contentManagement", "client": { - "classes": [], - "functions": [], - "interfaces": [], + "classes": [ + { + "parentPluginId": "contentManagement", + "id": "def-public.ContentClient", + "type": "Class", + "tags": [], + "label": "ContentClient", + "description": [], + "path": "src/plugins/content_management/public/content_client/content_client.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-public.ContentClient.queryClient", + "type": "Object", + "tags": [], + "label": "queryClient", + "description": [], + "signature": [ + "QueryClient" + ], + "path": "src/plugins/content_management/public/content_client/content_client.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "contentManagement", + "id": "def-public.ContentClient.queryOptionBuilder", + "type": "Object", + "tags": [], + "label": "queryOptionBuilder", + "description": [], + "signature": [ + "{ get: = ", + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.GetIn", + "text": "GetIn" + }, + ", O = unknown>(input: I) => { queryKey: readonly [string, string]; queryFn: () => Promise; }; search: = ", + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.SearchIn", + "text": "SearchIn" + }, + ", O = unknown>(input: I) => { queryKey: readonly [string, \"search\", unknown]; queryFn: () => Promise; }; }" + ], + "path": "src/plugins/content_management/public/content_client/content_client.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "contentManagement", + "id": "def-public.ContentClient.Unnamed", + "type": "Function", + "tags": [], + "label": "Constructor", + "description": [], + "signature": [ + "any" + ], + "path": "src/plugins/content_management/public/content_client/content_client.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-public.ContentClient.Unnamed.$1", + "type": "Function", + "tags": [], + "label": "crudClientProvider", + "description": [], + "signature": [ + "(contentType: string) => ", + { + "pluginId": "contentManagement", + "scope": "public", + "docId": "kibContentManagementPluginApi", + "section": "def-public.CrudClient", + "text": "CrudClient" + } + ], + "path": "src/plugins/content_management/public/content_client/content_client.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "contentManagement", + "id": "def-public.ContentClient.get", + "type": "Function", + "tags": [], + "label": "get", + "description": [], + "signature": [ + " = ", + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.GetIn", + "text": "GetIn" + }, + ", O = unknown>(input: I) => Promise" + ], + "path": "src/plugins/content_management/public/content_client/content_client.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-public.ContentClient.get.$1", + "type": "Uncategorized", + "tags": [], + "label": "input", + "description": [], + "signature": [ + "I" + ], + "path": "src/plugins/content_management/public/content_client/content_client.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "contentManagement", + "id": "def-public.ContentClient.get$", + "type": "Function", + "tags": [], + "label": "get$", + "description": [], + "signature": [ + " = ", + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.GetIn", + "text": "GetIn" + }, + ", O = unknown>(input: I) => ", + "Observable", + "<", + "QueryObserverResult", + ">" + ], + "path": "src/plugins/content_management/public/content_client/content_client.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-public.ContentClient.get$.$1", + "type": "Uncategorized", + "tags": [], + "label": "input", + "description": [], + "signature": [ + "I" + ], + "path": "src/plugins/content_management/public/content_client/content_client.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "contentManagement", + "id": "def-public.ContentClient.create", + "type": "Function", + "tags": [], + "label": "create", + "description": [], + "signature": [ + ", O = unknown>(input: I) => Promise" + ], + "path": "src/plugins/content_management/public/content_client/content_client.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-public.ContentClient.create.$1", + "type": "Uncategorized", + "tags": [], + "label": "input", + "description": [], + "signature": [ + "I" + ], + "path": "src/plugins/content_management/public/content_client/content_client.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "contentManagement", + "id": "def-public.ContentClient.update", + "type": "Function", + "tags": [], + "label": "update", + "description": [], + "signature": [ + ", O = unknown>(input: I) => Promise" + ], + "path": "src/plugins/content_management/public/content_client/content_client.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-public.ContentClient.update.$1", + "type": "Uncategorized", + "tags": [], + "label": "input", + "description": [], + "signature": [ + "I" + ], + "path": "src/plugins/content_management/public/content_client/content_client.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "contentManagement", + "id": "def-public.ContentClient.delete", + "type": "Function", + "tags": [], + "label": "delete", + "description": [], + "signature": [ + ", O = unknown>(input: I) => Promise" + ], + "path": "src/plugins/content_management/public/content_client/content_client.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-public.ContentClient.delete.$1", + "type": "Uncategorized", + "tags": [], + "label": "input", + "description": [], + "signature": [ + "I" + ], + "path": "src/plugins/content_management/public/content_client/content_client.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "contentManagement", + "id": "def-public.ContentClient.search", + "type": "Function", + "tags": [], + "label": "search", + "description": [], + "signature": [ + ", O = unknown>(input: I) => Promise" + ], + "path": "src/plugins/content_management/public/content_client/content_client.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-public.ContentClient.search.$1", + "type": "Uncategorized", + "tags": [], + "label": "input", + "description": [], + "signature": [ + "I" + ], + "path": "src/plugins/content_management/public/content_client/content_client.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "contentManagement", + "id": "def-public.ContentClient.search$", + "type": "Function", + "tags": [], + "label": "search$", + "description": [], + "signature": [ + ", O = unknown>(input: I) => ", + "Observable", + "<", + "QueryObserverResult", + ">" + ], + "path": "src/plugins/content_management/public/content_client/content_client.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-public.ContentClient.search$.$1", + "type": "Uncategorized", + "tags": [], + "label": "input", + "description": [], + "signature": [ + "I" + ], + "path": "src/plugins/content_management/public/content_client/content_client.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + } + ], + "functions": [ + { + "parentPluginId": "contentManagement", + "id": "def-public.ContentClientProvider", + "type": "Function", + "tags": [], + "label": "ContentClientProvider", + "description": [], + "signature": [ + "({ contentClient, children, }: React.PropsWithChildren<{ contentClient: ", + { + "pluginId": "contentManagement", + "scope": "public", + "docId": "kibContentManagementPluginApi", + "section": "def-public.ContentClient", + "text": "ContentClient" + }, + "; }>) => JSX.Element" + ], + "path": "src/plugins/content_management/public/content_client/content_client_context.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-public.ContentClientProvider.$1", + "type": "CompoundType", + "tags": [], + "label": "{\n contentClient,\n children,\n}", + "description": [], + "signature": [ + "React.PropsWithChildren<{ contentClient: ", + { + "pluginId": "contentManagement", + "scope": "public", + "docId": "kibContentManagementPluginApi", + "section": "def-public.ContentClient", + "text": "ContentClient" + }, + "; }>" + ], + "path": "src/plugins/content_management/public/content_client/content_client_context.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "contentManagement", + "id": "def-public.useContentClient", + "type": "Function", + "tags": [], + "label": "useContentClient", + "description": [], + "signature": [ + "() => ", + { + "pluginId": "contentManagement", + "scope": "public", + "docId": "kibContentManagementPluginApi", + "section": "def-public.ContentClient", + "text": "ContentClient" + } + ], + "path": "src/plugins/content_management/public/content_client/content_client_context.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "contentManagement", + "id": "def-public.useCreateContentMutation", + "type": "Function", + "tags": [], + "label": "useCreateContentMutation", + "description": [], + "signature": [ + " = ", + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.CreateIn", + "text": "CreateIn" + }, + ", O = unknown>() => ", + "UseMutationResult", + "" + ], + "path": "src/plugins/content_management/public/content_client/content_client_mutation_hooks.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "contentManagement", + "id": "def-public.useDeleteContentMutation", + "type": "Function", + "tags": [], + "label": "useDeleteContentMutation", + "description": [], + "signature": [ + " = ", + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.DeleteIn", + "text": "DeleteIn" + }, + ", O = unknown>() => ", + "UseMutationResult", + "" + ], + "path": "src/plugins/content_management/public/content_client/content_client_mutation_hooks.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "contentManagement", + "id": "def-public.useGetContentQuery", + "type": "Function", + "tags": [], + "label": "useGetContentQuery", + "description": [ + "\n" + ], + "signature": [ + " = ", + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.GetIn", + "text": "GetIn" + }, + ", O = unknown>(input: I, queryOptions?: ", + { + "pluginId": "contentManagement", + "scope": "public", + "docId": "kibContentManagementPluginApi", + "section": "def-public.QueryOptions", + "text": "QueryOptions" + }, + " | undefined) => ", + "UseQueryResult", + "" + ], + "path": "src/plugins/content_management/public/content_client/content_client_query_hooks.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-public.useGetContentQuery.$1", + "type": "Uncategorized", + "tags": [], + "label": "input", + "description": [ + "- get content identifier like \"id\" and \"contentType\"" + ], + "signature": [ + "I" + ], + "path": "src/plugins/content_management/public/content_client/content_client_query_hooks.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "contentManagement", + "id": "def-public.useGetContentQuery.$2", + "type": "Object", + "tags": [], + "label": "queryOptions", + "description": [ + "- query options" + ], + "signature": [ + { + "pluginId": "contentManagement", + "scope": "public", + "docId": "kibContentManagementPluginApi", + "section": "def-public.QueryOptions", + "text": "QueryOptions" + }, + " | undefined" + ], + "path": "src/plugins/content_management/public/content_client/content_client_query_hooks.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "contentManagement", + "id": "def-public.useSearchContentQuery", + "type": "Function", + "tags": [], + "label": "useSearchContentQuery", + "description": [ + "\n" + ], + "signature": [ + " = ", + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.SearchIn", + "text": "SearchIn" + }, + ", O = unknown>(input: I, queryOptions?: ", + { + "pluginId": "contentManagement", + "scope": "public", + "docId": "kibContentManagementPluginApi", + "section": "def-public.QueryOptions", + "text": "QueryOptions" + }, + " | undefined) => ", + "UseQueryResult", + "" + ], + "path": "src/plugins/content_management/public/content_client/content_client_query_hooks.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-public.useSearchContentQuery.$1", + "type": "Uncategorized", + "tags": [], + "label": "input", + "description": [ + "- get content identifier like \"id\" and \"contentType\"" + ], + "signature": [ + "I" + ], + "path": "src/plugins/content_management/public/content_client/content_client_query_hooks.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "contentManagement", + "id": "def-public.useSearchContentQuery.$2", + "type": "Object", + "tags": [], + "label": "queryOptions", + "description": [ + "- query options" + ], + "signature": [ + { + "pluginId": "contentManagement", + "scope": "public", + "docId": "kibContentManagementPluginApi", + "section": "def-public.QueryOptions", + "text": "QueryOptions" + }, + " | undefined" + ], + "path": "src/plugins/content_management/public/content_client/content_client_query_hooks.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "contentManagement", + "id": "def-public.useUpdateContentMutation", + "type": "Function", + "tags": [], + "label": "useUpdateContentMutation", + "description": [], + "signature": [ + " = ", + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.UpdateIn", + "text": "UpdateIn" + }, + ", O = unknown>() => ", + "UseMutationResult", + "" + ], + "path": "src/plugins/content_management/public/content_client/content_client_mutation_hooks.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [], + "initialIsOpen": false + } + ], + "interfaces": [ + { + "parentPluginId": "contentManagement", + "id": "def-public.CrudClient", + "type": "Interface", + "tags": [], + "label": "CrudClient", + "description": [], + "path": "src/plugins/content_management/public/crud_client/crud_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-public.CrudClient.get", + "type": "Function", + "tags": [], + "label": "get", + "description": [], + "signature": [ + "(input: ", + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.GetIn", + "text": "GetIn" + }, + ") => Promise" + ], + "path": "src/plugins/content_management/public/crud_client/crud_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-public.CrudClient.get.$1", + "type": "Object", + "tags": [], + "label": "input", + "description": [], + "signature": [ + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.GetIn", + "text": "GetIn" + }, + "" + ], + "path": "src/plugins/content_management/public/crud_client/crud_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "contentManagement", + "id": "def-public.CrudClient.create", + "type": "Function", + "tags": [], + "label": "create", + "description": [], + "signature": [ + "(input: ", + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.CreateIn", + "text": "CreateIn" + }, + ") => Promise" + ], + "path": "src/plugins/content_management/public/crud_client/crud_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-public.CrudClient.create.$1", + "type": "Object", + "tags": [], + "label": "input", + "description": [], + "signature": [ + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.CreateIn", + "text": "CreateIn" + }, + "" + ], + "path": "src/plugins/content_management/public/crud_client/crud_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "contentManagement", + "id": "def-public.CrudClient.update", + "type": "Function", + "tags": [], + "label": "update", + "description": [], + "signature": [ + "(input: ", + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.UpdateIn", + "text": "UpdateIn" + }, + ") => Promise" + ], + "path": "src/plugins/content_management/public/crud_client/crud_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-public.CrudClient.update.$1", + "type": "Object", + "tags": [], + "label": "input", + "description": [], + "signature": [ + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.UpdateIn", + "text": "UpdateIn" + }, + "" + ], + "path": "src/plugins/content_management/public/crud_client/crud_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "contentManagement", + "id": "def-public.CrudClient.delete", + "type": "Function", + "tags": [], + "label": "delete", + "description": [], + "signature": [ + "(input: ", + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.DeleteIn", + "text": "DeleteIn" + }, + ") => Promise" + ], + "path": "src/plugins/content_management/public/crud_client/crud_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-public.CrudClient.delete.$1", + "type": "Object", + "tags": [], + "label": "input", + "description": [], + "signature": [ + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.DeleteIn", + "text": "DeleteIn" + }, + "" + ], + "path": "src/plugins/content_management/public/crud_client/crud_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "contentManagement", + "id": "def-public.CrudClient.search", + "type": "Function", + "tags": [], + "label": "search", + "description": [], + "signature": [ + "(input: ", + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.SearchIn", + "text": "SearchIn" + }, + ") => Promise" + ], + "path": "src/plugins/content_management/public/crud_client/crud_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-public.CrudClient.search.$1", + "type": "Object", + "tags": [], + "label": "input", + "description": [], + "signature": [ + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.SearchIn", + "text": "SearchIn" + }, + "" + ], + "path": "src/plugins/content_management/public/crud_client/crud_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + } + ], "enums": [], - "misc": [], + "misc": [ + { + "parentPluginId": "contentManagement", + "id": "def-public.QueryOptions", + "type": "Type", + "tags": [], + "label": "QueryOptions", + "description": [ + "\nExposed `useQuery` options" + ], + "signature": [ + "{ enabled?: boolean | undefined; }" + ], + "path": "src/plugins/content_management/public/content_client/content_client_query_hooks.tsx", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], "objects": [], "start": { "parentPluginId": "contentManagement", @@ -26,7 +1065,13 @@ "label": "client", "description": [], "signature": [ - "ContentClient" + { + "pluginId": "contentManagement", + "scope": "public", + "docId": "kibContentManagementPluginApi", + "section": "def-public.ContentClient", + "text": "ContentClient" + } ], "path": "src/plugins/content_management/public/types.ts", "deprecated": false, @@ -91,7 +1136,541 @@ "server": { "classes": [], "functions": [], - "interfaces": [], + "interfaces": [ + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage", + "type": "Interface", + "tags": [], + "label": "ContentStorage", + "description": [], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.get", + "type": "Function", + "tags": [], + "label": "get", + "description": [ + "Get a single item" + ], + "signature": [ + "(ctx: ", + { + "pluginId": "contentManagement", + "scope": "server", + "docId": "kibContentManagementPluginApi", + "section": "def-server.StorageContext", + "text": "StorageContext" + }, + ", id: string, options: unknown) => Promise" + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.get.$1", + "type": "Object", + "tags": [], + "label": "ctx", + "description": [], + "signature": [ + { + "pluginId": "contentManagement", + "scope": "server", + "docId": "kibContentManagementPluginApi", + "section": "def-server.StorageContext", + "text": "StorageContext" + } + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.get.$2", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "signature": [ + "string" + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.get.$3", + "type": "Unknown", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "unknown" + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.bulkGet", + "type": "Function", + "tags": [], + "label": "bulkGet", + "description": [ + "Get multiple items" + ], + "signature": [ + "(ctx: ", + { + "pluginId": "contentManagement", + "scope": "server", + "docId": "kibContentManagementPluginApi", + "section": "def-server.StorageContext", + "text": "StorageContext" + }, + ", ids: string[], options: unknown) => Promise" + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.bulkGet.$1", + "type": "Object", + "tags": [], + "label": "ctx", + "description": [], + "signature": [ + { + "pluginId": "contentManagement", + "scope": "server", + "docId": "kibContentManagementPluginApi", + "section": "def-server.StorageContext", + "text": "StorageContext" + } + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.bulkGet.$2", + "type": "Array", + "tags": [], + "label": "ids", + "description": [], + "signature": [ + "string[]" + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.bulkGet.$3", + "type": "Unknown", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "unknown" + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.create", + "type": "Function", + "tags": [], + "label": "create", + "description": [ + "Create an item" + ], + "signature": [ + "(ctx: ", + { + "pluginId": "contentManagement", + "scope": "server", + "docId": "kibContentManagementPluginApi", + "section": "def-server.StorageContext", + "text": "StorageContext" + }, + ", data: object, options: unknown) => Promise" + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.create.$1", + "type": "Object", + "tags": [], + "label": "ctx", + "description": [], + "signature": [ + { + "pluginId": "contentManagement", + "scope": "server", + "docId": "kibContentManagementPluginApi", + "section": "def-server.StorageContext", + "text": "StorageContext" + } + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.create.$2", + "type": "Uncategorized", + "tags": [], + "label": "data", + "description": [], + "signature": [ + "object" + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.create.$3", + "type": "Unknown", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "unknown" + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.update", + "type": "Function", + "tags": [], + "label": "update", + "description": [ + "Update an item" + ], + "signature": [ + "(ctx: ", + { + "pluginId": "contentManagement", + "scope": "server", + "docId": "kibContentManagementPluginApi", + "section": "def-server.StorageContext", + "text": "StorageContext" + }, + ", id: string, data: object, options: unknown) => Promise" + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.update.$1", + "type": "Object", + "tags": [], + "label": "ctx", + "description": [], + "signature": [ + { + "pluginId": "contentManagement", + "scope": "server", + "docId": "kibContentManagementPluginApi", + "section": "def-server.StorageContext", + "text": "StorageContext" + } + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.update.$2", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "signature": [ + "string" + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.update.$3", + "type": "Uncategorized", + "tags": [], + "label": "data", + "description": [], + "signature": [ + "object" + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.update.$4", + "type": "Unknown", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "unknown" + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.delete", + "type": "Function", + "tags": [], + "label": "delete", + "description": [ + "Delete an item" + ], + "signature": [ + "(ctx: ", + { + "pluginId": "contentManagement", + "scope": "server", + "docId": "kibContentManagementPluginApi", + "section": "def-server.StorageContext", + "text": "StorageContext" + }, + ", id: string, options: unknown) => Promise" + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.delete.$1", + "type": "Object", + "tags": [], + "label": "ctx", + "description": [], + "signature": [ + { + "pluginId": "contentManagement", + "scope": "server", + "docId": "kibContentManagementPluginApi", + "section": "def-server.StorageContext", + "text": "StorageContext" + } + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.delete.$2", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "signature": [ + "string" + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.delete.$3", + "type": "Unknown", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "unknown" + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.search", + "type": "Function", + "tags": [], + "label": "search", + "description": [ + "Search items" + ], + "signature": [ + "(ctx: ", + { + "pluginId": "contentManagement", + "scope": "server", + "docId": "kibContentManagementPluginApi", + "section": "def-server.StorageContext", + "text": "StorageContext" + }, + ", query: object, options: unknown) => Promise" + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.search.$1", + "type": "Object", + "tags": [], + "label": "ctx", + "description": [], + "signature": [ + { + "pluginId": "contentManagement", + "scope": "server", + "docId": "kibContentManagementPluginApi", + "section": "def-server.StorageContext", + "text": "StorageContext" + } + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.search.$2", + "type": "Uncategorized", + "tags": [], + "label": "query", + "description": [], + "signature": [ + "object" + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "contentManagement", + "id": "def-server.ContentStorage.search.$3", + "type": "Unknown", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "unknown" + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "contentManagement", + "id": "def-server.StorageContext", + "type": "Interface", + "tags": [], + "label": "StorageContext", + "description": [ + "Context that is sent to all storage instance methods" + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "contentManagement", + "id": "def-server.StorageContext.requestHandlerContext", + "type": "Object", + "tags": [], + "label": "requestHandlerContext", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-http-request-handler-context-server", + "scope": "common", + "docId": "kibKbnCoreHttpRequestHandlerContextServerPluginApi", + "section": "def-common.RequestHandlerContext", + "text": "RequestHandlerContext" + } + ], + "path": "src/plugins/content_management/server/core/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ], "enums": [], "misc": [], "objects": [], @@ -102,6 +1681,17 @@ "tags": [], "label": "ContentManagementServerSetup", "description": [], + "signature": [ + { + "pluginId": "contentManagement", + "scope": "server", + "docId": "kibContentManagementPluginApi", + "section": "def-server.ContentManagementServerSetup", + "text": "ContentManagementServerSetup" + }, + " extends ", + "CoreApi" + ], "path": "src/plugins/content_management/server/types.ts", "deprecated": false, "trackAdoption": false, @@ -598,7 +2188,7 @@ "label": "API_ENDPOINT", "description": [], "signature": [ - "\"/api/content_management\"" + "\"/api/content_management/rpc\"" ], "path": "src/plugins/content_management/common/constants.ts", "deprecated": false, @@ -651,140 +2241,6 @@ "deprecated": false, "trackAdoption": false, "initialIsOpen": false - }, - { - "parentPluginId": "contentManagement", - "id": "def-common.schemas", - "type": "Object", - "tags": [], - "label": "schemas", - "description": [], - "path": "src/plugins/content_management/common/rpc/rpc.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "contentManagement", - "id": "def-common.schemas.get", - "type": "Object", - "tags": [], - "label": "get", - "description": [], - "signature": [ - { - "pluginId": "contentManagement", - "scope": "common", - "docId": "kibContentManagementPluginApi", - "section": "def-common.ProcedureSchemas", - "text": "ProcedureSchemas" - } - ], - "path": "src/plugins/content_management/common/rpc/rpc.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "contentManagement", - "id": "def-common.schemas.bulkGet", - "type": "Object", - "tags": [], - "label": "bulkGet", - "description": [], - "signature": [ - { - "pluginId": "contentManagement", - "scope": "common", - "docId": "kibContentManagementPluginApi", - "section": "def-common.ProcedureSchemas", - "text": "ProcedureSchemas" - } - ], - "path": "src/plugins/content_management/common/rpc/rpc.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "contentManagement", - "id": "def-common.schemas.create", - "type": "Object", - "tags": [], - "label": "create", - "description": [], - "signature": [ - { - "pluginId": "contentManagement", - "scope": "common", - "docId": "kibContentManagementPluginApi", - "section": "def-common.ProcedureSchemas", - "text": "ProcedureSchemas" - } - ], - "path": "src/plugins/content_management/common/rpc/rpc.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "contentManagement", - "id": "def-common.schemas.update", - "type": "Object", - "tags": [], - "label": "update", - "description": [], - "signature": [ - { - "pluginId": "contentManagement", - "scope": "common", - "docId": "kibContentManagementPluginApi", - "section": "def-common.ProcedureSchemas", - "text": "ProcedureSchemas" - } - ], - "path": "src/plugins/content_management/common/rpc/rpc.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "contentManagement", - "id": "def-common.schemas.delete", - "type": "Object", - "tags": [], - "label": "delete", - "description": [], - "signature": [ - { - "pluginId": "contentManagement", - "scope": "common", - "docId": "kibContentManagementPluginApi", - "section": "def-common.ProcedureSchemas", - "text": "ProcedureSchemas" - } - ], - "path": "src/plugins/content_management/common/rpc/rpc.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "contentManagement", - "id": "def-common.schemas.search", - "type": "Object", - "tags": [], - "label": "search", - "description": [], - "signature": [ - { - "pluginId": "contentManagement", - "scope": "common", - "docId": "kibContentManagementPluginApi", - "section": "def-common.ProcedureSchemas", - "text": "ProcedureSchemas" - } - ], - "path": "src/plugins/content_management/common/rpc/rpc.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false } ] } diff --git a/api_docs/content_management.mdx b/api_docs/content_management.mdx index f14ff89397cde..a1249c4b5ba37 100644 --- a/api_docs/content_management.mdx +++ b/api_docs/content_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/contentManagement title: "contentManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the contentManagement plugin -date: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'contentManagement'] --- import contentManagementObj from './content_management.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sh | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 46 | 0 | 46 | 3 | +| 110 | 0 | 96 | 3 | ## Client @@ -31,6 +31,18 @@ Contact [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sh ### Start +### Functions + + +### Classes + + +### Interfaces + + +### Consts, variables and types + + ## Server ### Setup @@ -39,6 +51,9 @@ Contact [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sh ### Start +### Interfaces + + ## Common ### Objects diff --git a/api_docs/controls.mdx b/api_docs/controls.mdx index 99a0b87121e18..ae450f37bed7c 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'controls'] --- import controlsObj from './controls.devdocs.json'; diff --git a/api_docs/custom_integrations.mdx b/api_docs/custom_integrations.mdx index f011348359e18..cc8dd895b8951 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: 2023-02-27 +date: 2023-03-02 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 b341979bce4f1..78208b4775831 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: 2023-02-27 +date: 2023-03-02 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 f84933f0b1b4e..dee012217c67b 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: 2023-02-27 +date: 2023-03-02 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 b2be9a6510899..ea8e61e7d1903 100644 --- a/api_docs/data.devdocs.json +++ b/api_docs/data.devdocs.json @@ -13087,35 +13087,35 @@ }, { "plugin": "expressionPartitionVis", - "path": "src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.test.ts" + "path": "src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_color.test.ts" }, { "plugin": "expressionPartitionVis", - "path": "src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.test.ts" + "path": "src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_color.test.ts" }, { "plugin": "expressionPartitionVis", - "path": "src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.test.ts" + "path": "src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_color.test.ts" }, { "plugin": "expressionPartitionVis", - "path": "src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.test.ts" + "path": "src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_color.test.ts" }, { "plugin": "expressionPartitionVis", - "path": "src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.test.ts" + "path": "src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_color.test.ts" }, { "plugin": "expressionPartitionVis", - "path": "src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.test.ts" + "path": "src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_color.test.ts" }, { "plugin": "expressionPartitionVis", - "path": "src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.test.ts" + "path": "src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_color.test.ts" }, { "plugin": "expressionPartitionVis", - "path": "src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.test.ts" + "path": "src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_color.test.ts" } ] }, @@ -13729,6 +13729,14 @@ "plugin": "dataViews", "path": "src/plugins/data_views/common/data_views/data_view.ts" }, + { + "plugin": "savedObjectsManagement", + "path": "src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx" + }, + { + "plugin": "savedObjectsManagement", + "path": "src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx" + }, { "plugin": "unifiedSearch", "path": "src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts" @@ -13753,14 +13761,6 @@ "plugin": "triggersActionsUi", "path": "x-pack/plugins/triggers_actions_ui/public/common/lib/data_apis.ts" }, - { - "plugin": "savedObjectsManagement", - "path": "src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx" - }, - { - "plugin": "savedObjectsManagement", - "path": "src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx" - }, { "plugin": "controls", "path": "src/plugins/controls/public/services/options_list/options_list_service.ts" @@ -21352,6 +21352,14 @@ "plugin": "dataViews", "path": "src/plugins/data_views/common/data_views/data_view.ts" }, + { + "plugin": "savedObjectsManagement", + "path": "src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx" + }, + { + "plugin": "savedObjectsManagement", + "path": "src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx" + }, { "plugin": "unifiedSearch", "path": "src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts" @@ -21376,14 +21384,6 @@ "plugin": "triggersActionsUi", "path": "x-pack/plugins/triggers_actions_ui/public/common/lib/data_apis.ts" }, - { - "plugin": "savedObjectsManagement", - "path": "src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx" - }, - { - "plugin": "savedObjectsManagement", - "path": "src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx" - }, { "plugin": "controls", "path": "src/plugins/controls/public/services/options_list/options_list_service.ts" diff --git a/api_docs/data.mdx b/api_docs/data.mdx index 717733b31880f..6545ee735da87 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data'] --- import dataObj from './data.devdocs.json'; diff --git a/api_docs/data_query.mdx b/api_docs/data_query.mdx index 2eb4ba583ca7d..641d4d68f2dac 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.query'] --- import dataQueryObj from './data_query.devdocs.json'; diff --git a/api_docs/data_search.mdx b/api_docs/data_search.mdx index 55145af95934b..f53eff91b211e 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.search'] --- import dataSearchObj from './data_search.devdocs.json'; diff --git a/api_docs/data_view_editor.mdx b/api_docs/data_view_editor.mdx index 2c803c9c2d839..afb18de3eb68c 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: 2023-02-27 +date: 2023-03-02 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 79135b32e8e81..895ca68247a5b 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: 2023-02-27 +date: 2023-03-02 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 b685ec997b5d0..521c678ffc05d 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: 2023-02-27 +date: 2023-03-02 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 ba7f5aa586f15..45193f697c63e 100644 --- a/api_docs/data_views.devdocs.json +++ b/api_docs/data_views.devdocs.json @@ -283,6 +283,14 @@ "plugin": "data", "path": "src/plugins/data/public/search/errors/painless_error.tsx" }, + { + "plugin": "savedObjectsManagement", + "path": "src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx" + }, + { + "plugin": "savedObjectsManagement", + "path": "src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx" + }, { "plugin": "unifiedSearch", "path": "src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts" @@ -299,14 +307,6 @@ "plugin": "triggersActionsUi", "path": "x-pack/plugins/triggers_actions_ui/public/common/lib/data_apis.ts" }, - { - "plugin": "savedObjectsManagement", - "path": "src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx" - }, - { - "plugin": "savedObjectsManagement", - "path": "src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx" - }, { "plugin": "controls", "path": "src/plugins/controls/public/services/options_list/options_list_service.ts" @@ -8491,6 +8491,14 @@ "plugin": "data", "path": "src/plugins/data/public/search/errors/painless_error.tsx" }, + { + "plugin": "savedObjectsManagement", + "path": "src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx" + }, + { + "plugin": "savedObjectsManagement", + "path": "src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx" + }, { "plugin": "unifiedSearch", "path": "src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts" @@ -8507,14 +8515,6 @@ "plugin": "triggersActionsUi", "path": "x-pack/plugins/triggers_actions_ui/public/common/lib/data_apis.ts" }, - { - "plugin": "savedObjectsManagement", - "path": "src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx" - }, - { - "plugin": "savedObjectsManagement", - "path": "src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx" - }, { "plugin": "controls", "path": "src/plugins/controls/public/services/options_list/options_list_service.ts" @@ -15794,6 +15794,14 @@ "plugin": "data", "path": "src/plugins/data/public/search/errors/painless_error.tsx" }, + { + "plugin": "savedObjectsManagement", + "path": "src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx" + }, + { + "plugin": "savedObjectsManagement", + "path": "src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx" + }, { "plugin": "unifiedSearch", "path": "src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts" @@ -15810,14 +15818,6 @@ "plugin": "triggersActionsUi", "path": "x-pack/plugins/triggers_actions_ui/public/common/lib/data_apis.ts" }, - { - "plugin": "savedObjectsManagement", - "path": "src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx" - }, - { - "plugin": "savedObjectsManagement", - "path": "src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx" - }, { "plugin": "controls", "path": "src/plugins/controls/public/services/options_list/options_list_service.ts" diff --git a/api_docs/data_views.mdx b/api_docs/data_views.mdx index 0ca6b88b3bb74..53cee388cd196 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViews'] --- import dataViewsObj from './data_views.devdocs.json'; diff --git a/api_docs/data_visualizer.mdx b/api_docs/data_visualizer.mdx index 12a25580eb6e2..e4fa7ec8e0cbb 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: 2023-02-27 +date: 2023-03-02 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 ed6456585150b..9639366079728 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -17,17 +17,17 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | Deprecated API | Referencing plugin(s) | Remove By | | ---------------|-----------|-----------| | | ml, stackAlerts | - | -| | @kbn/es-query, securitySolution, timelines, lists, threatIntelligence, dataViews, unifiedSearch, triggersActionsUi, savedObjectsManagement, controls, unifiedFieldList, lens, aiops, ml, infra, visTypeTimeseries, apm, observability, dataVisualizer, fleet, canvas, graph, stackAlerts, synthetics, transform, upgradeAssistant, ux, maps, dataViewManagement, inputControlVis, visDefaultEditor, presentationUtil, visTypeTimelion, visTypeVega, discover, data | - | -| | @kbn/es-query, securitySolution, timelines, lists, threatIntelligence, dataViews, unifiedSearch, triggersActionsUi, savedObjectsManagement, controls, unifiedFieldList, lens, aiops, ml, infra, visTypeTimeseries, apm, observability, dataVisualizer, fleet, canvas, graph, stackAlerts, synthetics, transform, upgradeAssistant, ux, maps, dataViewManagement, inputControlVis, visDefaultEditor, presentationUtil, visTypeTimelion, visTypeVega, discover, data | - | -| | @kbn/es-query, securitySolution, timelines, lists, threatIntelligence, data, unifiedSearch, triggersActionsUi, savedObjectsManagement, controls, unifiedFieldList, lens, aiops, ml, infra, visTypeTimeseries, apm, observability, dataVisualizer, fleet, canvas, graph, stackAlerts, synthetics, transform, upgradeAssistant, ux, maps, dataViewManagement, inputControlVis, visDefaultEditor, presentationUtil, visTypeTimelion, visTypeVega, discover | - | +| | @kbn/es-query, securitySolution, timelines, lists, threatIntelligence, dataViews, savedObjectsManagement, unifiedSearch, triggersActionsUi, controls, unifiedFieldList, lens, aiops, ml, infra, visTypeTimeseries, apm, observability, dataVisualizer, fleet, canvas, graph, stackAlerts, synthetics, transform, upgradeAssistant, ux, maps, dataViewManagement, inputControlVis, visDefaultEditor, presentationUtil, visTypeTimelion, visTypeVega, discover, data | - | +| | @kbn/es-query, securitySolution, timelines, lists, threatIntelligence, dataViews, savedObjectsManagement, unifiedSearch, triggersActionsUi, controls, unifiedFieldList, lens, aiops, ml, infra, visTypeTimeseries, apm, observability, dataVisualizer, fleet, canvas, graph, stackAlerts, synthetics, transform, upgradeAssistant, ux, maps, dataViewManagement, inputControlVis, visDefaultEditor, presentationUtil, visTypeTimelion, visTypeVega, discover, data | - | +| | @kbn/es-query, securitySolution, timelines, lists, threatIntelligence, data, savedObjectsManagement, unifiedSearch, triggersActionsUi, controls, unifiedFieldList, lens, aiops, ml, infra, visTypeTimeseries, apm, observability, dataVisualizer, fleet, canvas, graph, stackAlerts, synthetics, transform, upgradeAssistant, ux, maps, dataViewManagement, inputControlVis, visDefaultEditor, presentationUtil, visTypeTimelion, visTypeVega, discover | - | | | home, data, esUiShared, spaces, savedObjectsManagement, fleet, observability, ml, apm, enterpriseSearch, indexLifecycleManagement, synthetics, upgradeAssistant, ux, kibanaOverview | - | | | encryptedSavedObjects, actions, data, ml, logstash, securitySolution, cloudChat | - | | | actions, ml, savedObjectsTagging, enterpriseSearch | - | -| | @kbn/core-plugins-browser-internal, @kbn/core-root-browser-internal, dataViews, home, data, savedObjects, unifiedSearch, presentationUtil, visualizations, dashboard, lens, discover, cases, fileUpload, maps, ml, infra, fleet, canvas, dashboardEnhanced, graph, monitoring, synthetics, transform, watcher, dataVisualizer, cloudSecurityPosture, securitySolution | - | +| | @kbn/core-plugins-browser-internal, @kbn/core-root-browser-internal, dataViews, home, data, savedObjects, unifiedSearch, presentationUtil, visualizations, dashboard, lens, discover, fileUpload, maps, ml, infra, fleet, canvas, dashboardEnhanced, graph, monitoring, synthetics, transform, watcher, dataVisualizer, cloudSecurityPosture, securitySolution | - | | | @kbn/core-saved-objects-browser, @kbn/core-saved-objects-browser-internal, @kbn/core, dataViews, home, savedObjects, savedSearch, visualizations, dashboard, lens, ml, canvas, graph, securitySolution, synthetics, watcher, visTypeTimeseries, @kbn/core-saved-objects-browser-mocks | - | -| | @kbn/core-saved-objects-browser-mocks, dataViews, savedObjects, presentationUtil, savedSearch, visualizations, dashboard, lens, cases, maps, ml, infra, cloudSecurityPosture, dashboardEnhanced, graph, securitySolution, synthetics, @kbn/core-saved-objects-browser-internal | - | +| | @kbn/core-saved-objects-browser-mocks, dataViews, savedObjects, presentationUtil, savedSearch, visualizations, dashboard, lens, maps, ml, infra, cloudSecurityPosture, dashboardEnhanced, graph, securitySolution, synthetics, @kbn/core-saved-objects-browser-internal | - | | | @kbn/core-saved-objects-browser-mocks, dataViews, savedObjects, visualizations, dashboard, ml, infra, cloudSecurityPosture, dashboardEnhanced, monitoring, synthetics, @kbn/core-saved-objects-browser-internal | - | -| | @kbn/core-saved-objects-browser-internal, @kbn/core, dataViews, savedObjects, embeddable, presentationUtil, visualizations, dashboard, lens, aiops, ml, cases, maps, dataVisualizer, infra, fleet, cloudSecurityPosture, dashboardEnhanced, graph, synthetics, securitySolution, @kbn/core-saved-objects-browser-mocks | - | +| | @kbn/core-saved-objects-browser-internal, @kbn/core, dataViews, savedObjects, embeddable, presentationUtil, visualizations, dashboard, lens, aiops, ml, maps, dataVisualizer, infra, fleet, cloudSecurityPosture, dashboardEnhanced, graph, synthetics, securitySolution, @kbn/core-saved-objects-browser-mocks | - | | | @kbn/core-lifecycle-browser-mocks, @kbn/core, ml, dashboard, dataViews, savedSearch, @kbn/core-plugins-browser-internal | - | | | @kbn/core, savedObjects, embeddable, visualizations, dashboard, fleet, infra, canvas, graph, ml, @kbn/core-saved-objects-common, @kbn/core-saved-objects-server, actions, alerting, savedSearch, enterpriseSearch, securitySolution, taskManager, @kbn/core-saved-objects-server-internal, @kbn/core-saved-objects-api-server | - | | | stackAlerts, alerting, securitySolution, inputControlVis | - | diff --git a/api_docs/deprecations_by_plugin.mdx b/api_docs/deprecations_by_plugin.mdx index 90479084fc698..883246adfda69 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -382,9 +382,6 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | ---------------|-----------|-----------| | | [so_references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/so_references.ts#:~:text=SavedObject), [so_references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/so_references.ts#:~:text=SavedObject), [so_references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/so_references.ts#:~:text=SavedObject), [so_references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/so_references.ts#:~:text=SavedObject), [find.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/operations/find.ts#:~:text=SavedObject), [find.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/operations/find.ts#:~:text=SavedObject), [find.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/operations/find.ts#:~:text=SavedObject), [test_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/test_utils.ts#:~:text=SavedObject), [test_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/test_utils.ts#:~:text=SavedObject), [test_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/test_utils.ts#:~:text=SavedObject)+ 8 more | - | | | [so_references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/so_references.ts#:~:text=SavedObject), [so_references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/so_references.ts#:~:text=SavedObject), [so_references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/so_references.ts#:~:text=SavedObject), [so_references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/so_references.ts#:~:text=SavedObject), [find.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/operations/find.ts#:~:text=SavedObject), [find.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/operations/find.ts#:~:text=SavedObject), [find.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/operations/find.ts#:~:text=SavedObject), [test_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/test_utils.ts#:~:text=SavedObject), [test_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/test_utils.ts#:~:text=SavedObject), [test_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/test_utils.ts#:~:text=SavedObject)+ 44 more | - | -| | [saved_objects_finder.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx#:~:text=savedObjects), [plugin.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/plugin.tsx#:~:text=savedObjects) | - | -| | [saved_objects_finder.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx#:~:text=find) | - | -| | [saved_objects_finder.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx#:~:text=SimpleSavedObject), [saved_objects_finder.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx#:~:text=SimpleSavedObject), [saved_objects_finder.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx#:~:text=SimpleSavedObject), [saved_objects_finder.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx#:~:text=SimpleSavedObject), [saved_objects_finder.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx#:~:text=SimpleSavedObject), [saved_objects_finder.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx#:~:text=SimpleSavedObject), [saved_objects_finder.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx#:~:text=SimpleSavedObject), [saved_objects_finder.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx#:~:text=SimpleSavedObject), [saved_objects_finder.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx#:~:text=SimpleSavedObject), [saved_objects_finder.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx#:~:text=SimpleSavedObject)+ 1 more | - | | | [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/common/ui/types.ts#:~:text=ResolvedSimpleSavedObject), [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/common/ui/types.ts#:~:text=ResolvedSimpleSavedObject), [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/common/ui/types.ts#:~:text=ResolvedSimpleSavedObject), [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/common/ui/types.ts#:~:text=ResolvedSimpleSavedObject) | - | | | [so_references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/attachment_framework/so_references.ts#:~:text=SavedObjectReference), [so_references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/attachment_framework/so_references.ts#:~:text=SavedObjectReference), [so_references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/attachment_framework/so_references.ts#:~:text=SavedObjectReference), [so_references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/attachment_framework/so_references.ts#:~:text=SavedObjectReference), [so_references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/so_references.ts#:~:text=SavedObjectReference), [so_references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/so_references.ts#:~:text=SavedObjectReference), [so_references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/so_references.ts#:~:text=SavedObjectReference), [test_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/test_utils.ts#:~:text=SavedObjectReference), [test_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/test_utils.ts#:~:text=SavedObjectReference) | - | | | [cases.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/saved_object_types/cases.ts#:~:text=convertToMultiNamespaceTypeVersion), [configure.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/saved_object_types/configure.ts#:~:text=convertToMultiNamespaceTypeVersion), [comments.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/saved_object_types/comments.ts#:~:text=convertToMultiNamespaceTypeVersion), [user_actions.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/saved_object_types/user_actions.ts#:~:text=convertToMultiNamespaceTypeVersion), [connector_mappings.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/saved_object_types/connector_mappings.ts#:~:text=convertToMultiNamespaceTypeVersion) | - | @@ -628,7 +625,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| -| | [get_layers.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.test.ts#:~:text=fieldFormats), [get_layers.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.test.ts#:~:text=fieldFormats), [get_layers.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.test.ts#:~:text=fieldFormats), [get_layers.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.test.ts#:~:text=fieldFormats), [get_layers.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.test.ts#:~:text=fieldFormats), [get_layers.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.test.ts#:~:text=fieldFormats), [get_layers.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.test.ts#:~:text=fieldFormats), [get_layers.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.test.ts#:~:text=fieldFormats) | - | +| | [get_color.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_color.test.ts#:~:text=fieldFormats), [get_color.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_color.test.ts#:~:text=fieldFormats), [get_color.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_color.test.ts#:~:text=fieldFormats), [get_color.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_color.test.ts#:~:text=fieldFormats), [get_color.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_color.test.ts#:~:text=fieldFormats), [get_color.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_color.test.ts#:~:text=fieldFormats), [get_color.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_color.test.ts#:~:text=fieldFormats), [get_color.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_color.test.ts#:~:text=fieldFormats) | - | @@ -1179,12 +1176,12 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ | | [filter_group.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/filter_group/filter_group.tsx#:~:text=title), [filters_expression_select.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/alerts/monitor_expressions/filters_expression_select.tsx#:~:text=title) | - | | | [stderr_logs.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/synthetics/check_steps/stderr_logs.tsx#:~:text=indexPatternId), [stderr_logs.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/stderr_logs.tsx#:~:text=indexPatternId) | - | | | [alert_messages.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/lib/alert_types/alert_messages.tsx#:~:text=RedirectAppLinks), [alert_messages.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/lib/alert_types/alert_messages.tsx#:~:text=RedirectAppLinks), [alert_messages.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/lib/alert_types/alert_messages.tsx#:~:text=RedirectAppLinks), [uptime_app.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/app/uptime_app.tsx#:~:text=RedirectAppLinks), [uptime_app.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/app/uptime_app.tsx#:~:text=RedirectAppLinks), [uptime_app.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/app/uptime_app.tsx#:~:text=RedirectAppLinks), [synthetics_app.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/synthetics_app.tsx#:~:text=RedirectAppLinks), [synthetics_app.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/synthetics_app.tsx#:~:text=RedirectAppLinks), [synthetics_app.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/synthetics_app.tsx#:~:text=RedirectAppLinks) | - | -| | [use_filters.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/use_filters.ts#:~:text=savedObjects), [use_monitor_name.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/use_monitor_name.ts#:~:text=savedObjects), [use_location_monitors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/hooks/use_location_monitors.ts#:~:text=savedObjects), [use_locations_api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/hooks/use_locations_api.ts#:~:text=savedObjects), [use_invalid_monitors.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_invalid_monitors.tsx#:~:text=savedObjects), [use_recently_viewed_monitors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_selector/use_recently_viewed_monitors.ts#:~:text=savedObjects), [delete_param.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/delete_param.tsx#:~:text=savedObjects), [use_location_monitors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_location_monitors.ts#:~:text=savedObjects), [use_locations_api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_locations_api.ts#:~:text=savedObjects), [use_monitor_name.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/hooks/use_monitor_name.ts#:~:text=savedObjects)+ 1 more | - | -| | [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/api.ts#:~:text=SavedObjectsClientContract), [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/api.ts#:~:text=SavedObjectsClientContract), [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/api.ts#:~:text=SavedObjectsClientContract), [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/api.ts#:~:text=SavedObjectsClientContract), [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/api.ts#:~:text=SavedObjectsClientContract), [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/api.ts#:~:text=SavedObjectsClientContract) | - | -| | [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/api.ts#:~:text=create), [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/api.ts#:~:text=create) | - | +| | [use_filters.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/use_filters.ts#:~:text=savedObjects), [use_monitor_name.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/use_monitor_name.ts#:~:text=savedObjects), [use_location_monitors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/hooks/use_location_monitors.ts#:~:text=savedObjects), [use_locations_api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/hooks/use_locations_api.ts#:~:text=savedObjects), [use_invalid_monitors.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_invalid_monitors.tsx#:~:text=savedObjects), [use_recently_viewed_monitors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_selector/use_recently_viewed_monitors.ts#:~:text=savedObjects), [delete_param.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/delete_param.tsx#:~:text=savedObjects), [use_location_monitors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_location_monitors.ts#:~:text=savedObjects), [use_monitor_name.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/hooks/use_monitor_name.ts#:~:text=savedObjects), [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/plugin.ts#:~:text=savedObjects) | - | +| | [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/api.ts#:~:text=SavedObjectsClientContract), [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/api.ts#:~:text=SavedObjectsClientContract), [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/api.ts#:~:text=SavedObjectsClientContract) | - | +| | [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/api.ts#:~:text=create) | - | | | [delete_param.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/delete_param.tsx#:~:text=bulkDelete) | - | | | [use_filters.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/use_filters.ts#:~:text=find), [use_monitor_name.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/use_monitor_name.ts#:~:text=find), [use_location_monitors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/hooks/use_location_monitors.ts#:~:text=find), [use_location_monitors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_location_monitors.ts#:~:text=find), [use_monitor_name.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/hooks/use_monitor_name.ts#:~:text=find), [use_filters.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/use_filters.test.ts#:~:text=find), [use_filters.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/use_filters.test.ts#:~:text=find) | - | -| | [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/api.ts#:~:text=get), [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/api.ts#:~:text=get) | - | +| | [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/api.ts#:~:text=get) | - | | | [use_invalid_monitors.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_invalid_monitors.tsx#:~:text=bulkResolve), [use_recently_viewed_monitors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_selector/use_recently_viewed_monitors.ts#:~:text=bulkResolve) | - | | | [synthetics_monitor.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/common/types/synthetics_monitor.ts#:~:text=SimpleSavedObject), [synthetics_monitor.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/common/types/synthetics_monitor.ts#:~:text=SimpleSavedObject), [synthetics_monitor.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/common/types/synthetics_monitor.ts#:~:text=SimpleSavedObject) | - | diff --git a/api_docs/deprecations_by_team.mdx b/api_docs/deprecations_by_team.mdx index a4257edc748df..738aca7a7e31b 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/dev_tools.mdx b/api_docs/dev_tools.mdx index ccf63dda93828..304ae62c0106f 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: 2023-02-27 +date: 2023-03-02 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 ddef23b4a631e..17563076bad59 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: 2023-02-27 +date: 2023-03-02 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 28887555fed58..52ccdeefff620 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discoverEnhanced'] --- import discoverEnhancedObj from './discover_enhanced.devdocs.json'; diff --git a/api_docs/ecs_data_quality_dashboard.mdx b/api_docs/ecs_data_quality_dashboard.mdx index 36b3d4dcf7acb..f65e8737f6bbb 100644 --- a/api_docs/ecs_data_quality_dashboard.mdx +++ b/api_docs/ecs_data_quality_dashboard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ecsDataQualityDashboard title: "ecsDataQualityDashboard" image: https://source.unsplash.com/400x175/?github description: API docs for the ecsDataQualityDashboard plugin -date: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ecsDataQualityDashboard'] --- import ecsDataQualityDashboardObj from './ecs_data_quality_dashboard.devdocs.json'; diff --git a/api_docs/embeddable.devdocs.json b/api_docs/embeddable.devdocs.json index 4ac29344dbe29..f0d95e9e1edbd 100644 --- a/api_docs/embeddable.devdocs.json +++ b/api_docs/embeddable.devdocs.json @@ -6250,6 +6250,59 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "embeddable", + "id": "def-public.shouldFetch$", + "type": "Function", + "tags": [], + "label": "shouldFetch$", + "description": [], + "signature": [ + "(updated$: ", + "Observable", + ", getInput: () => TFilterableEmbeddableInput) => ", + "Observable", + "" + ], + "path": "src/plugins/embeddable/public/lib/filterable_embeddable/should_fetch.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "embeddable", + "id": "def-public.shouldFetch$.$1", + "type": "Object", + "tags": [], + "label": "updated$", + "description": [], + "signature": [ + "Observable", + "" + ], + "path": "src/plugins/embeddable/public/lib/filterable_embeddable/should_fetch.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "embeddable", + "id": "def-public.shouldFetch$.$2", + "type": "Function", + "tags": [], + "label": "getInput", + "description": [], + "signature": [ + "() => TFilterableEmbeddableInput" + ], + "path": "src/plugins/embeddable/public/lib/filterable_embeddable/should_fetch.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "embeddable", "id": "def-public.useEmbeddableFactory", @@ -8121,6 +8174,26 @@ "path": "src/plugins/embeddable/public/plugin.tsx", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "embeddable", + "id": "def-public.EmbeddableStartDependencies.savedObjectsManagement", + "type": "Object", + "tags": [], + "label": "savedObjectsManagement", + "description": [], + "signature": [ + { + "pluginId": "savedObjectsManagement", + "scope": "public", + "docId": "kibSavedObjectsManagementPluginApi", + "section": "def-public.SavedObjectsManagementPluginStart", + "text": "SavedObjectsManagementPluginStart" + } + ], + "path": "src/plugins/embeddable/public/plugin.tsx", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -10711,6 +10784,47 @@ } ], "initialIsOpen": false + }, + { + "parentPluginId": "embeddable", + "id": "def-public.shouldRefreshFilterCompareOptions", + "type": "Object", + "tags": [], + "label": "shouldRefreshFilterCompareOptions", + "description": [], + "path": "src/plugins/embeddable/public/lib/filterable_embeddable/should_fetch.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "embeddable", + "id": "def-public.shouldRefreshFilterCompareOptions.Unnamed", + "type": "Any", + "tags": [], + "label": "Unnamed", + "description": [], + "signature": [ + "any" + ], + "path": "src/plugins/embeddable/public/lib/filterable_embeddable/should_fetch.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "embeddable", + "id": "def-public.shouldRefreshFilterCompareOptions.state", + "type": "boolean", + "tags": [], + "label": "state", + "description": [ + "// do not compare $state to avoid refreshing when filter is pinned/unpinned (which does not impact results)" + ], + "path": "src/plugins/embeddable/public/lib/filterable_embeddable/should_fetch.tsx", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false } ], "setup": { diff --git a/api_docs/embeddable.mdx b/api_docs/embeddable.mdx index a86f88c5770a3..c80efe99fc85f 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddable'] --- import embeddableObj from './embeddable.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kib | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 532 | 8 | 430 | 4 | +| 539 | 9 | 436 | 4 | ## Client diff --git a/api_docs/embeddable_enhanced.mdx b/api_docs/embeddable_enhanced.mdx index c6159057905f2..6117c6df39dad 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: 2023-02-27 +date: 2023-03-02 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 915fbca25d243..dc277f689ef82 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: 2023-02-27 +date: 2023-03-02 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 ac199094d61f5..3e1821034dbd5 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: 2023-02-27 +date: 2023-03-02 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 93f25aafbaab0..9d5c5c84ea989 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: 2023-02-27 +date: 2023-03-02 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 9dc9bbfad5134..c2f5cac318ca3 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: 2023-02-27 +date: 2023-03-02 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 960b7a226d30c..ef90f20d24aa0 100644 --- a/api_docs/event_log.devdocs.json +++ b/api_docs/event_log.devdocs.json @@ -1514,7 +1514,7 @@ "label": "data", "description": [], "signature": [ - "(Readonly<{ log?: Readonly<{ logger?: string | undefined; level?: string | undefined; } & {}> | undefined; error?: Readonly<{ type?: string | undefined; id?: string | undefined; message?: string | undefined; code?: string | undefined; stack_trace?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; message?: string | undefined; tags?: string[] | undefined; rule?: Readonly<{ id?: string | undefined; name?: string | undefined; description?: string | undefined; category?: string | undefined; version?: string | undefined; uuid?: string | undefined; license?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; } & {}> | undefined; kibana?: Readonly<{ action?: Readonly<{ id?: string | undefined; name?: string | undefined; execution?: Readonly<{ uuid?: string | undefined; } & {}> | undefined; } & {}> | undefined; alerting?: Readonly<{ outcome?: string | undefined; summary?: Readonly<{ recovered?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; new?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; ongoing?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; } & {}> | undefined; status?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; execution?: Readonly<{ uuid?: string | undefined; metrics?: Readonly<{ number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ recovered?: string | number | undefined; active?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; es_search_duration_ms?: string | number | undefined; total_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; total_enrichment_duration_ms?: string | number | undefined; } & {}> | undefined; status?: string | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; flapping?: boolean | undefined; } & {}> | undefined; version?: string | 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; rel?: string | undefined; namespace?: string | undefined; type_id?: string | undefined; space_agnostic?: boolean | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; event?: Readonly<{ type?: string[] | undefined; reason?: string | undefined; action?: string | undefined; id?: string | undefined; start?: string | undefined; end?: string | undefined; category?: string[] | undefined; outcome?: string | undefined; code?: string | undefined; url?: string | undefined; created?: string | undefined; severity?: string | number | undefined; duration?: string | number | undefined; original?: string | undefined; dataset?: string | undefined; hash?: string | undefined; ingested?: string | undefined; kind?: 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; user?: Readonly<{ name?: string | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; } & {}> | undefined)[]" + "(Readonly<{ log?: Readonly<{ logger?: string | undefined; level?: string | undefined; } & {}> | undefined; error?: Readonly<{ type?: string | undefined; id?: string | undefined; message?: string | undefined; code?: string | undefined; stack_trace?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; message?: string | undefined; tags?: string[] | undefined; rule?: Readonly<{ id?: string | undefined; name?: string | undefined; description?: string | undefined; category?: string | undefined; version?: string | undefined; uuid?: string | undefined; license?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; } & {}> | undefined; kibana?: Readonly<{ action?: Readonly<{ id?: string | undefined; name?: string | undefined; execution?: Readonly<{ uuid?: string | undefined; } & {}> | undefined; } & {}> | undefined; alerting?: Readonly<{ outcome?: string | undefined; summary?: Readonly<{ recovered?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; new?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; ongoing?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; } & {}> | undefined; status?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; execution?: Readonly<{ uuid?: string | undefined; metrics?: Readonly<{ number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ recovered?: string | number | undefined; active?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; es_search_duration_ms?: string | number | undefined; total_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; total_enrichment_duration_ms?: string | number | undefined; } & {}> | undefined; status?: string | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; flapping?: boolean | undefined; } & {}> | undefined; version?: string | 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; rel?: string | undefined; namespace?: string | undefined; type_id?: string | undefined; space_agnostic?: boolean | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; event?: Readonly<{ type?: string[] | undefined; reason?: string | undefined; action?: string | undefined; id?: string | undefined; start?: string | undefined; end?: string | undefined; category?: string[] | undefined; outcome?: string | undefined; code?: string | undefined; url?: string | undefined; created?: string | undefined; severity?: string | number | undefined; duration?: string | number | undefined; original?: string | undefined; dataset?: string | undefined; hash?: string | undefined; ingested?: string | undefined; kind?: 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; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; user?: Readonly<{ name?: string | undefined; } & {}> | undefined; } & {}> | undefined)[]" ], "path": "x-pack/plugins/event_log/server/es/cluster_client_adapter.ts", "deprecated": false, @@ -1534,7 +1534,7 @@ "label": "IEvent", "description": [], "signature": [ - "DeepPartial | undefined; error?: Readonly<{ type?: string | undefined; id?: string | undefined; message?: string | undefined; code?: string | undefined; stack_trace?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; message?: string | undefined; tags?: string[] | undefined; rule?: Readonly<{ id?: string | undefined; name?: string | undefined; description?: string | undefined; category?: string | undefined; version?: string | undefined; uuid?: string | undefined; license?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; } & {}> | undefined; kibana?: Readonly<{ action?: Readonly<{ id?: string | undefined; name?: string | undefined; execution?: Readonly<{ uuid?: string | undefined; } & {}> | undefined; } & {}> | undefined; alerting?: Readonly<{ outcome?: string | undefined; summary?: Readonly<{ recovered?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; new?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; ongoing?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; } & {}> | undefined; status?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; execution?: Readonly<{ uuid?: string | undefined; metrics?: Readonly<{ number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ recovered?: string | number | undefined; active?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; es_search_duration_ms?: string | number | undefined; total_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; total_enrichment_duration_ms?: string | number | undefined; } & {}> | undefined; status?: string | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; flapping?: boolean | undefined; } & {}> | undefined; version?: string | 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; rel?: string | undefined; namespace?: string | undefined; type_id?: string | undefined; space_agnostic?: boolean | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; event?: Readonly<{ type?: string[] | undefined; reason?: string | undefined; action?: string | undefined; id?: string | undefined; start?: string | undefined; end?: string | undefined; category?: string[] | undefined; outcome?: string | undefined; code?: string | undefined; url?: string | undefined; created?: string | undefined; severity?: string | number | undefined; duration?: string | number | undefined; original?: string | undefined; dataset?: string | undefined; hash?: string | undefined; ingested?: string | undefined; kind?: 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; user?: Readonly<{ name?: string | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; } & {}>>> | undefined" + "DeepPartial | undefined; error?: Readonly<{ type?: string | undefined; id?: string | undefined; message?: string | undefined; code?: string | undefined; stack_trace?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; message?: string | undefined; tags?: string[] | undefined; rule?: Readonly<{ id?: string | undefined; name?: string | undefined; description?: string | undefined; category?: string | undefined; version?: string | undefined; uuid?: string | undefined; license?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; } & {}> | undefined; kibana?: Readonly<{ action?: Readonly<{ id?: string | undefined; name?: string | undefined; execution?: Readonly<{ uuid?: string | undefined; } & {}> | undefined; } & {}> | undefined; alerting?: Readonly<{ outcome?: string | undefined; summary?: Readonly<{ recovered?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; new?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; ongoing?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; } & {}> | undefined; status?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; execution?: Readonly<{ uuid?: string | undefined; metrics?: Readonly<{ number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ recovered?: string | number | undefined; active?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; es_search_duration_ms?: string | number | undefined; total_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; total_enrichment_duration_ms?: string | number | undefined; } & {}> | undefined; status?: string | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; flapping?: boolean | undefined; } & {}> | undefined; version?: string | 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; rel?: string | undefined; namespace?: string | undefined; type_id?: string | undefined; space_agnostic?: boolean | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; event?: Readonly<{ type?: string[] | undefined; reason?: string | undefined; action?: string | undefined; id?: string | undefined; start?: string | undefined; end?: string | undefined; category?: string[] | undefined; outcome?: string | undefined; code?: string | undefined; url?: string | undefined; created?: string | undefined; severity?: string | number | undefined; duration?: string | number | undefined; original?: string | undefined; dataset?: string | undefined; hash?: string | undefined; ingested?: string | undefined; kind?: 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; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; user?: Readonly<{ name?: string | undefined; } & {}> | undefined; } & {}>>> | undefined" ], "path": "x-pack/plugins/event_log/generated/schemas.ts", "deprecated": false, @@ -1549,7 +1549,7 @@ "label": "IValidatedEvent", "description": [], "signature": [ - "Readonly<{ log?: Readonly<{ logger?: string | undefined; level?: string | undefined; } & {}> | undefined; error?: Readonly<{ type?: string | undefined; id?: string | undefined; message?: string | undefined; code?: string | undefined; stack_trace?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; message?: string | undefined; tags?: string[] | undefined; rule?: Readonly<{ id?: string | undefined; name?: string | undefined; description?: string | undefined; category?: string | undefined; version?: string | undefined; uuid?: string | undefined; license?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; } & {}> | undefined; kibana?: Readonly<{ action?: Readonly<{ id?: string | undefined; name?: string | undefined; execution?: Readonly<{ uuid?: string | undefined; } & {}> | undefined; } & {}> | undefined; alerting?: Readonly<{ outcome?: string | undefined; summary?: Readonly<{ recovered?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; new?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; ongoing?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; } & {}> | undefined; status?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; execution?: Readonly<{ uuid?: string | undefined; metrics?: Readonly<{ number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ recovered?: string | number | undefined; active?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; es_search_duration_ms?: string | number | undefined; total_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; total_enrichment_duration_ms?: string | number | undefined; } & {}> | undefined; status?: string | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; flapping?: boolean | undefined; } & {}> | undefined; version?: string | 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; rel?: string | undefined; namespace?: string | undefined; type_id?: string | undefined; space_agnostic?: boolean | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; event?: Readonly<{ type?: string[] | undefined; reason?: string | undefined; action?: string | undefined; id?: string | undefined; start?: string | undefined; end?: string | undefined; category?: string[] | undefined; outcome?: string | undefined; code?: string | undefined; url?: string | undefined; created?: string | undefined; severity?: string | number | undefined; duration?: string | number | undefined; original?: string | undefined; dataset?: string | undefined; hash?: string | undefined; ingested?: string | undefined; kind?: 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; user?: Readonly<{ name?: string | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; } & {}> | undefined" + "Readonly<{ log?: Readonly<{ logger?: string | undefined; level?: string | undefined; } & {}> | undefined; error?: Readonly<{ type?: string | undefined; id?: string | undefined; message?: string | undefined; code?: string | undefined; stack_trace?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; message?: string | undefined; tags?: string[] | undefined; rule?: Readonly<{ id?: string | undefined; name?: string | undefined; description?: string | undefined; category?: string | undefined; version?: string | undefined; uuid?: string | undefined; license?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; } & {}> | undefined; kibana?: Readonly<{ action?: Readonly<{ id?: string | undefined; name?: string | undefined; execution?: Readonly<{ uuid?: string | undefined; } & {}> | undefined; } & {}> | undefined; alerting?: Readonly<{ outcome?: string | undefined; summary?: Readonly<{ recovered?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; new?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; ongoing?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; } & {}> | undefined; status?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; execution?: Readonly<{ uuid?: string | undefined; metrics?: Readonly<{ number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ recovered?: string | number | undefined; active?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; es_search_duration_ms?: string | number | undefined; total_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; total_enrichment_duration_ms?: string | number | undefined; } & {}> | undefined; status?: string | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; flapping?: boolean | undefined; } & {}> | undefined; version?: string | 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; rel?: string | undefined; namespace?: string | undefined; type_id?: string | undefined; space_agnostic?: boolean | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; event?: Readonly<{ type?: string[] | undefined; reason?: string | undefined; action?: string | undefined; id?: string | undefined; start?: string | undefined; end?: string | undefined; category?: string[] | undefined; outcome?: string | undefined; code?: string | undefined; url?: string | undefined; created?: string | undefined; severity?: string | number | undefined; duration?: string | number | undefined; original?: string | undefined; dataset?: string | undefined; hash?: string | undefined; ingested?: string | undefined; kind?: 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; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; user?: Readonly<{ name?: string | undefined; } & {}> | 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 77e029e48299c..e7718f18bc7f0 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: 2023-02-27 +date: 2023-03-02 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 d2cde25c51a23..b630e478cc044 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: 2023-02-27 +date: 2023-03-02 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 11d527ee9f30b..b4db7b0f3b828 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: 2023-02-27 +date: 2023-03-02 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 04dbccdf0997f..3dbc815880c4a 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: 2023-02-27 +date: 2023-03-02 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 286140f0d24b6..4c3a8b11829b2 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: 2023-02-27 +date: 2023-03-02 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 95c8fae0e37c9..a1a701f6ab08d 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: 2023-02-27 +date: 2023-03-02 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 692a945b7e667..01b55fbf63707 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: 2023-02-27 +date: 2023-03-02 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 a15b827070bde..22ec9b39787cc 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: 2023-02-27 +date: 2023-03-02 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 192785384e81e..d74ba9f0b1ee1 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: 2023-02-27 +date: 2023-03-02 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 73e7aa1f1b7e5..35e21e3a17c3f 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: 2023-02-27 +date: 2023-03-02 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 caf5c83c2ab06..fe87192fe6aaa 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: 2023-02-27 +date: 2023-03-02 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 7ea256e85e08d..994784ddab036 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: 2023-02-27 +date: 2023-03-02 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 247988910c3e6..1eb3789a95d8a 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: 2023-02-27 +date: 2023-03-02 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 b3424677d1306..3618e9de943fa 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: 2023-02-27 +date: 2023-03-02 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 fc26a51f9c653..67b1f2939627f 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: 2023-02-27 +date: 2023-03-02 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 5ef6733937074..9d22d7403800b 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: 2023-02-27 +date: 2023-03-02 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 8bea1b1e8715d..aefcd3d2b0047 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: 2023-02-27 +date: 2023-03-02 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 e8f573925ec16..3e398317686ca 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fileUpload'] --- import fileUploadObj from './file_upload.devdocs.json'; diff --git a/api_docs/files.devdocs.json b/api_docs/files.devdocs.json index 462d19625454f..3c2201044be35 100644 --- a/api_docs/files.devdocs.json +++ b/api_docs/files.devdocs.json @@ -16,7 +16,13 @@ "signature": [ "FilesClient", " extends ", - "BaseFilesClient", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.BaseFilesClient", + "text": "BaseFilesClient" + }, "" ], "path": "src/plugins/files/common/files_client.ts", @@ -203,17 +209,77 @@ "text": "FilesMetrics" }, "; publicDownload: any; find: { files: ", - "FileJSON", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, "[]; total: number; }; bulkDelete: { succeeded: string[]; failed?: [id: string, reason: string][] | undefined; }; create: { file: ", - "FileJSON", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, "; }; delete: { ok: true; }; getById: { file: ", - "FileJSON", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, "; }; list: { files: ", - "FileJSON", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, "[]; total: number; }; update: { file: ", - "FileJSON", - "; }; upload: { ok: true; size: number; }; download: any; getDownloadHref: string; share: any; unshare: { ok: true; }; getShare: { share: FileShareJSON; }; listShares: { shares: FileShareJSON[]; }; getFileKind: ", - "FileKind", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, + "; }; upload: { ok: true; size: number; }; download: any; getDownloadHref: string; share: ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileShareJSON", + "text": "FileShareJSON" + }, + " & { token: string; }; unshare: { ok: true; }; getShare: { share: ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileShareJSON", + "text": "FileShareJSON" + }, + "; }; listShares: { shares: ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileShareJSON", + "text": "FileShareJSON" + }, + "[]; }; getFileKind: ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileKindBase", + "text": "FileKindBase" + }, "; }" ], "path": "src/plugins/files/common/files_client.ts", @@ -240,332 +306,222 @@ "text": "FilesMetrics" }, ">; publicDownload: (arg: Omit<{ token: string; fileName?: string | undefined; }, \"kind\">) => any; find: (arg: Omit<{ kind?: string | string[] | undefined; status?: string | string[] | undefined; extension?: string | string[] | undefined; name?: string | string[] | undefined; meta?: M | undefined; } & ", - "Pagination", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Pagination", + "text": "Pagination" + }, " & ", - "Abortable", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + }, ", \"kind\">) => Promise<{ files: ", - "FileJSON", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, "[]; total: number; }>; bulkDelete: (arg: Omit<{ ids: string[]; } & ", - "Abortable", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + }, ", \"kind\">) => Promise<{ succeeded: string[]; failed?: [id: string, reason: string][] | undefined; }>; create: (arg: Omit<{ name: string; meta?: M | undefined; alt?: string | undefined; mimeType?: string | undefined; kind: string; } & ", - "Abortable", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + }, ", \"kind\">) => Promise<{ file: ", - "FileJSON", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, "; }>; delete: (arg: Omit<{ id: string; kind: string; } & ", - "Abortable", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + }, ", \"kind\">) => Promise<{ ok: true; }>; getById: (arg: Omit<{ id: string; kind: string; } & ", - "Abortable", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + }, ", \"kind\">) => Promise<{ file: ", - "FileJSON", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, "; }>; list: (arg?: Omit<{ kind: string; status?: string | string[] | undefined; extension?: string | string[] | undefined; name?: string | string[] | undefined; meta?: M | undefined; } & ", - "Pagination", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Pagination", + "text": "Pagination" + }, " & ", - "Abortable", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + }, ", \"kind\"> | undefined) => Promise<{ files: ", - "FileJSON", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, "[]; total: number; }>; update: (arg: Omit<{ id: string; kind: string; name?: string | undefined; meta?: M | undefined; alt?: string | undefined; } & ", - "Abortable", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + }, ", \"kind\">) => Promise<{ file: ", - "FileJSON", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, "; }>; upload: (arg: Omit<{ id: string; body: unknown; kind: string; abortSignal?: AbortSignal | undefined; contentType?: string | undefined; selfDestructOnAbort?: boolean | undefined; } & ", - "Abortable", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + }, ", \"kind\">) => Promise<{ ok: true; size: number; }>; download: (arg: Omit<{ fileName?: string | undefined; id: string; kind: string; } & ", - "Abortable", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + }, ", \"kind\">) => Promise; getDownloadHref: (arg: Omit, \"id\" | \"fileKind\">, \"kind\">) => string; share: (arg: Omit<{ name?: string | undefined; validUntil?: number | undefined; fileId: string; kind: string; } & ", - "Abortable", - ", \"kind\">) => Promise; unshare: (arg: Omit<{ id: string; kind: string; } & ", - "Abortable", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + }, + ", \"kind\">) => Promise<", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileShareJSONWithToken", + "text": "FileShareJSONWithToken" + }, + ">; unshare: (arg: Omit<{ id: string; kind: string; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + }, ", \"kind\">) => Promise<{ ok: true; }>; getShare: (arg: Omit<{ id: string; kind: string; } & ", - "Abortable", - ", \"kind\">) => Promise<{ share: FileShareJSON; }>; listShares: (arg: Omit<{ forFileId?: string | undefined; kind: string; } & ", - "Pagination", - " & ", - "Abortable", - ", \"kind\">) => Promise<{ shares: FileShareJSON[]; }>; getFileKind: (arg: Omit) => ", - "FileKind", - "; }" - ], - "path": "src/plugins/files/common/files_client.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - } - ], - "objects": [ - { - "parentPluginId": "files", - "id": "def-public.defaultImageFileKind", - "type": "Object", - "tags": [], - "label": "defaultImageFileKind", - "description": [ - "\nA file kind that is available to all plugins to use for uploading images\nintended to be reused across Kibana." - ], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ { - "parentPluginId": "files", - "id": "def-public.defaultImageFileKind.id", - "type": "string", - "tags": [], - "label": "id", - "description": [], - "signature": [ - "\"defaultImage\"" - ], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" }, + ", \"kind\">) => Promise<{ share: ", { - "parentPluginId": "files", - "id": "def-public.defaultImageFileKind.maxSizeBytes", - "type": "number", - "tags": [], - "label": "maxSizeBytes", - "description": [], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileShareJSON", + "text": "FileShareJSON" }, + "; }>; listShares: (arg: Omit<{ forFileId?: string | undefined; kind: string; } & ", { - "parentPluginId": "files", - "id": "def-public.defaultImageFileKind.blobStoreSettings", - "type": "Object", - "tags": [], - "label": "blobStoreSettings", - "description": [], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false, - "children": [] + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Pagination", + "text": "Pagination" }, + " & ", { - "parentPluginId": "files", - "id": "def-public.defaultImageFileKind.allowedMimeTypes", - "type": "Array", - "tags": [], - "label": "allowedMimeTypes", - "description": [ - "// tried using \"image/*\" but it did not work with the HTTP endpoint (got 415 Unsupported Media Type)" - ], - "signature": [ - "string[]" - ], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" }, + ", \"kind\">) => Promise<{ shares: ", { - "parentPluginId": "files", - "id": "def-public.defaultImageFileKind.http", - "type": "Object", - "tags": [], - "label": "http", - "description": [], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "files", - "id": "def-public.defaultImageFileKind.http.create", - "type": "Object", - "tags": [], - "label": "create", - "description": [], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "files", - "id": "def-public.defaultImageFileKind.http.create.tags", - "type": "Array", - "tags": [], - "label": "tags", - "description": [], - "signature": [ - "string[]" - ], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false - } - ] - }, - { - "parentPluginId": "files", - "id": "def-public.defaultImageFileKind.http.delete", - "type": "Object", - "tags": [], - "label": "delete", - "description": [], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "files", - "id": "def-public.defaultImageFileKind.http.delete.tags", - "type": "Array", - "tags": [], - "label": "tags", - "description": [], - "signature": [ - "string[]" - ], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false - } - ] - }, - { - "parentPluginId": "files", - "id": "def-public.defaultImageFileKind.http.download", - "type": "Object", - "tags": [], - "label": "download", - "description": [], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "files", - "id": "def-public.defaultImageFileKind.http.download.tags", - "type": "Array", - "tags": [], - "label": "tags", - "description": [], - "signature": [ - "string[]" - ], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false - } - ] - }, - { - "parentPluginId": "files", - "id": "def-public.defaultImageFileKind.http.getById", - "type": "Object", - "tags": [], - "label": "getById", - "description": [], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "files", - "id": "def-public.defaultImageFileKind.http.getById.tags", - "type": "Array", - "tags": [], - "label": "tags", - "description": [], - "signature": [ - "string[]" - ], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false - } - ] - }, - { - "parentPluginId": "files", - "id": "def-public.defaultImageFileKind.http.list", - "type": "Object", - "tags": [], - "label": "list", - "description": [], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "files", - "id": "def-public.defaultImageFileKind.http.list.tags", - "type": "Array", - "tags": [], - "label": "tags", - "description": [], - "signature": [ - "string[]" - ], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false - } - ] - }, - { - "parentPluginId": "files", - "id": "def-public.defaultImageFileKind.http.share", - "type": "Object", - "tags": [], - "label": "share", - "description": [], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "files", - "id": "def-public.defaultImageFileKind.http.share.tags", - "type": "Array", - "tags": [], - "label": "tags", - "description": [], - "signature": [ - "string[]" - ], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false - } - ] - }, - { - "parentPluginId": "files", - "id": "def-public.defaultImageFileKind.http.update", - "type": "Object", - "tags": [], - "label": "update", - "description": [], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "files", - "id": "def-public.defaultImageFileKind.http.update.tags", - "type": "Array", - "tags": [], - "label": "tags", - "description": [], - "signature": [ - "string[]" - ], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false - } - ] - } - ] - } + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileShareJSON", + "text": "FileShareJSON" + }, + "[]; }>; getFileKind: (arg: Omit) => ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileKindBase", + "text": "FileKindBase" + }, + "; }" ], + "path": "src/plugins/files/common/files_client.ts", + "deprecated": false, + "trackAdoption": false, "initialIsOpen": false } ], + "objects": [], "setup": { "parentPluginId": "files", "id": "def-public.FilesSetup", @@ -588,7 +544,7 @@ ], "label": "filesClientFactory", "description": [ - "\nA factory for creating an {@link FilesClient} instance. This requires a\nregistered {@link FileKind}.\n" + "\nA factory for creating an {@link FilesClient} instance. This requires a\nregistered {@link FileKindBrowser}.\n" ], "signature": [ "FilesClientFactory" @@ -618,7 +574,13 @@ ], "signature": [ "(fileKind: ", - "FileKind", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileKindBrowser", + "text": "FileKindBrowser" + }, ") => void" ], "path": "src/plugins/files/public/plugin.ts", @@ -635,7 +597,13 @@ "- the file kind to register" ], "signature": [ - "FileKind" + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileKindBrowser", + "text": "FileKindBrowser" + } ], "path": "src/plugins/files/public/plugin.ts", "deprecated": false, @@ -779,7 +747,7 @@ "tags": [], "label": "elasticsearchClient", "description": [ - "\nAn elasticsearch client that will be used to interact with the cluster" + "\nAn elasticsearch client that will be used to interact with the cluster." ], "signature": [ "{ create: { (this: That, params: ", @@ -1981,7 +1949,7 @@ "tags": [], "label": "maxSizeBytes", "description": [ - "\nThe maximum file size to be write" + "\nThe maximum file size to be written." ], "signature": [ "number | undefined" @@ -1997,7 +1965,7 @@ "tags": [], "label": "logger", "description": [ - "\nA logger for debuggin purposes" + "\nA logger for debugging purposes." ], "signature": [ { @@ -2615,9 +2583,9 @@ "ShareArgs", ") => Promise<", { - "pluginId": "files", + "pluginId": "@kbn/shared-ux-file-types", "scope": "common", - "docId": "kibFilesPluginApi", + "docId": "kibKbnSharedUxFileTypesPluginApi", "section": "def-common.FileShareJSONWithToken", "text": "FileShareJSONWithToken" }, @@ -2706,9 +2674,9 @@ }, ") => Promise<{ shares: ", { - "pluginId": "files", + "pluginId": "@kbn/shared-ux-file-types", "scope": "common", - "docId": "kibFilesPluginApi", + "docId": "kibKbnSharedUxFileTypesPluginApi", "section": "def-common.FileShareJSON", "text": "FileShareJSON" }, @@ -2793,9 +2761,21 @@ ], "signature": [ "Required> & ", - "BaseFileMetadata", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.BaseFileMetadata", + "text": "BaseFileMetadata" + }, " & { FileKind: string; Meta?: M | undefined; }" ], "path": "src/plugins/files/server/file_client/file_metadata_client/file_metadata_client.ts", @@ -3514,7 +3494,13 @@ "text": "FindFileArgs" }, ") => Promise<{ files: ", - "FileJSON", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, "[]; total: number; }>" ], "path": "src/plugins/files/server/file_service/file_service.ts", @@ -3559,9 +3545,9 @@ "signature": [ "(arg: IdArg) => Promise<", { - "pluginId": "files", + "pluginId": "@kbn/shared-ux-file-types", "scope": "common", - "docId": "kibFilesPluginApi", + "docId": "kibKbnSharedUxFileTypesPluginApi", "section": "def-common.FileShareJSON", "text": "FileShareJSON" }, @@ -3610,9 +3596,9 @@ }, ") => Promise<{ shares: ", { - "pluginId": "files", + "pluginId": "@kbn/shared-ux-file-types", "scope": "common", - "docId": "kibFilesPluginApi", + "docId": "kibKbnSharedUxFileTypesPluginApi", "section": "def-common.FileShareJSON", "text": "FileShareJSON" }, @@ -3667,9 +3653,9 @@ }, ") => Promise<", { - "pluginId": "files", + "pluginId": "@kbn/shared-ux-file-types", "scope": "common", - "docId": "kibFilesPluginApi", + "docId": "kibKbnSharedUxFileTypesPluginApi", "section": "def-common.FileShare", "text": "FileShare" }, @@ -3837,9 +3823,9 @@ "signature": [ "(arg: IdArg) => Promise<", { - "pluginId": "files", + "pluginId": "@kbn/shared-ux-file-types", "scope": "common", - "docId": "kibFilesPluginApi", + "docId": "kibKbnSharedUxFileTypesPluginApi", "section": "def-common.FileShareJSON", "text": "FileShareJSON" }, @@ -3889,9 +3875,9 @@ }, ") => Promise<{ shares: ", { - "pluginId": "files", + "pluginId": "@kbn/shared-ux-file-types", "scope": "common", - "docId": "kibFilesPluginApi", + "docId": "kibKbnSharedUxFileTypesPluginApi", "section": "def-common.FileShareJSON", "text": "FileShareJSON" }, @@ -3947,9 +3933,9 @@ }, ") => Promise<", { - "pluginId": "files", + "pluginId": "@kbn/shared-ux-file-types", "scope": "common", - "docId": "kibFilesPluginApi", + "docId": "kibKbnSharedUxFileTypesPluginApi", "section": "def-common.FileShare", "text": "FileShare" }, @@ -4404,7 +4390,13 @@ ], "signature": [ "{ name?: string | undefined; created?: string | undefined; Status?: \"AWAITING_UPLOAD\" | \"UPLOADING\" | \"READY\" | \"UPLOAD_ERROR\" | \"DELETED\" | undefined; Updated?: string | undefined; mime_type?: string | undefined; size?: number | undefined; hash?: { [hashName: string]: string | undefined; md5?: string | undefined; sha1?: string | undefined; sha256?: string | undefined; sha384?: string | undefined; sha512?: string | undefined; ssdeep?: string | undefined; tlsh?: string | undefined; } | undefined; user?: { name?: string | undefined; id?: string | undefined; } | undefined; extension?: string | undefined; Alt?: string | undefined; ChunkSize?: number | undefined; Compression?: ", - "FileCompression", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileCompression", + "text": "FileCompression" + }, " | undefined; FileKind?: string | undefined; Meta?: M | undefined; }" ], "path": "src/plugins/files/server/file_client/file_metadata_client/file_metadata_client.ts", @@ -4569,7 +4561,13 @@ ], "signature": [ "(fileKind: ", - "FileKind", + { + "pluginId": "files", + "scope": "common", + "docId": "kibFilesPluginApi", + "section": "def-common.FileKind", + "text": "FileKind" + }, ") => void" ], "path": "src/plugins/files/server/types.ts", @@ -4587,7 +4585,13 @@ "- the file kind to register" ], "signature": [ - "FileKind" + { + "pluginId": "files", + "scope": "common", + "docId": "kibFilesPluginApi", + "section": "def-common.FileKind", + "text": "FileKind" + } ], "path": "src/plugins/files/server/types.ts", "deprecated": false, @@ -4726,7 +4730,13 @@ "\nFile metadata in camelCase form." ], "signature": [ - "FileJSON", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, "" ], "path": "src/plugins/files/common/types.ts", @@ -4921,9 +4931,9 @@ }, " | undefined) => Promise<", { - "pluginId": "files", + "pluginId": "@kbn/shared-ux-file-types", "scope": "common", - "docId": "kibFilesPluginApi", + "docId": "kibKbnSharedUxFileTypesPluginApi", "section": "def-common.FileShareJSONWithToken", "text": "FileShareJSONWithToken" }, @@ -4972,9 +4982,9 @@ "signature": [ "() => Promise<", { - "pluginId": "files", + "pluginId": "@kbn/shared-ux-file-types", "scope": "common", - "docId": "kibFilesPluginApi", + "docId": "kibKbnSharedUxFileTypesPluginApi", "section": "def-common.FileShareJSON", "text": "FileShareJSON" }, @@ -5047,7 +5057,13 @@ ], "signature": [ "() => ", - "FileJSON", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, "" ], "path": "src/plugins/files/common/types.ts", @@ -5069,10 +5085,16 @@ "\nAttributes of a file that represent a serialised version of the file." ], "signature": [ - "FileJSON", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, "" ], - "path": "packages/shared-ux/file/types/index.d.ts", + "path": "packages/shared-ux/file/types/index.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -5085,7 +5107,7 @@ "description": [ "\nUnique file ID." ], - "path": "packages/shared-ux/file/types/index.d.ts", + "path": "packages/shared-ux/file/types/index.ts", "deprecated": false, "trackAdoption": false }, @@ -5098,7 +5120,7 @@ "description": [ "\nISO string of when this file was created" ], - "path": "packages/shared-ux/file/types/index.d.ts", + "path": "packages/shared-ux/file/types/index.ts", "deprecated": false, "trackAdoption": false }, @@ -5111,7 +5133,7 @@ "description": [ "\nISO string of when the file was updated" ], - "path": "packages/shared-ux/file/types/index.d.ts", + "path": "packages/shared-ux/file/types/index.ts", "deprecated": false, "trackAdoption": false }, @@ -5126,7 +5148,7 @@ "description": [ "\nFile name.\n" ], - "path": "packages/shared-ux/file/types/index.d.ts", + "path": "packages/shared-ux/file/types/index.ts", "deprecated": false, "trackAdoption": false }, @@ -5142,7 +5164,7 @@ "signature": [ "string | undefined" ], - "path": "packages/shared-ux/file/types/index.d.ts", + "path": "packages/shared-ux/file/types/index.ts", "deprecated": false, "trackAdoption": false }, @@ -5158,7 +5180,7 @@ "signature": [ "number | undefined" ], - "path": "packages/shared-ux/file/types/index.d.ts", + "path": "packages/shared-ux/file/types/index.ts", "deprecated": false, "trackAdoption": false }, @@ -5176,7 +5198,7 @@ "signature": [ "string | undefined" ], - "path": "packages/shared-ux/file/types/index.d.ts", + "path": "packages/shared-ux/file/types/index.ts", "deprecated": false, "trackAdoption": false }, @@ -5192,7 +5214,7 @@ "signature": [ "Meta | undefined" ], - "path": "packages/shared-ux/file/types/index.d.ts", + "path": "packages/shared-ux/file/types/index.ts", "deprecated": false, "trackAdoption": false }, @@ -5208,7 +5230,7 @@ "signature": [ "string | undefined" ], - "path": "packages/shared-ux/file/types/index.d.ts", + "path": "packages/shared-ux/file/types/index.ts", "deprecated": false, "trackAdoption": false }, @@ -5223,7 +5245,7 @@ "description": [ "\nA unique kind that governs various aspects of the file. A consumer of the\nfiles service must register a file kind and link their files to a specific\nkind.\n" ], - "path": "packages/shared-ux/file/types/index.d.ts", + "path": "packages/shared-ux/file/types/index.ts", "deprecated": false, "trackAdoption": false }, @@ -5239,7 +5261,7 @@ "signature": [ "\"AWAITING_UPLOAD\" | \"UPLOADING\" | \"READY\" | \"UPLOAD_ERROR\" | \"DELETED\"" ], - "path": "packages/shared-ux/file/types/index.d.ts", + "path": "packages/shared-ux/file/types/index.ts", "deprecated": false, "trackAdoption": false }, @@ -5255,7 +5277,7 @@ "signature": [ "{ name?: string | undefined; id?: string | undefined; } | undefined" ], - "path": "packages/shared-ux/file/types/index.d.ts", + "path": "packages/shared-ux/file/types/index.ts", "deprecated": false, "trackAdoption": false } @@ -5269,72 +5291,73 @@ "tags": [], "label": "FileKind", "description": [], - "path": "packages/shared-ux/file/types/index.d.ts", + "signature": [ + { + "pluginId": "files", + "scope": "common", + "docId": "kibFilesPluginApi", + "section": "def-common.FileKind", + "text": "FileKind" + }, + " extends ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileKindBase", + "text": "FileKindBase" + } + ], + "path": "src/plugins/files/common/types.ts", "deprecated": false, "trackAdoption": false, "children": [ - { - "parentPluginId": "files", - "id": "def-common.FileKind.id", - "type": "string", - "tags": [], - "label": "id", - "description": [ - "\nUnique file kind ID" - ], - "path": "packages/shared-ux/file/types/index.d.ts", - "deprecated": false, - "trackAdoption": false - }, { "parentPluginId": "files", "id": "def-common.FileKind.maxSizeBytes", - "type": "number", + "type": "CompoundType", "tags": [ "default" ], "label": "maxSizeBytes", "description": [ - "\nMaximum size, in bytes, a file of this kind can be.\n" + "\nMax file contents size, in bytes. Can be customized per file using the\n{@link FileJSON} object. This is enforced on the server-side as well as\nin the upload React component.\n" ], "signature": [ - "number | undefined" - ], - "path": "packages/shared-ux/file/types/index.d.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "files", - "id": "def-common.FileKind.allowedMimeTypes", - "type": "Array", - "tags": [ - "default" - ], - "label": "allowedMimeTypes", - "description": [ - "\nThe MIME type of the file content.\n" - ], - "signature": [ - "string[] | undefined" + "number | ((file: ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, + ") => number) | undefined" ], - "path": "packages/shared-ux/file/types/index.d.ts", + "path": "src/plugins/files/common/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "files", "id": "def-common.FileKind.blobStoreSettings", - "type": "Any", + "type": "Object", "tags": [], "label": "blobStoreSettings", "description": [ "\nBlob store specific settings that enable configuration of storage\ndetails." ], "signature": [ - "any" + { + "pluginId": "files", + "scope": "common", + "docId": "kibFilesPluginApi", + "section": "def-common.BlobStorageSettings", + "text": "BlobStorageSettings" + }, + " | undefined" ], - "path": "packages/shared-ux/file/types/index.d.ts", + "path": "src/plugins/files/common/types.ts", "deprecated": false, "trackAdoption": false }, @@ -5352,7 +5375,56 @@ "signature": [ "{ create?: HttpEndpointDefinition | undefined; update?: HttpEndpointDefinition | undefined; delete?: HttpEndpointDefinition | undefined; getById?: HttpEndpointDefinition | undefined; list?: HttpEndpointDefinition | undefined; download?: HttpEndpointDefinition | undefined; share?: HttpEndpointDefinition | undefined; }" ], - "path": "packages/shared-ux/file/types/index.d.ts", + "path": "src/plugins/files/common/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "files", + "id": "def-common.FileKindBrowser", + "type": "Interface", + "tags": [], + "label": "FileKindBrowser", + "description": [], + "signature": [ + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileKindBrowser", + "text": "FileKindBrowser" + }, + " extends ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileKindBase", + "text": "FileKindBase" + } + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "files", + "id": "def-common.FileKindBrowser.maxSizeBytes", + "type": "number", + "tags": [ + "default" + ], + "label": "maxSizeBytes", + "description": [ + "\nMax file contents size, in bytes, enforced for this file kind in the upload\ncomponent.\n" + ], + "signature": [ + "number | undefined" + ], + "path": "packages/shared-ux/file/types/index.ts", "deprecated": false, "trackAdoption": false } @@ -5368,7 +5440,7 @@ "description": [ "\nAttributes of a file that represent a serialised version of the file." ], - "path": "src/plugins/files/common/types.ts", + "path": "packages/shared-ux/file/types/sharing.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -5381,7 +5453,7 @@ "description": [ "\nUnique ID share instance" ], - "path": "src/plugins/files/common/types.ts", + "path": "packages/shared-ux/file/types/sharing.ts", "deprecated": false, "trackAdoption": false }, @@ -5394,7 +5466,7 @@ "description": [ "\nISO timestamp the share was created" ], - "path": "src/plugins/files/common/types.ts", + "path": "packages/shared-ux/file/types/sharing.ts", "deprecated": false, "trackAdoption": false }, @@ -5407,7 +5479,7 @@ "description": [ "\nUnix timestamp (in milliseconds) of when this share expires" ], - "path": "src/plugins/files/common/types.ts", + "path": "packages/shared-ux/file/types/sharing.ts", "deprecated": false, "trackAdoption": false }, @@ -5423,7 +5495,7 @@ "signature": [ "string | undefined" ], - "path": "src/plugins/files/common/types.ts", + "path": "packages/shared-ux/file/types/sharing.ts", "deprecated": false, "trackAdoption": false }, @@ -5436,7 +5508,7 @@ "description": [ "\nThe ID of the file this share is linked to" ], - "path": "src/plugins/files/common/types.ts", + "path": "packages/shared-ux/file/types/sharing.ts", "deprecated": false, "trackAdoption": false } @@ -5648,12 +5720,24 @@ ], "signature": [ "{ name?: string | undefined; mime_type?: string | undefined; created?: string | undefined; size?: number | undefined; hash?: { [hashName: string]: string | undefined; md5?: string | undefined; sha1?: string | undefined; sha256?: string | undefined; sha384?: string | undefined; sha512?: string | undefined; ssdeep?: string | undefined; tlsh?: string | undefined; } | undefined; user?: { name?: string | undefined; id?: string | undefined; } | undefined; extension?: string | undefined; Alt?: string | undefined; Updated?: string | undefined; Status?: ", - "FileStatus", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileStatus", + "text": "FileStatus" + }, " | undefined; ChunkSize?: number | undefined; Compression?: ", - "FileCompression", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileCompression", + "text": "FileCompression" + }, " | undefined; }" ], - "path": "packages/shared-ux/file/types/index.d.ts", + "path": "packages/shared-ux/file/types/index.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -5704,7 +5788,7 @@ "signature": [ "\"none\" | \"br\" | \"gzip\" | \"deflate\"" ], - "path": "packages/shared-ux/file/types/index.d.ts", + "path": "packages/shared-ux/file/types/index.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -5720,12 +5804,24 @@ ], "signature": [ "Required> & ", - "BaseFileMetadata", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.BaseFileMetadata", + "text": "BaseFileMetadata" + }, " & { FileKind: string; Meta?: Meta | undefined; }" ], - "path": "packages/shared-ux/file/types/index.d.ts", + "path": "packages/shared-ux/file/types/index.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -5748,7 +5844,13 @@ "text": "SavedObject" }, "<", - "FileMetadata", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileMetadata", + "text": "FileMetadata" + }, ">" ], "path": "src/plugins/files/common/types.ts", @@ -5768,7 +5870,7 @@ "signature": [ "{ created: string; token: string; name?: string | undefined; valid_until: number; }" ], - "path": "src/plugins/files/common/types.ts", + "path": "packages/shared-ux/file/types/sharing.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -5786,15 +5888,15 @@ ], "signature": [ { - "pluginId": "files", + "pluginId": "@kbn/shared-ux-file-types", "scope": "common", - "docId": "kibFilesPluginApi", + "docId": "kibKbnSharedUxFileTypesPluginApi", "section": "def-common.FileShareJSON", "text": "FileShareJSON" }, " & { token: string; }" ], - "path": "src/plugins/files/common/types.ts", + "path": "packages/shared-ux/file/types/sharing.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -5809,7 +5911,7 @@ "signature": [ "\"AWAITING_UPLOAD\" | \"UPLOADING\" | \"READY\" | \"UPLOAD_ERROR\" | \"DELETED\"" ], - "path": "packages/shared-ux/file/types/index.d.ts", + "path": "packages/shared-ux/file/types/index.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -5883,278 +5985,6 @@ "initialIsOpen": false } ], - "objects": [ - { - "parentPluginId": "files", - "id": "def-common.defaultImageFileKind", - "type": "Object", - "tags": [], - "label": "defaultImageFileKind", - "description": [ - "\nA file kind that is available to all plugins to use for uploading images\nintended to be reused across Kibana." - ], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "files", - "id": "def-common.defaultImageFileKind.id", - "type": "string", - "tags": [], - "label": "id", - "description": [], - "signature": [ - "\"defaultImage\"" - ], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "files", - "id": "def-common.defaultImageFileKind.maxSizeBytes", - "type": "number", - "tags": [], - "label": "maxSizeBytes", - "description": [], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "files", - "id": "def-common.defaultImageFileKind.blobStoreSettings", - "type": "Object", - "tags": [], - "label": "blobStoreSettings", - "description": [], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false, - "children": [] - }, - { - "parentPluginId": "files", - "id": "def-common.defaultImageFileKind.allowedMimeTypes", - "type": "Array", - "tags": [], - "label": "allowedMimeTypes", - "description": [ - "// tried using \"image/*\" but it did not work with the HTTP endpoint (got 415 Unsupported Media Type)" - ], - "signature": [ - "string[]" - ], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "files", - "id": "def-common.defaultImageFileKind.http", - "type": "Object", - "tags": [], - "label": "http", - "description": [], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "files", - "id": "def-common.defaultImageFileKind.http.create", - "type": "Object", - "tags": [], - "label": "create", - "description": [], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "files", - "id": "def-common.defaultImageFileKind.http.create.tags", - "type": "Array", - "tags": [], - "label": "tags", - "description": [], - "signature": [ - "string[]" - ], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false - } - ] - }, - { - "parentPluginId": "files", - "id": "def-common.defaultImageFileKind.http.delete", - "type": "Object", - "tags": [], - "label": "delete", - "description": [], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "files", - "id": "def-common.defaultImageFileKind.http.delete.tags", - "type": "Array", - "tags": [], - "label": "tags", - "description": [], - "signature": [ - "string[]" - ], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false - } - ] - }, - { - "parentPluginId": "files", - "id": "def-common.defaultImageFileKind.http.download", - "type": "Object", - "tags": [], - "label": "download", - "description": [], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "files", - "id": "def-common.defaultImageFileKind.http.download.tags", - "type": "Array", - "tags": [], - "label": "tags", - "description": [], - "signature": [ - "string[]" - ], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false - } - ] - }, - { - "parentPluginId": "files", - "id": "def-common.defaultImageFileKind.http.getById", - "type": "Object", - "tags": [], - "label": "getById", - "description": [], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "files", - "id": "def-common.defaultImageFileKind.http.getById.tags", - "type": "Array", - "tags": [], - "label": "tags", - "description": [], - "signature": [ - "string[]" - ], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false - } - ] - }, - { - "parentPluginId": "files", - "id": "def-common.defaultImageFileKind.http.list", - "type": "Object", - "tags": [], - "label": "list", - "description": [], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "files", - "id": "def-common.defaultImageFileKind.http.list.tags", - "type": "Array", - "tags": [], - "label": "tags", - "description": [], - "signature": [ - "string[]" - ], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false - } - ] - }, - { - "parentPluginId": "files", - "id": "def-common.defaultImageFileKind.http.share", - "type": "Object", - "tags": [], - "label": "share", - "description": [], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "files", - "id": "def-common.defaultImageFileKind.http.share.tags", - "type": "Array", - "tags": [], - "label": "tags", - "description": [], - "signature": [ - "string[]" - ], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false - } - ] - }, - { - "parentPluginId": "files", - "id": "def-common.defaultImageFileKind.http.update", - "type": "Object", - "tags": [], - "label": "update", - "description": [], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "files", - "id": "def-common.defaultImageFileKind.http.update.tags", - "type": "Array", - "tags": [], - "label": "tags", - "description": [], - "signature": [ - "string[]" - ], - "path": "src/plugins/files/common/default_image_file_kind.ts", - "deprecated": false, - "trackAdoption": false - } - ] - } - ] - } - ], - "initialIsOpen": false - } - ] + "objects": [] } } \ No newline at end of file diff --git a/api_docs/files.mdx b/api_docs/files.mdx index 5b94fadff8259..9cf192f9c4f3e 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'files'] --- import filesObj from './files.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sh | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 254 | 1 | 45 | 5 | +| 214 | 0 | 10 | 5 | ## Client @@ -31,9 +31,6 @@ Contact [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sh ### Start -### Objects - - ### Interfaces @@ -59,9 +56,6 @@ Contact [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sh ## Common -### Objects - - ### Interfaces diff --git a/api_docs/files_management.mdx b/api_docs/files_management.mdx index b916774dbdb67..b1896f8199ca0 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: 2023-02-27 +date: 2023-03-02 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 f9a49b8123b49..df2aca4d29022 100644 --- a/api_docs/fleet.devdocs.json +++ b/api_docs/fleet.devdocs.json @@ -5625,7 +5625,8 @@ "docId": "kibSpacesPluginApi", "section": "def-server.SpacesPluginStart", "text": "SpacesPluginStart" - } + }, + " | undefined" ], "path": "x-pack/plugins/fleet/server/plugin.ts", "deprecated": false, @@ -6029,6 +6030,86 @@ ], "returnComment": [] }, + { + "parentPluginId": "fleet", + "id": "def-server.PackageClient.getPackages", + "type": "Function", + "tags": [], + "label": "getPackages", + "description": [], + "signature": [ + "(params?: { excludeInstallStatus?: false | undefined; category?: string | undefined; prerelease?: false | undefined; } | undefined) => Promise<", + { + "pluginId": "fleet", + "scope": "common", + "docId": "kibFleetPluginApi", + "section": "def-common.PackageList", + "text": "PackageList" + }, + ">" + ], + "path": "x-pack/plugins/fleet/server/services/epm/package_service.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "fleet", + "id": "def-server.PackageClient.getPackages.$1", + "type": "Object", + "tags": [], + "label": "params", + "description": [], + "path": "x-pack/plugins/fleet/server/services/epm/package_service.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "fleet", + "id": "def-server.PackageClient.getPackages.$1.excludeInstallStatus", + "type": "boolean", + "tags": [], + "label": "excludeInstallStatus", + "description": [], + "signature": [ + "false | undefined" + ], + "path": "x-pack/plugins/fleet/server/services/epm/package_service.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "fleet", + "id": "def-server.PackageClient.getPackages.$1.category", + "type": "string", + "tags": [], + "label": "category", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "x-pack/plugins/fleet/server/services/epm/package_service.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "fleet", + "id": "def-server.PackageClient.getPackages.$1.prerelease", + "type": "boolean", + "tags": [], + "label": "prerelease", + "description": [], + "signature": [ + "false | undefined" + ], + "path": "x-pack/plugins/fleet/server/services/epm/package_service.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ], + "returnComment": [] + }, { "parentPluginId": "fleet", "id": "def-server.PackageClient.reinstallEsAssets", @@ -25180,6 +25261,17 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "fleet", + "id": "def-common.EPM_API_ROUTES.VERIFICATION_KEY_ID", + "type": "string", + "tags": [], + "label": "VERIFICATION_KEY_ID", + "description": [], + "path": "x-pack/plugins/fleet/common/constants/routes.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "fleet", "id": "def-common.EPM_API_ROUTES.STATS_PATTERN", @@ -25238,6 +25330,22 @@ "deprecated": false, "trackAdoption": false, "children": [ + { + "parentPluginId": "fleet", + "id": "def-common.epmRouteService.getVerificationKeyIdPath", + "type": "Function", + "tags": [], + "label": "getVerificationKeyIdPath", + "description": [], + "signature": [ + "() => string" + ], + "path": "x-pack/plugins/fleet/common/services/routes.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, { "parentPluginId": "fleet", "id": "def-common.epmRouteService.getCategoriesPath", diff --git a/api_docs/fleet.mdx b/api_docs/fleet.mdx index 75c2497452fab..f72c1c07dae47 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fleet'] --- import fleetObj from './fleet.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/fleet](https://github.com/orgs/elastic/teams/fleet) for questi | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 1087 | 3 | 982 | 27 | +| 1094 | 3 | 989 | 27 | ## Client diff --git a/api_docs/global_search.mdx b/api_docs/global_search.mdx index 63bc515caf0ed..1361b0c3bbb54 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: 2023-02-27 +date: 2023-03-02 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 062c0047405d4..831408669b1c3 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: 2023-02-27 +date: 2023-03-02 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 768a848a4aab2..a2001e302b069 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'home'] --- import homeObj from './home.devdocs.json'; diff --git a/api_docs/image_embeddable.mdx b/api_docs/image_embeddable.mdx index 4780f3b4c14be..a1c917a1d7197 100644 --- a/api_docs/image_embeddable.mdx +++ b/api_docs/image_embeddable.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/imageEmbeddable title: "imageEmbeddable" image: https://source.unsplash.com/400x175/?github description: API docs for the imageEmbeddable plugin -date: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'imageEmbeddable'] --- import imageEmbeddableObj from './image_embeddable.devdocs.json'; diff --git a/api_docs/index_lifecycle_management.mdx b/api_docs/index_lifecycle_management.mdx index c97553b7c1299..c56a887ea0c31 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: 2023-02-27 +date: 2023-03-02 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 3a58461fc3584..1a2441d2f57f3 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: 2023-02-27 +date: 2023-03-02 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 18968689e11dd..224351c42f871 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: 2023-02-27 +date: 2023-03-02 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 d439b28ace1d2..9aecbf08da449 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: 2023-02-27 +date: 2023-03-02 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 626a3739cfdc0..e3d72fbaa1968 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: 2023-02-27 +date: 2023-03-02 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 0fa12f26cf43e..d23833d267aac 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: 2023-02-27 +date: 2023-03-02 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 4824f734f9101..c2ecac6ab7a68 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: 2023-02-27 +date: 2023-03-02 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 dc8cac3bee834..bacae41837451 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: 2023-02-27 +date: 2023-03-02 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 d42a9f6a264a9..263bad7a01dc3 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts'] --- import kbnAlertsObj from './kbn_alerts.devdocs.json'; diff --git a/api_docs/kbn_alerts_as_data_utils.devdocs.json b/api_docs/kbn_alerts_as_data_utils.devdocs.json new file mode 100644 index 0000000000000..b5390c5044148 --- /dev/null +++ b/api_docs/kbn_alerts_as_data_utils.devdocs.json @@ -0,0 +1,215 @@ +{ + "id": "@kbn/alerts-as-data-utils", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [ + { + "parentPluginId": "@kbn/alerts-as-data-utils", + "id": "def-common.FieldMap", + "type": "Interface", + "tags": [], + "label": "FieldMap", + "description": [], + "path": "packages/kbn-alerts-as-data-utils/src/field_maps/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/alerts-as-data-utils", + "id": "def-common.FieldMap.Unnamed", + "type": "IndexSignature", + "tags": [], + "label": "[key: string]: { type: string; required: boolean; array?: boolean | undefined; doc_values?: boolean | undefined; enabled?: boolean | undefined; format?: string | undefined; ignore_above?: number | undefined; ... 4 more ...; dynamic?: boolean | ... 1 more ... | undefined; }", + "description": [], + "signature": [ + "[key: string]: { type: string; required: boolean; array?: boolean | undefined; doc_values?: boolean | undefined; enabled?: boolean | undefined; format?: string | undefined; ignore_above?: number | undefined; multi_fields?: ", + { + "pluginId": "@kbn/alerts-as-data-utils", + "scope": "common", + "docId": "kibKbnAlertsAsDataUtilsPluginApi", + "section": "def-common.MultiField", + "text": "MultiField" + }, + "[] | undefined; index?: boolean | undefined; path?: string | undefined; scaling_factor?: number | undefined; dynamic?: boolean | \"strict\" | undefined; }" + ], + "path": "packages/kbn-alerts-as-data-utils/src/field_maps/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/alerts-as-data-utils", + "id": "def-common.MultiField", + "type": "Interface", + "tags": [], + "label": "MultiField", + "description": [], + "path": "packages/kbn-alerts-as-data-utils/src/field_maps/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/alerts-as-data-utils", + "id": "def-common.MultiField.flat_name", + "type": "string", + "tags": [], + "label": "flat_name", + "description": [], + "path": "packages/kbn-alerts-as-data-utils/src/field_maps/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-as-data-utils", + "id": "def-common.MultiField.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "path": "packages/kbn-alerts-as-data-utils/src/field_maps/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-as-data-utils", + "id": "def-common.MultiField.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "path": "packages/kbn-alerts-as-data-utils/src/field_maps/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ], + "enums": [], + "misc": [ + { + "parentPluginId": "@kbn/alerts-as-data-utils", + "id": "def-common.AlertFieldMap", + "type": "Type", + "tags": [], + "label": "AlertFieldMap", + "description": [], + "signature": [ + "{ readonly \"kibana.alert.action_group\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.case_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.duration.us\": { readonly type: \"long\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.end\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.flapping\": { readonly type: \"boolean\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.flapping_history\": { readonly type: \"boolean\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.instance.id\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.last_detected\": { readonly type: \"date\"; readonly required: false; readonly array: false; }; readonly \"kibana.alert.reason\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.category\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.consumer\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.execution.uuid\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.name\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.parameters\": { readonly array: false; readonly type: \"flattened\"; readonly ignore_above: 4096; readonly required: false; }; readonly \"kibana.alert.rule.producer\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.rule.rule_type_id\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.uuid\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.start\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.status\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.time_range\": { readonly type: \"date_range\"; readonly format: \"epoch_millis||strict_date_optional_time\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.uuid\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.workflow_status\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.space_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: true; }; readonly \"@timestamp\": { readonly type: \"date\"; readonly required: true; readonly array: false; }; readonly \"kibana.version\": { readonly type: \"version\"; readonly array: false; readonly required: false; }; }" + ], + "path": "packages/kbn-alerts-as-data-utils/src/field_maps/alert_field_map.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/alerts-as-data-utils", + "id": "def-common.EcsFieldMap", + "type": "Type", + "tags": [], + "label": "EcsFieldMap", + "description": [], + "signature": [ + { + "pluginId": "@kbn/alerts-as-data-utils", + "scope": "common", + "docId": "kibKbnAlertsAsDataUtilsPluginApi", + "section": "def-common.FieldMap", + "text": "FieldMap" + } + ], + "path": "packages/kbn-alerts-as-data-utils/src/field_maps/ecs_field_map.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/alerts-as-data-utils", + "id": "def-common.LegacyAlertFieldMap", + "type": "Type", + "tags": [], + "label": "LegacyAlertFieldMap", + "description": [], + "signature": [ + "{ readonly \"kibana.alert.risk_score\": { readonly type: \"float\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.author\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.created_at\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.created_by\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.description\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.enabled\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.from\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.interval\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.license\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.note\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.references\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.rule.rule_id\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.rule_name_override\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.to\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.type\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.updated_at\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.updated_by\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.version\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.severity\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.suppression.docs_count\": { readonly type: \"long\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.suppression.end\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.suppression.terms.field\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.suppression.start\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.suppression.terms.value\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.system_status\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.workflow_reason\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.workflow_user\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"ecs.version\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"event.action\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"event.kind\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly tags: { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; }" + ], + "path": "packages/kbn-alerts-as-data-utils/src/field_maps/legacy_alert_field_map.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "objects": [ + { + "parentPluginId": "@kbn/alerts-as-data-utils", + "id": "def-common.alertFieldMap", + "type": "Object", + "tags": [], + "label": "alertFieldMap", + "description": [], + "signature": [ + "{ readonly \"kibana.alert.action_group\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.case_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.duration.us\": { readonly type: \"long\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.end\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.flapping\": { readonly type: \"boolean\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.flapping_history\": { readonly type: \"boolean\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.instance.id\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.last_detected\": { readonly type: \"date\"; readonly required: false; readonly array: false; }; readonly \"kibana.alert.reason\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.category\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.consumer\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.execution.uuid\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.name\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.parameters\": { readonly array: false; readonly type: \"flattened\"; readonly ignore_above: 4096; readonly required: false; }; readonly \"kibana.alert.rule.producer\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.tags\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.rule.rule_type_id\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.rule.uuid\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.start\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.status\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.time_range\": { readonly type: \"date_range\"; readonly format: \"epoch_millis||strict_date_optional_time\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.uuid\": { readonly type: \"keyword\"; readonly array: false; readonly required: true; }; readonly \"kibana.alert.workflow_status\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.space_ids\": { readonly type: \"keyword\"; readonly array: true; readonly required: true; }; readonly \"@timestamp\": { readonly type: \"date\"; readonly required: true; readonly array: false; }; readonly \"kibana.version\": { readonly type: \"version\"; readonly array: false; readonly required: false; }; }" + ], + "path": "packages/kbn-alerts-as-data-utils/src/field_maps/alert_field_map.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/alerts-as-data-utils", + "id": "def-common.ecsFieldMap", + "type": "Object", + "tags": [], + "label": "ecsFieldMap", + "description": [], + "signature": [ + { + "pluginId": "@kbn/alerts-as-data-utils", + "scope": "common", + "docId": "kibKbnAlertsAsDataUtilsPluginApi", + "section": "def-common.FieldMap", + "text": "FieldMap" + } + ], + "path": "packages/kbn-alerts-as-data-utils/src/field_maps/ecs_field_map.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/alerts-as-data-utils", + "id": "def-common.legacyAlertFieldMap", + "type": "Object", + "tags": [], + "label": "legacyAlertFieldMap", + "description": [], + "signature": [ + "{ readonly \"kibana.alert.risk_score\": { readonly type: \"float\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.author\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.created_at\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.created_by\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.description\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.enabled\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.from\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.interval\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.license\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.note\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.references\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.rule.rule_id\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.rule_name_override\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.to\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.type\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.updated_at\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.updated_by\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.rule.version\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.severity\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.suppression.docs_count\": { readonly type: \"long\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.suppression.end\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.suppression.terms.field\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.suppression.start\": { readonly type: \"date\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.suppression.terms.value\": { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly \"kibana.alert.system_status\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.workflow_reason\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"kibana.alert.workflow_user\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"ecs.version\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"event.action\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly \"event.kind\": { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly tags: { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; }" + ], + "path": "packages/kbn-alerts-as-data-utils/src/field_maps/legacy_alert_field_map.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ] + } +} \ No newline at end of file diff --git a/api_docs/kbn_alerts_as_data_utils.mdx b/api_docs/kbn_alerts_as_data_utils.mdx new file mode 100644 index 0000000000000..9c9ffcd49f74a --- /dev/null +++ b/api_docs/kbn_alerts_as_data_utils.mdx @@ -0,0 +1,36 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibKbnAlertsAsDataUtilsPluginApi +slug: /kibana-dev-docs/api/kbn-alerts-as-data-utils +title: "@kbn/alerts-as-data-utils" +image: https://source.unsplash.com/400x175/?github +description: API docs for the @kbn/alerts-as-data-utils plugin +date: 2023-03-02 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts-as-data-utils'] +--- +import kbnAlertsAsDataUtilsObj from './kbn_alerts_as_data_utils.devdocs.json'; + + + +Contact [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 12 | 0 | 12 | 0 | + +## Common + +### Objects + + +### Interfaces + + +### Consts, variables and types + + diff --git a/api_docs/kbn_alerts_ui_shared.mdx b/api_docs/kbn_alerts_ui_shared.mdx index 4feef26b61869..be08717fd1fbc 100644 --- a/api_docs/kbn_alerts_ui_shared.mdx +++ b/api_docs/kbn_alerts_ui_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerts-ui-shared title: "@kbn/alerts-ui-shared" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerts-ui-shared plugin -date: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts-ui-shared'] --- import kbnAlertsUiSharedObj from './kbn_alerts_ui_shared.devdocs.json'; diff --git a/api_docs/kbn_analytics.mdx b/api_docs/kbn_analytics.mdx index b43b2e542aa7a..3efbb27ae6b4c 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: 2023-02-27 +date: 2023-03-02 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 83c0c9326f5e9..0f590ec71b575 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: 2023-02-27 +date: 2023-03-02 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 8444fb1c70527..7fa02c977ef0f 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: 2023-02-27 +date: 2023-03-02 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 5598eab2e267c..5d1ce84c337fc 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: 2023-02-27 +date: 2023-03-02 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 89b4d27e88db7..b5352daa851ae 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: 2023-02-27 +date: 2023-03-02 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 7ed85b3e2a725..7f059748811ad 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: 2023-02-27 +date: 2023-03-02 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 3d976b803011c..643778c571a3f 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: 2023-02-27 +date: 2023-03-02 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 293ec377ece5b..c273ed05035f1 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-config-loader'] --- import kbnApmConfigLoaderObj from './kbn_apm_config_loader.devdocs.json'; diff --git a/api_docs/kbn_apm_synthtrace.mdx b/api_docs/kbn_apm_synthtrace.mdx index 220c4d0eff04c..f48057fdc63e4 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-synthtrace'] --- import kbnApmSynthtraceObj from './kbn_apm_synthtrace.devdocs.json'; diff --git a/api_docs/kbn_apm_synthtrace_client.devdocs.json b/api_docs/kbn_apm_synthtrace_client.devdocs.json index ea59a115a467f..ff0b1e2cf64e5 100644 --- a/api_docs/kbn_apm_synthtrace_client.devdocs.json +++ b/api_docs/kbn_apm_synthtrace_client.devdocs.json @@ -2299,7 +2299,7 @@ "section": "def-common.ApmFields", "text": "ApmFields" }, - ", \"@timestamp\" | \"metricset.name\" | \"ecs.version\" | \"event.ingested\" | \"observer.type\" | \"observer.version\" | \"observer.version_major\" | \"processor.event\" | \"processor.name\"> & Partial<{ 'labels.etag': string; agent_config_applied: number; 'event.agent_id_status': string; }>" + ", \"@timestamp\" | \"metricset.name\" | \"ecs.version\" | \"event.ingested\" | \"observer.type\" | \"observer.version_major\" | \"observer.version\" | \"processor.event\" | \"processor.name\"> & Partial<{ 'labels.etag': string; agent_config_applied: number; 'event.agent_id_status': string; }>" ], "path": "packages/kbn-apm-synthtrace-client/src/lib/agent_config/agent_config_fields.ts", "deprecated": false, diff --git a/api_docs/kbn_apm_synthtrace_client.mdx b/api_docs/kbn_apm_synthtrace_client.mdx index 375c04442cbc2..998222ae3142e 100644 --- a/api_docs/kbn_apm_synthtrace_client.mdx +++ b/api_docs/kbn_apm_synthtrace_client.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-synthtrace-client title: "@kbn/apm-synthtrace-client" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-synthtrace-client plugin -date: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-synthtrace-client'] --- import kbnApmSynthtraceClientObj from './kbn_apm_synthtrace_client.devdocs.json'; diff --git a/api_docs/kbn_apm_utils.mdx b/api_docs/kbn_apm_utils.mdx index b8ef3faac8e19..df98534f70e2c 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: 2023-02-27 +date: 2023-03-02 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 ddf1730e45ac9..8e21de87a558e 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: 2023-02-27 +date: 2023-03-02 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 3511b505e0e2f..642195695ef32 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cases-components'] --- import kbnCasesComponentsObj from './kbn_cases_components.devdocs.json'; diff --git a/api_docs/kbn_cell_actions.mdx b/api_docs/kbn_cell_actions.mdx index 02061af2d14dd..cae863b3ad48f 100644 --- a/api_docs/kbn_cell_actions.mdx +++ b/api_docs/kbn_cell_actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cell-actions title: "@kbn/cell-actions" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cell-actions plugin -date: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cell-actions'] --- import kbnCellActionsObj from './kbn_cell_actions.devdocs.json'; diff --git a/api_docs/kbn_chart_expressions_common.mdx b/api_docs/kbn_chart_expressions_common.mdx index deb309320b4bb..047e2235c2d02 100644 --- a/api_docs/kbn_chart_expressions_common.mdx +++ b/api_docs/kbn_chart_expressions_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-chart-expressions-common title: "@kbn/chart-expressions-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/chart-expressions-common plugin -date: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/chart-expressions-common'] --- import kbnChartExpressionsCommonObj from './kbn_chart_expressions_common.devdocs.json'; diff --git a/api_docs/kbn_chart_icons.mdx b/api_docs/kbn_chart_icons.mdx index bf64afd743b3b..04d450ae11d4e 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: 2023-02-27 +date: 2023-03-02 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 746317a3feac4..551f02d9542cc 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: 2023-02-27 +date: 2023-03-02 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 b3a7d25e34933..681ff13ffec2e 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: 2023-02-27 +date: 2023-03-02 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 c2ae9b8a179bc..5ba8714268011 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: 2023-02-27 +date: 2023-03-02 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 c7dfe9733b1e1..a501527e84f43 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cli-dev-mode'] --- import kbnCliDevModeObj from './kbn_cli_dev_mode.devdocs.json'; diff --git a/api_docs/kbn_code_editor.mdx b/api_docs/kbn_code_editor.mdx index 92cc1a4550224..306bf1bf3cc04 100644 --- a/api_docs/kbn_code_editor.mdx +++ b/api_docs/kbn_code_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-code-editor title: "@kbn/code-editor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/code-editor plugin -date: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/code-editor'] --- import kbnCodeEditorObj from './kbn_code_editor.devdocs.json'; diff --git a/api_docs/kbn_code_editor_mocks.mdx b/api_docs/kbn_code_editor_mocks.mdx index 86b1ef2ecb1a1..3eb75ac47f2de 100644 --- a/api_docs/kbn_code_editor_mocks.mdx +++ b/api_docs/kbn_code_editor_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-code-editor-mocks title: "@kbn/code-editor-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/code-editor-mocks plugin -date: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/code-editor-mocks'] --- import kbnCodeEditorMocksObj from './kbn_code_editor_mocks.devdocs.json'; diff --git a/api_docs/kbn_coloring.mdx b/api_docs/kbn_coloring.mdx index 849cc7d107d55..1a7154a89ba74 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: 2023-02-27 +date: 2023-03-02 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 cb251ab0c54fe..548b1c790d867 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: 2023-02-27 +date: 2023-03-02 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 3239215bb6adf..3cd190aefa52b 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: 2023-02-27 +date: 2023-03-02 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 711b3e8bd807a..cf2176a083699 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-schema'] --- import kbnConfigSchemaObj from './kbn_config_schema.devdocs.json'; diff --git a/api_docs/kbn_content_management_content_editor.mdx b/api_docs/kbn_content_management_content_editor.mdx index 09e4ce4f3836f..ae248ef63ef85 100644 --- a/api_docs/kbn_content_management_content_editor.mdx +++ b/api_docs/kbn_content_management_content_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-content-editor title: "@kbn/content-management-content-editor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-content-editor plugin -date: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-content-editor'] --- import kbnContentManagementContentEditorObj from './kbn_content_management_content_editor.devdocs.json'; diff --git a/api_docs/kbn_content_management_table_list.mdx b/api_docs/kbn_content_management_table_list.mdx index 9ab4d41b30e87..53aab7b923e5b 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: 2023-02-27 +date: 2023-03-02 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 3a3df328dc8d7..959fa6d5e4fcb 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: 2023-02-27 +date: 2023-03-02 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 81db7f3f0ab31..39cc8d5e65dd7 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: 2023-02-27 +date: 2023-03-02 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 15cfcda31f131..693479a213c6b 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: 2023-02-27 +date: 2023-03-02 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 eb94716c82be0..ba1667310138f 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: 2023-02-27 +date: 2023-03-02 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 1535727bc8d26..ea4a5288ef4d7 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: 2023-02-27 +date: 2023-03-02 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 7347a63bc8e23..62494fbd712d9 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: 2023-02-27 +date: 2023-03-02 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 072e69f42cab8..ed5a63bd6b481 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: 2023-02-27 +date: 2023-03-02 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 bd3b0383bc332..0ae948a0f1967 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: 2023-02-27 +date: 2023-03-02 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 993b94c6655ce..f388bdba22b0d 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: 2023-02-27 +date: 2023-03-02 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 068e2faf47ed6..27b3210a296b2 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: 2023-02-27 +date: 2023-03-02 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 721be4c216c89..00d7805a17f85 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: 2023-02-27 +date: 2023-03-02 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 0a109d63443f6..c60a9cea687b8 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: 2023-02-27 +date: 2023-03-02 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 c2bc58640090a..a8b27d9611a63 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: 2023-02-27 +date: 2023-03-02 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 b975c09f45aaf..4fc1e24faf504 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: 2023-02-27 +date: 2023-03-02 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 c7e8c5b615357..7fa62be774cc5 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: 2023-02-27 +date: 2023-03-02 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 f1571949332a2..daec7cc05a345 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: 2023-02-27 +date: 2023-03-02 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 e9f1270cf71fb..8c8951b3d7c37 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: 2023-02-27 +date: 2023-03-02 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 0ebeda8790a7c..62ad4d1aa9dc3 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: 2023-02-27 +date: 2023-03-02 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 4c206c74f1a8d..638f292fdb98c 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: 2023-02-27 +date: 2023-03-02 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 bcc107a33368f..38b47cd1d1fae 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: 2023-02-27 +date: 2023-03-02 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 01a7e68610e7c..eadd485abc837 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: 2023-02-27 +date: 2023-03-02 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 1de0023acef2e..f0a27f2cfa02e 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: 2023-02-27 +date: 2023-03-02 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 3ed282bbfd36e..6a3fc90b2c703 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: 2023-02-27 +date: 2023-03-02 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 c101b8c201c2c..11d112819ab49 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-config-server-internal'] --- import kbnCoreConfigServerInternalObj from './kbn_core_config_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_browser.mdx b/api_docs/kbn_core_custom_branding_browser.mdx index 580ff5a3b57fe..aa063965b3ee7 100644 --- a/api_docs/kbn_core_custom_branding_browser.mdx +++ b/api_docs/kbn_core_custom_branding_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-browser title: "@kbn/core-custom-branding-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-browser plugin -date: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-browser'] --- import kbnCoreCustomBrandingBrowserObj from './kbn_core_custom_branding_browser.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_browser_internal.mdx b/api_docs/kbn_core_custom_branding_browser_internal.mdx index cda93ff8286cd..86828fa0fdd9f 100644 --- a/api_docs/kbn_core_custom_branding_browser_internal.mdx +++ b/api_docs/kbn_core_custom_branding_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-browser-internal title: "@kbn/core-custom-branding-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-browser-internal plugin -date: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-browser-internal'] --- import kbnCoreCustomBrandingBrowserInternalObj from './kbn_core_custom_branding_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_browser_mocks.mdx b/api_docs/kbn_core_custom_branding_browser_mocks.mdx index 20ec492830d9b..04eb2582e72a4 100644 --- a/api_docs/kbn_core_custom_branding_browser_mocks.mdx +++ b/api_docs/kbn_core_custom_branding_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-browser-mocks title: "@kbn/core-custom-branding-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-browser-mocks plugin -date: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-browser-mocks'] --- import kbnCoreCustomBrandingBrowserMocksObj from './kbn_core_custom_branding_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_common.mdx b/api_docs/kbn_core_custom_branding_common.mdx index d3df38b317fe0..353bff8c49d62 100644 --- a/api_docs/kbn_core_custom_branding_common.mdx +++ b/api_docs/kbn_core_custom_branding_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-common title: "@kbn/core-custom-branding-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-common plugin -date: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-common'] --- import kbnCoreCustomBrandingCommonObj from './kbn_core_custom_branding_common.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_server.mdx b/api_docs/kbn_core_custom_branding_server.mdx index e8f9159b959a9..6aa7969a43b51 100644 --- a/api_docs/kbn_core_custom_branding_server.mdx +++ b/api_docs/kbn_core_custom_branding_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-server title: "@kbn/core-custom-branding-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-server plugin -date: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-server'] --- import kbnCoreCustomBrandingServerObj from './kbn_core_custom_branding_server.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_server_internal.mdx b/api_docs/kbn_core_custom_branding_server_internal.mdx index 8cf17e8a59c8b..7bd406d920bc6 100644 --- a/api_docs/kbn_core_custom_branding_server_internal.mdx +++ b/api_docs/kbn_core_custom_branding_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-server-internal title: "@kbn/core-custom-branding-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-server-internal plugin -date: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-server-internal'] --- import kbnCoreCustomBrandingServerInternalObj from './kbn_core_custom_branding_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_server_mocks.mdx b/api_docs/kbn_core_custom_branding_server_mocks.mdx index 8cf281aa41042..722fa3fa285d8 100644 --- a/api_docs/kbn_core_custom_branding_server_mocks.mdx +++ b/api_docs/kbn_core_custom_branding_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-server-mocks title: "@kbn/core-custom-branding-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-server-mocks plugin -date: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-server-mocks'] --- import kbnCoreCustomBrandingServerMocksObj from './kbn_core_custom_branding_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser.mdx b/api_docs/kbn_core_deprecations_browser.mdx index 1cf9cc03fd97c..665a262270ea7 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: 2023-02-27 +date: 2023-03-02 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 ad46c1edd9657..d9f84f98feb3d 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: 2023-02-27 +date: 2023-03-02 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 33f157f2a212b..65a8d9b69582b 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: 2023-02-27 +date: 2023-03-02 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 042e4b04bcf52..462a0bf5285f3 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: 2023-02-27 +date: 2023-03-02 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 f8d49d314c606..5db940d54a40d 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: 2023-02-27 +date: 2023-03-02 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 3857af8276b4a..7e9c1a6c6ae2e 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: 2023-02-27 +date: 2023-03-02 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 ea2ea178cf45d..261fdab10d421 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: 2023-02-27 +date: 2023-03-02 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 7d97ab37e707c..d6a569a5f4c4b 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: 2023-02-27 +date: 2023-03-02 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 cf86951be6012..4142f67ae6fd8 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: 2023-02-27 +date: 2023-03-02 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 baeb86012fd03..3f5406f8fc672 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: 2023-02-27 +date: 2023-03-02 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 15c05311a73a5..649286af18285 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: 2023-02-27 +date: 2023-03-02 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 9281ef198f2ee..9266b9941613a 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: 2023-02-27 +date: 2023-03-02 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 c9ea75a6c3b23..2bc3ed1923ee2 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: 2023-02-27 +date: 2023-03-02 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 a4086757492e5..a12129bf7ab2f 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: 2023-02-27 +date: 2023-03-02 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 a707b2a011962..cb44e1f6f2a5f 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: 2023-02-27 +date: 2023-03-02 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 c00e6c8f19b53..3b12b50d38668 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: 2023-02-27 +date: 2023-03-02 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 62d29a36829ee..c5f3a640b13dc 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: 2023-02-27 +date: 2023-03-02 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 df6cdd92bf87e..4fa17b4ed208e 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: 2023-02-27 +date: 2023-03-02 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 05859ea24e9e8..2f4f45857690a 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: 2023-02-27 +date: 2023-03-02 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 e62babd649cbf..80dc6b3f42667 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: 2023-02-27 +date: 2023-03-02 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 df4f5446a3094..1ecefa27b6cd3 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: 2023-02-27 +date: 2023-03-02 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 d32765fbcfc14..202214f6b557d 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: 2023-02-27 +date: 2023-03-02 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 0ec13c09b40dc..4afc04a60783b 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: 2023-02-27 +date: 2023-03-02 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 6d07e121f437f..b2ddbf0a9010f 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: 2023-02-27 +date: 2023-03-02 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 7be2d7332e660..474ea66f88224 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: 2023-02-27 +date: 2023-03-02 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 c9a614378a360..9b5f7fc44fde5 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: 2023-02-27 +date: 2023-03-02 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 14a37f5004d02..442597a23b31c 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: 2023-02-27 +date: 2023-03-02 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 93e0fb83aae93..e3e51a50d3922 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: 2023-02-27 +date: 2023-03-02 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 6fdb476db5757..f94d4846c71f8 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: 2023-02-27 +date: 2023-03-02 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 04d6d8654db89..ae3c02e387d5d 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: 2023-02-27 +date: 2023-03-02 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 f0fe8987be77a..fdbcf724f249f 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: 2023-02-27 +date: 2023-03-02 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 bd1b8d72dbcb9..099083e2d0951 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: 2023-02-27 +date: 2023-03-02 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 ff966b3d52166..5f3f3669b8801 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: 2023-02-27 +date: 2023-03-02 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 d2e0cc08f2eb4..f279de0f63180 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: 2023-02-27 +date: 2023-03-02 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 2bae75d9a5c77..d3afd758189df 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: 2023-02-27 +date: 2023-03-02 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 6daa81af7f719..2e64643ebfbbe 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: 2023-02-27 +date: 2023-03-02 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 5abfd211d3f55..e7a36fd46d9b7 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: 2023-02-27 +date: 2023-03-02 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 9c6b7735df4fd..f638b954afa2c 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: 2023-02-27 +date: 2023-03-02 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 7132683763250..4978c2bcf1edc 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server'] --- import kbnCoreHttpServerObj from './kbn_core_http_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_server_internal.mdx b/api_docs/kbn_core_http_server_internal.mdx index 974f85a8b55f7..af4705eebf0af 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: 2023-02-27 +date: 2023-03-02 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 0bfca6e59b368..b586c524253a4 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: 2023-02-27 +date: 2023-03-02 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 47db66b5ecc84..eeb40f40210db 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: 2023-02-27 +date: 2023-03-02 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 c27bfbd37feac..6fb7956371da3 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: 2023-02-27 +date: 2023-03-02 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 09dae1b2abf1b..f68a4931bbade 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: 2023-02-27 +date: 2023-03-02 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 6d56e08c115c5..0e32141f1eada 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: 2023-02-27 +date: 2023-03-02 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 9ae37a90003a0..165f46f6141ee 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server-mocks'] --- import kbnCoreI18nServerMocksObj from './kbn_core_i18n_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_injected_metadata_browser_mocks.mdx b/api_docs/kbn_core_injected_metadata_browser_mocks.mdx index a1f7cb96252a7..ca24813984b7b 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: 2023-02-27 +date: 2023-03-02 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 80e25386fc84b..9df4ebd75e861 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: 2023-02-27 +date: 2023-03-02 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 1011becb38e39..828d24502d372 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: 2023-02-27 +date: 2023-03-02 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.devdocs.json b/api_docs/kbn_core_lifecycle_browser.devdocs.json index 92a9bfa433892..fa4c32568aba3 100644 --- a/api_docs/kbn_core_lifecycle_browser.devdocs.json +++ b/api_docs/kbn_core_lifecycle_browser.devdocs.json @@ -677,10 +677,6 @@ "plugin": "discover", "path": "src/plugins/discover/public/application/main/discover_main_route.tsx" }, - { - "plugin": "cases", - "path": "x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx" - }, { "plugin": "fileUpload", "path": "x-pack/plugins/file_upload/public/kibana_services.ts" @@ -801,10 +797,6 @@ "plugin": "synthetics", "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_location_monitors.ts" }, - { - "plugin": "synthetics", - "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_locations_api.ts" - }, { "plugin": "synthetics", "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/hooks/use_monitor_name.ts" @@ -833,10 +825,6 @@ "plugin": "visualizations", "path": "src/plugins/visualizations/public/visualize_app/components/visualize_listing.tsx" }, - { - "plugin": "cases", - "path": "x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/plugin.tsx" - }, { "plugin": "dataVisualizer", "path": "x-pack/plugins/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx" diff --git a/api_docs/kbn_core_lifecycle_browser.mdx b/api_docs/kbn_core_lifecycle_browser.mdx index 610ccc9db4916..e68f69e5b2541 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: 2023-02-27 +date: 2023-03-02 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 035371808b5f4..47d4140fa4fa3 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: 2023-02-27 +date: 2023-03-02 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 2ed4f5fef2db3..eb11dad2ad958 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: 2023-02-27 +date: 2023-03-02 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 562f7f28c60f8..b84dcef82884e 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: 2023-02-27 +date: 2023-03-02 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 1eb48afd95a2b..39c7734369d0a 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: 2023-02-27 +date: 2023-03-02 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 327d6e7a3ce5a..e1c35d2d1849f 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: 2023-02-27 +date: 2023-03-02 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 83d8ea5c57490..854f2e1c444f1 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: 2023-02-27 +date: 2023-03-02 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 c1d2cb23e5972..be6cd0ddc3609 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: 2023-02-27 +date: 2023-03-02 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 a4b7482bfe9e8..1371904a744ae 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: 2023-02-27 +date: 2023-03-02 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 92b8f41da6d32..44835e9fa7d13 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: 2023-02-27 +date: 2023-03-02 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 af3e01e6ece08..3d18047440579 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: 2023-02-27 +date: 2023-03-02 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 0e40b26c24c4c..0366bf0bddf29 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: 2023-02-27 +date: 2023-03-02 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 6e00b954efbb7..5653d21b167ea 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: 2023-02-27 +date: 2023-03-02 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 a238d378d5c79..8641f8766334b 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: 2023-02-27 +date: 2023-03-02 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 9c0ab3290114f..0bf561242359e 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: 2023-02-27 +date: 2023-03-02 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.devdocs.json b/api_docs/kbn_core_node_server.devdocs.json index 9b0d9eed398dd..92ce832afbc4e 100644 --- a/api_docs/kbn_core_node_server.devdocs.json +++ b/api_docs/kbn_core_node_server.devdocs.json @@ -96,6 +96,19 @@ "path": "packages/core/node/core-node-server/src/types.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-node-server", + "id": "def-common.NodeRoles.migrator", + "type": "boolean", + "tags": [], + "label": "migrator", + "description": [ + "\nStart Kibana with the specific purpose of completing the migrations phase then shutting down." + ], + "path": "packages/core/node/core-node-server/src/types.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false diff --git a/api_docs/kbn_core_node_server.mdx b/api_docs/kbn_core_node_server.mdx index 278c9eddb7fc4..93c4da6e7f8fb 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server'] --- import kbnCoreNodeServerObj from './kbn_core_node_server.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 5 | 0 | 0 | 0 | +| 6 | 0 | 0 | 0 | ## Common diff --git a/api_docs/kbn_core_node_server_internal.devdocs.json b/api_docs/kbn_core_node_server_internal.devdocs.json index 8db2a68da0239..435988185b457 100644 --- a/api_docs/kbn_core_node_server_internal.devdocs.json +++ b/api_docs/kbn_core_node_server_internal.devdocs.json @@ -87,64 +87,6 @@ ], "enums": [], "misc": [], - "objects": [ - { - "parentPluginId": "@kbn/core-node-server-internal", - "id": "def-common.nodeConfig", - "type": "Object", - "tags": [], - "label": "nodeConfig", - "description": [], - "path": "packages/core/node/core-node-server-internal/src/node_config.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/core-node-server-internal", - "id": "def-common.nodeConfig.path", - "type": "string", - "tags": [], - "label": "path", - "description": [], - "signature": [ - "\"node\"" - ], - "path": "packages/core/node/core-node-server-internal/src/node_config.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "@kbn/core-node-server-internal", - "id": "def-common.nodeConfig.schema", - "type": "Object", - "tags": [], - "label": "schema", - "description": [], - "signature": [ - { - "pluginId": "@kbn/config-schema", - "scope": "common", - "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.ObjectType", - "text": "ObjectType" - }, - "<{ roles: ", - { - "pluginId": "@kbn/config-schema", - "scope": "common", - "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" - }, - "<\"*\"[] | (\"ui\" | \"background_tasks\")[]>; }>" - ], - "path": "packages/core/node/core-node-server-internal/src/node_config.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - } - ] + "objects": [] } } \ No newline at end of file diff --git a/api_docs/kbn_core_node_server_internal.mdx b/api_docs/kbn_core_node_server_internal.mdx index 681584152552f..6e09f365f5ecf 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server-internal'] --- import kbnCoreNodeServerInternalObj from './kbn_core_node_server_internal.devdocs.json'; @@ -21,13 +21,10 @@ Contact [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 7 | 0 | 6 | 1 | +| 4 | 0 | 3 | 1 | ## Common -### Objects - - ### Interfaces diff --git a/api_docs/kbn_core_node_server_mocks.devdocs.json b/api_docs/kbn_core_node_server_mocks.devdocs.json index f2d7ca94bf8cf..7997492fa90e4 100644 --- a/api_docs/kbn_core_node_server_mocks.devdocs.json +++ b/api_docs/kbn_core_node_server_mocks.devdocs.json @@ -76,7 +76,7 @@ "label": "createInternalStartContract", "description": [], "signature": [ - "({ ui, backgroundTasks, }?: { ui: boolean; backgroundTasks: boolean; }) => jest.Mocked<", + "({ ui, backgroundTasks, migrator, }?: { ui: boolean; backgroundTasks: boolean; migrator: boolean; }) => jest.Mocked<", { "pluginId": "@kbn/core-node-server-internal", "scope": "common", @@ -99,7 +99,7 @@ "label": "__0", "description": [], "signature": [ - "{ ui: boolean; backgroundTasks: boolean; }" + "{ ui: boolean; backgroundTasks: boolean; migrator: boolean; }" ], "path": "packages/core/node/core-node-server-mocks/src/node_service.mock.ts", "deprecated": false, diff --git a/api_docs/kbn_core_node_server_mocks.mdx b/api_docs/kbn_core_node_server_mocks.mdx index af63729ff7af0..a40eb40c54868 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: 2023-02-27 +date: 2023-03-02 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 f232dc1095c47..d113868c4b205 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: 2023-02-27 +date: 2023-03-02 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 704727e17650c..701025f3a867f 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: 2023-02-27 +date: 2023-03-02 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 7302144640fee..46fb2d4432b9f 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: 2023-02-27 +date: 2023-03-02 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 f3f74033f0690..ade4741ff96e4 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: 2023-02-27 +date: 2023-03-02 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 dae2144fd0091..531d085b34330 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: 2023-02-27 +date: 2023-03-02 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 bde023a91ebb5..a6fefddb61000 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: 2023-02-27 +date: 2023-03-02 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 36b8d050f77d5..1b1a4764cfca8 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: 2023-02-27 +date: 2023-03-02 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 019e64ab0b420..d21b513671928 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: 2023-02-27 +date: 2023-03-02 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 bff0a2571ea11..bbe16935a49c7 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: 2023-02-27 +date: 2023-03-02 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 e9e97ad4ac7ba..13974d291b540 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: 2023-02-27 +date: 2023-03-02 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 be617032639b4..6e1190eaa10d1 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: 2023-02-27 +date: 2023-03-02 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 e99e6c1cb1a3e..c993507dfdb73 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: 2023-02-27 +date: 2023-03-02 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 fd4876fb7217e..8b8f56bc6d341 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: 2023-02-27 +date: 2023-03-02 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 05083a9379a4d..92892c2f4d607 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: 2023-02-27 +date: 2023-03-02 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 9a1b0c18751f7..b4d2dacecaf42 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-server-mocks'] --- import kbnCoreRenderingServerMocksObj from './kbn_core_rendering_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_root_server_internal.devdocs.json b/api_docs/kbn_core_root_server_internal.devdocs.json index 765f11e6ad76e..c5af081041ed3 100644 --- a/api_docs/kbn_core_root_server_internal.devdocs.json +++ b/api_docs/kbn_core_root_server_internal.devdocs.json @@ -417,7 +417,43 @@ "initialIsOpen": false } ], - "functions": [], + "functions": [ + { + "parentPluginId": "@kbn/core-root-server-internal", + "id": "def-common.registerServiceConfig", + "type": "Function", + "tags": [], + "label": "registerServiceConfig", + "description": [], + "signature": [ + "(configService: ", + "ConfigService", + ") => void" + ], + "path": "packages/core/root/core-root-server-internal/src/register_service_config.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-root-server-internal", + "id": "def-common.registerServiceConfig.$1", + "type": "Object", + "tags": [], + "label": "configService", + "description": [], + "signature": [ + "ConfigService" + ], + "path": "packages/core/root/core-root-server-internal/src/register_service_config.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + } + ], "interfaces": [], "enums": [], "misc": [], diff --git a/api_docs/kbn_core_root_server_internal.mdx b/api_docs/kbn_core_root_server_internal.mdx index cc1550490e12f..11d778cf27954 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-root-server-internal'] --- import kbnCoreRootServerInternalObj from './kbn_core_root_server_internal.devdocs.json'; @@ -21,10 +21,13 @@ Contact [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 23 | 1 | 22 | 0 | +| 25 | 1 | 24 | 0 | ## Common +### Functions + + ### Classes diff --git a/api_docs/kbn_core_saved_objects_api_browser.devdocs.json b/api_docs/kbn_core_saved_objects_api_browser.devdocs.json index 47616ddb798b9..755eca219cfc1 100644 --- a/api_docs/kbn_core_saved_objects_api_browser.devdocs.json +++ b/api_docs/kbn_core_saved_objects_api_browser.devdocs.json @@ -1278,18 +1278,6 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/common/containers/dashboards/utils.ts" }, - { - "plugin": "synthetics", - "path": "x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/api.ts" - }, - { - "plugin": "synthetics", - "path": "x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/api.ts" - }, - { - "plugin": "synthetics", - "path": "x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/api.ts" - }, { "plugin": "synthetics", "path": "x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/api.ts" @@ -1558,10 +1546,6 @@ "plugin": "graph", "path": "x-pack/plugins/graph/public/helpers/saved_workspace_utils.ts" }, - { - "plugin": "synthetics", - "path": "x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/api.ts" - }, { "plugin": "synthetics", "path": "x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/api.ts" @@ -2243,10 +2227,6 @@ "plugin": "lens", "path": "x-pack/plugins/lens/public/persistence/saved_objects_utils/find_object_by_title.ts" }, - { - "plugin": "cases", - "path": "x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx" - }, { "plugin": "maps", "path": "x-pack/plugins/maps/public/routes/list_page/maps_list_view.tsx" @@ -2527,10 +2507,6 @@ "plugin": "monitoring", "path": "x-pack/plugins/monitoring/public/application/pages/elasticsearch/ingest_pipeline_modal.tsx" }, - { - "plugin": "synthetics", - "path": "x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/api.ts" - }, { "plugin": "synthetics", "path": "x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/api.ts" @@ -4000,50 +3976,6 @@ "plugin": "ml", "path": "x-pack/plugins/ml/common/types/kibana.ts" }, - { - "plugin": "cases", - "path": "x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx" - }, - { - "plugin": "cases", - "path": "x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx" - }, - { - "plugin": "cases", - "path": "x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx" - }, - { - "plugin": "cases", - "path": "x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx" - }, - { - "plugin": "cases", - "path": "x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx" - }, - { - "plugin": "cases", - "path": "x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx" - }, - { - "plugin": "cases", - "path": "x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx" - }, - { - "plugin": "cases", - "path": "x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx" - }, - { - "plugin": "cases", - "path": "x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx" - }, - { - "plugin": "cases", - "path": "x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx" - }, - { - "plugin": "cases", - "path": "x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx" - }, { "plugin": "maps", "path": "x-pack/plugins/maps/public/maps_vis_type_alias.ts" diff --git a/api_docs/kbn_core_saved_objects_api_browser.mdx b/api_docs/kbn_core_saved_objects_api_browser.mdx index 5a6fe5d1dc482..9ab3d991bda82 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: 2023-02-27 +date: 2023-03-02 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 3eedd0bf046a0..42c6a15446502 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: 2023-02-27 +date: 2023-03-02 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 26e8db451e83a..2aeeb00ad057c 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: 2023-02-27 +date: 2023-03-02 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 eec7b798a06c1..8adfa0bfd41b4 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: 2023-02-27 +date: 2023-03-02 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.devdocs.json b/api_docs/kbn_core_saved_objects_base_server_internal.devdocs.json index 89b711814980d..7e50fa4187ed5 100644 --- a/api_docs/kbn_core_saved_objects_base_server_internal.devdocs.json +++ b/api_docs/kbn_core_saved_objects_base_server_internal.devdocs.json @@ -70,7 +70,7 @@ "label": "migration", "description": [], "signature": [ - "{ readonly discardUnknownObjects?: string | undefined; readonly discardCorruptObjects?: string | undefined; readonly skip: boolean; readonly pollInterval: number; readonly batchSize: number; readonly maxBatchSizeBytes: ", + "{ readonly discardUnknownObjects?: string | undefined; readonly discardCorruptObjects?: string | undefined; readonly skip: boolean; readonly pollInterval: number; readonly algorithm: \"v2\" | \"zdt\"; readonly batchSize: number; readonly maxBatchSizeBytes: ", { "pluginId": "@kbn/config-schema", "scope": "common", @@ -129,7 +129,7 @@ "label": "rawMigrationConfig", "description": [], "signature": [ - "Readonly<{ discardUnknownObjects?: string | undefined; discardCorruptObjects?: string | undefined; } & { skip: boolean; pollInterval: number; batchSize: number; maxBatchSizeBytes: ", + "Readonly<{ discardUnknownObjects?: string | undefined; discardCorruptObjects?: string | undefined; } & { skip: boolean; pollInterval: number; algorithm: \"v2\" | \"zdt\"; batchSize: number; maxBatchSizeBytes: ", { "pluginId": "@kbn/config-schema", "scope": "common", @@ -755,7 +755,7 @@ "label": "SavedObjectsMigrationConfigType", "description": [], "signature": [ - "{ readonly discardUnknownObjects?: string | undefined; readonly discardCorruptObjects?: string | undefined; readonly skip: boolean; readonly pollInterval: number; readonly batchSize: number; readonly maxBatchSizeBytes: ", + "{ readonly discardUnknownObjects?: string | undefined; readonly discardCorruptObjects?: string | undefined; readonly skip: boolean; readonly pollInterval: number; readonly algorithm: \"v2\" | \"zdt\"; readonly batchSize: number; readonly maxBatchSizeBytes: ", { "pluginId": "@kbn/config-schema", "scope": "common", @@ -887,7 +887,15 @@ "section": "def-common.ObjectType", "text": "ObjectType" }, - "<{ batchSize: ", + "<{ algorithm: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "<\"v2\" | \"zdt\">; batchSize: ", { "pluginId": "@kbn/config-schema", "scope": "common", 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 1f86a3cecacdc..750f6db42d9fc 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: 2023-02-27 +date: 2023-03-02 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 d36afdadf0cbd..0e82b824c6b63 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: 2023-02-27 +date: 2023-03-02 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 36259efd2ef5a..ec4157e9a46e1 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: 2023-02-27 +date: 2023-03-02 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 f84981449ddf2..f57b3dae843dd 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: 2023-02-27 +date: 2023-03-02 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 5228ef48d73d9..0b4311260a777 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: 2023-02-27 +date: 2023-03-02 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 0c231f1ff4878..62156f3b4dc5a 100644 --- a/api_docs/kbn_core_saved_objects_common.devdocs.json +++ b/api_docs/kbn_core_saved_objects_common.devdocs.json @@ -2800,6 +2800,22 @@ "plugin": "data", "path": "src/plugins/data/common/query/persistable_state.ts" }, + { + "plugin": "savedObjectsTaggingOss", + "path": "src/plugins/saved_objects_tagging_oss/public/api.ts" + }, + { + "plugin": "savedObjectsTaggingOss", + "path": "src/plugins/saved_objects_tagging_oss/public/api.ts" + }, + { + "plugin": "savedObjectsTaggingOss", + "path": "src/plugins/saved_objects_tagging_oss/public/api.ts" + }, + { + "plugin": "savedObjectsTaggingOss", + "path": "src/plugins/saved_objects_tagging_oss/public/api.ts" + }, { "plugin": "embeddable", "path": "src/plugins/embeddable/common/lib/migrate_base_input.ts" @@ -2820,22 +2836,6 @@ "plugin": "embeddable", "path": "src/plugins/embeddable/common/lib/inject.ts" }, - { - "plugin": "savedObjectsTaggingOss", - "path": "src/plugins/saved_objects_tagging_oss/public/api.ts" - }, - { - "plugin": "savedObjectsTaggingOss", - "path": "src/plugins/saved_objects_tagging_oss/public/api.ts" - }, - { - "plugin": "savedObjectsTaggingOss", - "path": "src/plugins/saved_objects_tagging_oss/public/api.ts" - }, - { - "plugin": "savedObjectsTaggingOss", - "path": "src/plugins/saved_objects_tagging_oss/public/api.ts" - }, { "plugin": "visualizations", "path": "src/plugins/visualizations/public/utils/saved_visualization_references/controls_references.ts" diff --git a/api_docs/kbn_core_saved_objects_common.mdx b/api_docs/kbn_core_saved_objects_common.mdx index 5db946fd1fc84..a7da75722e944 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: 2023-02-27 +date: 2023-03-02 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 66ce8098c17c7..7b218a2ad32f0 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: 2023-02-27 +date: 2023-03-02 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 e7695de2d2b2f..6c75150030f56 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: 2023-02-27 +date: 2023-03-02 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.devdocs.json b/api_docs/kbn_core_saved_objects_migration_server_internal.devdocs.json index 68bab1e33dbd5..64a0494b9127c 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_internal.devdocs.json +++ b/api_docs/kbn_core_saved_objects_migration_server_internal.devdocs.json @@ -599,6 +599,57 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/core-saved-objects-migration-server-internal", + "id": "def-common.buildTypesMappings", + "type": "Function", + "tags": [], + "label": "buildTypesMappings", + "description": [ + "\nMerge mappings from all registered saved object types." + ], + "signature": [ + "(types: ", + { + "pluginId": "@kbn/core-saved-objects-server", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsServerPluginApi", + "section": "def-common.SavedObjectsType", + "text": "SavedObjectsType" + }, + "[]) => ", + "SavedObjectsTypeMappingDefinitions" + ], + "path": "packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_types_mappings.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-saved-objects-migration-server-internal", + "id": "def-common.buildTypesMappings.$1", + "type": "Array", + "tags": [], + "label": "types", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-saved-objects-server", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsServerPluginApi", + "section": "def-common.SavedObjectsType", + "text": "SavedObjectsType" + }, + "[]" + ], + "path": "packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_types_mappings.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/core-saved-objects-migration-server-internal", "id": "def-common.bulkOverwriteTransformedDocuments", @@ -609,7 +660,7 @@ "\nWrite the up-to-date transformed documents to the index, overwriting any\ndocuments that are still on their outdated version." ], "signature": [ - "({ client, index, transformedDocs, refresh, }: ", + "({ client, index, operations, refresh, }: ", "BulkOverwriteTransformedDocumentsParams", ") => ", "TaskEither", @@ -632,7 +683,7 @@ "id": "def-common.bulkOverwriteTransformedDocuments.$1", "type": "Object", "tags": [], - "label": "{\n client,\n index,\n transformedDocs,\n refresh = false,\n }", + "label": "{\n client,\n index,\n operations,\n refresh = false,\n }", "description": [], "signature": [ "BulkOverwriteTransformedDocumentsParams" @@ -816,6 +867,92 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/core-saved-objects-migration-server-internal", + "id": "def-common.createBulkDeleteOperationBody", + "type": "Function", + "tags": [], + "label": "createBulkDeleteOperationBody", + "description": [ + "\nGiven a document id, creates a valid body to delete the document using the Bulk API." + ], + "signature": [ + "(_id: string) => ", + "BulkOperationContainer" + ], + "path": "packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-saved-objects-migration-server-internal", + "id": "def-common.createBulkDeleteOperationBody.$1", + "type": "string", + "tags": [], + "label": "_id", + "description": [], + "signature": [ + "string" + ], + "path": "packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-saved-objects-migration-server-internal", + "id": "def-common.createBulkIndexOperationTuple", + "type": "Function", + "tags": [], + "label": "createBulkIndexOperationTuple", + "description": [ + "\nGiven a document, creates a valid body to index the document using the Bulk API." + ], + "signature": [ + "(doc: ", + { + "pluginId": "@kbn/core-saved-objects-server", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsServerPluginApi", + "section": "def-common.SavedObjectsRawDoc", + "text": "SavedObjectsRawDoc" + }, + ") => ", + "BulkIndexOperationTuple" + ], + "path": "packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-saved-objects-migration-server-internal", + "id": "def-common.createBulkIndexOperationTuple.$1", + "type": "Object", + "tags": [], + "label": "doc", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-saved-objects-server", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsServerPluginApi", + "section": "def-common.SavedObjectsRawDoc", + "text": "SavedObjectsRawDoc" + } + ], + "path": "packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/core-saved-objects-migration-server-internal", "id": "def-common.createIndex", @@ -1091,57 +1228,6 @@ "returnComment": [], "initialIsOpen": false }, - { - "parentPluginId": "@kbn/core-saved-objects-migration-server-internal", - "id": "def-common.mergeTypes", - "type": "Function", - "tags": [], - "label": "mergeTypes", - "description": [ - "\nMerges savedObjectMappings properties into a single object, verifying that\nno mappings are redefined." - ], - "signature": [ - "(types: ", - { - "pluginId": "@kbn/core-saved-objects-server", - "scope": "common", - "docId": "kibKbnCoreSavedObjectsServerPluginApi", - "section": "def-common.SavedObjectsType", - "text": "SavedObjectsType" - }, - "[]) => ", - "SavedObjectsTypeMappingDefinitions" - ], - "path": "packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/core-saved-objects-migration-server-internal", - "id": "def-common.mergeTypes.$1", - "type": "Array", - "tags": [], - "label": "types", - "description": [], - "signature": [ - { - "pluginId": "@kbn/core-saved-objects-server", - "scope": "common", - "docId": "kibKbnCoreSavedObjectsServerPluginApi", - "section": "def-common.SavedObjectsType", - "text": "SavedObjectsType" - }, - "[]" - ], - "path": "packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [], - "initialIsOpen": false - }, { "parentPluginId": "@kbn/core-saved-objects-migration-server-internal", "id": "def-common.openPit", @@ -1685,37 +1771,39 @@ }, { "parentPluginId": "@kbn/core-saved-objects-migration-server-internal", - "id": "def-common.updateTargetMappingsMeta", + "id": "def-common.updateMappings", "type": "Function", "tags": [], - "label": "updateTargetMappingsMeta", + "label": "updateMappings", "description": [ - "\nUpdates an index's mappings _meta information" + "\nUpdates an index's mappings and runs an pickupUpdatedMappings task so that the mapping\nchanges are \"picked up\". Returns a taskId to track progress." ], "signature": [ - "({ client, index, meta, }: ", - "UpdateTargetMappingsMetaParams", + "({ client, index, mappings, }: ", + "UpdateMappingsParams", ") => ", "TaskEither", "<", "RetryableEsClientError", - ", \"update_mappings_meta_succeeded\">" + " | ", + "IncompatibleMappingException", + ", \"update_mappings_succeeded\">" ], - "path": "packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_target_mappings_meta.ts", + "path": "packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_mappings.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "@kbn/core-saved-objects-migration-server-internal", - "id": "def-common.updateTargetMappingsMeta.$1", + "id": "def-common.updateMappings.$1", "type": "Object", "tags": [], - "label": "{\n client,\n index,\n meta,\n }", + "label": "{\n client,\n index,\n mappings,\n}", "description": [], "signature": [ - "UpdateTargetMappingsMetaParams" + "UpdateMappingsParams" ], - "path": "packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_target_mappings_meta.ts", + "path": "packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_mappings.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -3335,7 +3423,7 @@ "label": "soMigrationsConfig", "description": [], "signature": [ - "{ readonly discardUnknownObjects?: string | undefined; readonly discardCorruptObjects?: string | undefined; readonly skip: boolean; readonly pollInterval: number; readonly batchSize: number; readonly maxBatchSizeBytes: ", + "{ readonly discardUnknownObjects?: string | undefined; readonly discardCorruptObjects?: string | undefined; readonly skip: boolean; readonly pollInterval: number; readonly algorithm: \"v2\" | \"zdt\"; readonly batchSize: number; readonly maxBatchSizeBytes: ", { "pluginId": "@kbn/config-schema", "scope": "common", 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 aaa6da1d1ce75..898a1e2d4ea8b 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-migration-server-internal'] --- import kbnCoreSavedObjectsMigrationServerInternalObj from './kbn_core_saved_objects_migration_server_internal.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 112 | 0 | 79 | 45 | +| 116 | 0 | 81 | 46 | ## Common 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 190de89e284e3..b9ef91939e896 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: 2023-02-27 +date: 2023-03-02 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 1997dd7e500a1..d203957246f69 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: 2023-02-27 +date: 2023-03-02 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 ad88d3434c93b..4ab8eecaa1622 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: 2023-02-27 +date: 2023-03-02 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 e019befaf4778..31fb2ac1d9941 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: 2023-02-27 +date: 2023-03-02 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 a3cdd465231a2..c1c957a5344ba 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: 2023-02-27 +date: 2023-03-02 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 db13569fc15a5..7653cf7c2050a 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: 2023-02-27 +date: 2023-03-02 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 e98ac07a6eb86..17c1f3117f330 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: 2023-02-27 +date: 2023-03-02 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 184613b2bd134..bd694773a5f16 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: 2023-02-27 +date: 2023-03-02 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 8ee1bcb32622d..d5f3d4039a718 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: 2023-02-27 +date: 2023-03-02 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 a72c75b0a892c..504c5c613feae 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: 2023-02-27 +date: 2023-03-02 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 e16d7583efcd8..33443ded64359 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: 2023-02-27 +date: 2023-03-02 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 1a08b1250ecf2..4f31e6de42168 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: 2023-02-27 +date: 2023-03-02 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 7fc2878ad1206..65d472a647d69 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: 2023-02-27 +date: 2023-03-02 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 eda623498db60..fc0e425c6fd46 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: 2023-02-27 +date: 2023-03-02 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 447c495ff4ccd..0d5e270a9df43 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: 2023-02-27 +date: 2023-03-02 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 175e1ed9bab85..7353d7a47d85e 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: 2023-02-27 +date: 2023-03-02 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 35c6273c61213..f4df154e80c44 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: 2023-02-27 +date: 2023-03-02 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 3b22b27f1997a..5edaedd815f0e 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: 2023-02-27 +date: 2023-03-02 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 f3406378d00d4..65093899ea9b9 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: 2023-02-27 +date: 2023-03-02 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 76e920393bb73..4a1f5feb62f3f 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: 2023-02-27 +date: 2023-03-02 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 66666a05ffe19..135b54ce7e869 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser-mocks'] --- import kbnCoreUiSettingsBrowserMocksObj from './kbn_core_ui_settings_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_common.mdx b/api_docs/kbn_core_ui_settings_common.mdx index 927468797587b..213dbf4d62c16 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-common'] --- import kbnCoreUiSettingsCommonObj from './kbn_core_ui_settings_common.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server.mdx b/api_docs/kbn_core_ui_settings_server.mdx index 71cb58aad9b56..0d5ad1eed566d 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server'] --- import kbnCoreUiSettingsServerObj from './kbn_core_ui_settings_server.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server_internal.mdx b/api_docs/kbn_core_ui_settings_server_internal.mdx index 56d22396db1b6..2e81fbb70e4ae 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server-internal'] --- import kbnCoreUiSettingsServerInternalObj from './kbn_core_ui_settings_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server_mocks.mdx b/api_docs/kbn_core_ui_settings_server_mocks.mdx index 78bad8c43e148..b188b63b47ec9 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: 2023-02-27 +date: 2023-03-02 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 9c2262467058a..ed6e1939b36f7 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: 2023-02-27 +date: 2023-03-02 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 588d770ab0f57..da52ba5ced2bd 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: 2023-02-27 +date: 2023-03-02 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 d5dda260a906e..878698459b87b 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: 2023-02-27 +date: 2023-03-02 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 02386cdf929e1..fa020d9f7e28a 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: 2023-02-27 +date: 2023-03-02 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 d7193bced5849..9d7d13934ab24 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto-browser'] --- import kbnCryptoBrowserObj from './kbn_crypto_browser.devdocs.json'; diff --git a/api_docs/kbn_cypress_config.mdx b/api_docs/kbn_cypress_config.mdx index 53c2e496b6b2e..1f710dbf67056 100644 --- a/api_docs/kbn_cypress_config.mdx +++ b/api_docs/kbn_cypress_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cypress-config title: "@kbn/cypress-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cypress-config plugin -date: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cypress-config'] --- import kbnCypressConfigObj from './kbn_cypress_config.devdocs.json'; diff --git a/api_docs/kbn_datemath.mdx b/api_docs/kbn_datemath.mdx index 2e5a072cf7495..75eabd2444aaf 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: 2023-02-27 +date: 2023-03-02 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 b08d3e2f1a0eb..cc58fa1feb24c 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: 2023-02-27 +date: 2023-03-02 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 658fd80529198..a64f39e0387e7 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: 2023-02-27 +date: 2023-03-02 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 1513bbc31f7a0..3247d3e925491 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: 2023-02-27 +date: 2023-03-02 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 5e9fb3d83fde2..a470e64f11a32 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: 2023-02-27 +date: 2023-03-02 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 415e48f690346..0a96000e4bb7d 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: 2023-02-27 +date: 2023-03-02 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 a9f89a9888a86..9983ad5184956 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: 2023-02-27 +date: 2023-03-02 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 21787f427307a..3086015b75ea1 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ebt-tools'] --- import kbnEbtToolsObj from './kbn_ebt_tools.devdocs.json'; diff --git a/api_docs/kbn_ecs.mdx b/api_docs/kbn_ecs.mdx index 552ef1cd7001e..e682604f096df 100644 --- a/api_docs/kbn_ecs.mdx +++ b/api_docs/kbn_ecs.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ecs title: "@kbn/ecs" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ecs plugin -date: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ecs'] --- import kbnEcsObj from './kbn_ecs.devdocs.json'; diff --git a/api_docs/kbn_ecs_data_quality_dashboard.mdx b/api_docs/kbn_ecs_data_quality_dashboard.mdx index 5c8cc814ac584..4182480b6429b 100644 --- a/api_docs/kbn_ecs_data_quality_dashboard.mdx +++ b/api_docs/kbn_ecs_data_quality_dashboard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ecs-data-quality-dashboard title: "@kbn/ecs-data-quality-dashboard" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ecs-data-quality-dashboard plugin -date: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ecs-data-quality-dashboard'] --- import kbnEcsDataQualityDashboardObj from './kbn_ecs_data_quality_dashboard.devdocs.json'; diff --git a/api_docs/kbn_es.mdx b/api_docs/kbn_es.mdx index fecb21184b8a1..130281480b778 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: 2023-02-27 +date: 2023-03-02 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 784763e15d41d..6432d72fe9933 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: 2023-02-27 +date: 2023-03-02 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 ba77b55460365..da4b187dd9845 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: 2023-02-27 +date: 2023-03-02 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 8234d3212999f..2c7721a298282 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: 2023-02-27 +date: 2023-03-02 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 273601e75d946..5a68fd11c6e47 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: 2023-02-27 +date: 2023-03-02 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 eaa59a2eea258..e08fd251f331f 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/eslint-plugin-imports'] --- import kbnEslintPluginImportsObj from './kbn_eslint_plugin_imports.devdocs.json'; diff --git a/api_docs/kbn_expandable_flyout.devdocs.json b/api_docs/kbn_expandable_flyout.devdocs.json new file mode 100644 index 0000000000000..9658f381fd343 --- /dev/null +++ b/api_docs/kbn_expandable_flyout.devdocs.json @@ -0,0 +1,273 @@ +{ + "id": "@kbn/expandable-flyout", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [ + { + "parentPluginId": "@kbn/expandable-flyout", + "id": "def-common.ExpandableFlyout", + "type": "Function", + "tags": [], + "label": "ExpandableFlyout", + "description": [ + "\nExpandable flyout UI React component.\nDisplays 3 sections (right, left, preview) depending on the panels in the context." + ], + "signature": [ + "{ ({ registeredPanels, handleOnFlyoutClosed, ...flyoutProps }: React.PropsWithChildren<", + { + "pluginId": "@kbn/expandable-flyout", + "scope": "common", + "docId": "kibKbnExpandableFlyoutPluginApi", + "section": "def-common.ExpandableFlyoutProps", + "text": "ExpandableFlyoutProps" + }, + ">): JSX.Element; displayName: string | undefined; }" + ], + "path": "packages/kbn-expandable-flyout/src/index.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/expandable-flyout", + "id": "def-common.ExpandableFlyout.$1", + "type": "CompoundType", + "tags": [], + "label": "{\n registeredPanels,\n handleOnFlyoutClosed,\n ...flyoutProps\n}", + "description": [], + "signature": [ + "React.PropsWithChildren<", + { + "pluginId": "@kbn/expandable-flyout", + "scope": "common", + "docId": "kibKbnExpandableFlyoutPluginApi", + "section": "def-common.ExpandableFlyoutProps", + "text": "ExpandableFlyoutProps" + }, + ">" + ], + "path": "packages/kbn-expandable-flyout/src/index.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/expandable-flyout", + "id": "def-common.ExpandableFlyoutProvider", + "type": "Function", + "tags": [], + "label": "ExpandableFlyoutProvider", + "description": [ + "\nWrap your plugin with this context for the ExpandableFlyout React component." + ], + "signature": [ + "({ children }: ", + "ExpandableFlyoutProviderProps", + ") => JSX.Element" + ], + "path": "packages/kbn-expandable-flyout/src/context.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/expandable-flyout", + "id": "def-common.ExpandableFlyoutProvider.$1", + "type": "Object", + "tags": [], + "label": "{ children }", + "description": [], + "signature": [ + "ExpandableFlyoutProviderProps" + ], + "path": "packages/kbn-expandable-flyout/src/context.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/expandable-flyout", + "id": "def-common.useExpandableFlyoutContext", + "type": "Function", + "tags": [], + "label": "useExpandableFlyoutContext", + "description": [ + "\nRetrieve context's properties" + ], + "signature": [ + "() => ", + "ExpandableFlyoutContext" + ], + "path": "packages/kbn-expandable-flyout/src/context.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [], + "initialIsOpen": false + } + ], + "interfaces": [ + { + "parentPluginId": "@kbn/expandable-flyout", + "id": "def-common.ExpandableFlyoutProps", + "type": "Interface", + "tags": [], + "label": "ExpandableFlyoutProps", + "description": [], + "signature": [ + { + "pluginId": "@kbn/expandable-flyout", + "scope": "common", + "docId": "kibKbnExpandableFlyoutPluginApi", + "section": "def-common.ExpandableFlyoutProps", + "text": "ExpandableFlyoutProps" + }, + " extends ", + "EuiFlyoutProps", + "<\"div\">" + ], + "path": "packages/kbn-expandable-flyout/src/index.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/expandable-flyout", + "id": "def-common.ExpandableFlyoutProps.registeredPanels", + "type": "Array", + "tags": [], + "label": "registeredPanels", + "description": [ + "\nList of all registered panels available for render" + ], + "signature": [ + "Panel", + "[]" + ], + "path": "packages/kbn-expandable-flyout/src/index.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/expandable-flyout", + "id": "def-common.ExpandableFlyoutProps.handleOnFlyoutClosed", + "type": "Function", + "tags": [], + "label": "handleOnFlyoutClosed", + "description": [ + "\nPropagate out EuiFlyout onClose event" + ], + "signature": [ + "(() => void) | undefined" + ], + "path": "packages/kbn-expandable-flyout/src/index.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/expandable-flyout", + "id": "def-common.FlyoutPanel", + "type": "Interface", + "tags": [], + "label": "FlyoutPanel", + "description": [], + "path": "packages/kbn-expandable-flyout/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/expandable-flyout", + "id": "def-common.FlyoutPanel.id", + "type": "string", + "tags": [], + "label": "id", + "description": [ + "\nUnique key to identify the panel" + ], + "path": "packages/kbn-expandable-flyout/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/expandable-flyout", + "id": "def-common.FlyoutPanel.params", + "type": "Object", + "tags": [], + "label": "params", + "description": [ + "\nAny parameters necessary for the initial requests within the flyout" + ], + "signature": [ + "Record | undefined" + ], + "path": "packages/kbn-expandable-flyout/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/expandable-flyout", + "id": "def-common.FlyoutPanel.path", + "type": "Array", + "tags": [], + "label": "path", + "description": [ + "\nTracks the path for what to show in a panel. We may have multiple tabs or details..., so easiest to just use a stack" + ], + "signature": [ + "string[] | undefined" + ], + "path": "packages/kbn-expandable-flyout/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/expandable-flyout", + "id": "def-common.FlyoutPanel.state", + "type": "Object", + "tags": [], + "label": "state", + "description": [ + "\nTracks visual state such as whether the panel is collapsed" + ], + "signature": [ + "Record | undefined" + ], + "path": "packages/kbn-expandable-flyout/src/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ], + "enums": [], + "misc": [], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/kbn_expandable_flyout.mdx b/api_docs/kbn_expandable_flyout.mdx new file mode 100644 index 0000000000000..ab9a929a7a38f --- /dev/null +++ b/api_docs/kbn_expandable_flyout.mdx @@ -0,0 +1,33 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibKbnExpandableFlyoutPluginApi +slug: /kibana-dev-docs/api/kbn-expandable-flyout +title: "@kbn/expandable-flyout" +image: https://source.unsplash.com/400x175/?github +description: API docs for the @kbn/expandable-flyout plugin +date: 2023-03-02 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/expandable-flyout'] +--- +import kbnExpandableFlyoutObj from './kbn_expandable_flyout.devdocs.json'; + + + +Contact [@elastic/security-threat-hunting-investigations](https://github.com/orgs/elastic/teams/security-threat-hunting-investigations) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 13 | 0 | 4 | 3 | + +## Common + +### Functions + + +### Interfaces + + diff --git a/api_docs/kbn_field_types.mdx b/api_docs/kbn_field_types.mdx index 0287d21eb53df..ef0856fc946b1 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: 2023-02-27 +date: 2023-03-02 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 8d3dbec5ef1d4..449cae1f26ea8 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: 2023-02-27 +date: 2023-03-02 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 17c629d017633..91d09b17dd4ec 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: 2023-02-27 +date: 2023-03-02 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 aee0d8aa6d524..65adba923f05f 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate'] --- import kbnGenerateObj from './kbn_generate.devdocs.json'; diff --git a/api_docs/kbn_guided_onboarding.mdx b/api_docs/kbn_guided_onboarding.mdx index 27c485203f114..880e6de629200 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/guided-onboarding'] --- import kbnGuidedOnboardingObj from './kbn_guided_onboarding.devdocs.json'; diff --git a/api_docs/kbn_handlebars.devdocs.json b/api_docs/kbn_handlebars.devdocs.json index 0c3d26cf1c175..84069ecfa5b9c 100644 --- a/api_docs/kbn_handlebars.devdocs.json +++ b/api_docs/kbn_handlebars.devdocs.json @@ -420,7 +420,7 @@ "\nSupported Handlebars compile options.\n\nThis is a subset of all the compile options supported by the upstream\nHandlebars module." ], "signature": [ - "{ data?: boolean | undefined; strict?: boolean | undefined; knownHelpers?: KnownHelpers | undefined; knownHelpersOnly?: boolean | undefined; noEscape?: boolean | undefined; assumeObjects?: boolean | undefined; preventIndent?: boolean | undefined; explicitPartialContext?: boolean | undefined; }" + "{ strict?: boolean | undefined; data?: boolean | undefined; knownHelpers?: KnownHelpers | undefined; knownHelpersOnly?: boolean | undefined; noEscape?: boolean | undefined; assumeObjects?: boolean | undefined; preventIndent?: boolean | undefined; explicitPartialContext?: boolean | undefined; }" ], "path": "packages/kbn-handlebars/src/types.ts", "deprecated": false, diff --git a/api_docs/kbn_handlebars.mdx b/api_docs/kbn_handlebars.mdx index adb6e8aa995fd..5878786295778 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: 2023-02-27 +date: 2023-03-02 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 508e8edcf7812..9425661e63606 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: 2023-02-27 +date: 2023-03-02 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 65fe99bcf0fd6..888f6ded8526c 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: 2023-02-27 +date: 2023-03-02 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 cb682ae4f9900..c22f3d87fbbed 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: 2023-02-27 +date: 2023-03-02 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 9d5c6575fdd70..a7c40f3fe82b6 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: 2023-02-27 +date: 2023-03-02 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 093991e202a75..86ea86435e56b 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: 2023-02-27 +date: 2023-03-02 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 f3147c2dfe769..117c6131cde4a 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: 2023-02-27 +date: 2023-03-02 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 021576539dda5..1319962df3f2b 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: 2023-02-27 +date: 2023-03-02 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 9a26721488b93..23a48bca32834 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: 2023-02-27 +date: 2023-03-02 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 2f4a023579df9..413243d232272 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: 2023-02-27 +date: 2023-03-02 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 a3c25d75910b4..55386dee3d910 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: 2023-02-27 +date: 2023-03-02 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 78c93dc47e995..ca19c050cf54f 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/journeys'] --- import kbnJourneysObj from './kbn_journeys.devdocs.json'; diff --git a/api_docs/kbn_json_ast.mdx b/api_docs/kbn_json_ast.mdx index 23162fdbcb8a0..9172eb25ebd57 100644 --- a/api_docs/kbn_json_ast.mdx +++ b/api_docs/kbn_json_ast.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-json-ast title: "@kbn/json-ast" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/json-ast plugin -date: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/json-ast'] --- import kbnJsonAstObj from './kbn_json_ast.devdocs.json'; diff --git a/api_docs/kbn_kibana_manifest_schema.mdx b/api_docs/kbn_kibana_manifest_schema.mdx index 7ab510e97ac36..31cf48b2c964a 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: 2023-02-27 +date: 2023-03-02 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 5a4aac9574156..f0205840574a5 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: 2023-02-27 +date: 2023-03-02 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 406cfb327294e..d019150acefd2 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: 2023-02-27 +date: 2023-03-02 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 8aa1631ce53a0..a4e1e123ef2a3 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: 2023-02-27 +date: 2023-03-02 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 c13e84f344705..cf68bcaa4b420 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: 2023-02-27 +date: 2023-03-02 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 53359db3b93a4..50d3deb0458c8 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: 2023-02-27 +date: 2023-03-02 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 8bb33f2663dc2..3127057ccd8c4 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: 2023-02-27 +date: 2023-03-02 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_date_picker.mdx b/api_docs/kbn_ml_date_picker.mdx index 2f2cd5efe78bc..70aa629bdf80b 100644 --- a/api_docs/kbn_ml_date_picker.mdx +++ b/api_docs/kbn_ml_date_picker.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-date-picker title: "@kbn/ml-date-picker" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-date-picker plugin -date: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-date-picker'] --- import kbnMlDatePickerObj from './kbn_ml_date_picker.devdocs.json'; diff --git a/api_docs/kbn_ml_is_defined.mdx b/api_docs/kbn_ml_is_defined.mdx index ebd371fa39360..9ef0f2503787d 100644 --- a/api_docs/kbn_ml_is_defined.mdx +++ b/api_docs/kbn_ml_is_defined.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-is-defined title: "@kbn/ml-is-defined" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-is-defined plugin -date: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-is-defined'] --- import kbnMlIsDefinedObj from './kbn_ml_is_defined.devdocs.json'; diff --git a/api_docs/kbn_ml_is_populated_object.mdx b/api_docs/kbn_ml_is_populated_object.mdx index 1d634f80ec520..36b9c12cf8d9d 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: 2023-02-27 +date: 2023-03-02 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_local_storage.mdx b/api_docs/kbn_ml_local_storage.mdx index 1691190226d46..8fa8dfb1d8949 100644 --- a/api_docs/kbn_ml_local_storage.mdx +++ b/api_docs/kbn_ml_local_storage.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-local-storage title: "@kbn/ml-local-storage" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-local-storage plugin -date: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-local-storage'] --- import kbnMlLocalStorageObj from './kbn_ml_local_storage.devdocs.json'; diff --git a/api_docs/kbn_ml_nested_property.mdx b/api_docs/kbn_ml_nested_property.mdx index bcee4b75b6c2a..408295f5ee767 100644 --- a/api_docs/kbn_ml_nested_property.mdx +++ b/api_docs/kbn_ml_nested_property.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-nested-property title: "@kbn/ml-nested-property" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-nested-property plugin -date: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-nested-property'] --- import kbnMlNestedPropertyObj from './kbn_ml_nested_property.devdocs.json'; diff --git a/api_docs/kbn_ml_query_utils.mdx b/api_docs/kbn_ml_query_utils.mdx index 09ed1ad02393c..81bd4a60e0987 100644 --- a/api_docs/kbn_ml_query_utils.mdx +++ b/api_docs/kbn_ml_query_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-query-utils title: "@kbn/ml-query-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-query-utils plugin -date: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-query-utils'] --- import kbnMlQueryUtilsObj from './kbn_ml_query_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_string_hash.mdx b/api_docs/kbn_ml_string_hash.mdx index f9c4d29869a7f..9507eea80b252 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-string-hash'] --- import kbnMlStringHashObj from './kbn_ml_string_hash.devdocs.json'; diff --git a/api_docs/kbn_ml_url_state.mdx b/api_docs/kbn_ml_url_state.mdx index d804c533e1b09..7f2f96360ee86 100644 --- a/api_docs/kbn_ml_url_state.mdx +++ b/api_docs/kbn_ml_url_state.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-url-state title: "@kbn/ml-url-state" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-url-state plugin -date: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-url-state'] --- import kbnMlUrlStateObj from './kbn_ml_url_state.devdocs.json'; diff --git a/api_docs/kbn_monaco.mdx b/api_docs/kbn_monaco.mdx index 3638a55e2ad02..438c5d5888eb9 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/monaco'] --- import kbnMonacoObj from './kbn_monaco.devdocs.json'; diff --git a/api_docs/kbn_optimizer.mdx b/api_docs/kbn_optimizer.mdx index 8f216afa89398..317ff8207a0c7 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: 2023-02-27 +date: 2023-03-02 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 5de6feece7499..421a949c6eae8 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: 2023-02-27 +date: 2023-03-02 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 1701080c152a4..7f3b1642e85d7 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/osquery-io-ts-types'] --- import kbnOsqueryIoTsTypesObj from './kbn_osquery_io_ts_types.devdocs.json'; diff --git a/api_docs/kbn_performance_testing_dataset_extractor.mdx b/api_docs/kbn_performance_testing_dataset_extractor.mdx index c89cd61bfdc71..bf6e073992a21 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: 2023-02-27 +date: 2023-03-02 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 db2b66505446a..0778f126ccf81 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: 2023-02-27 +date: 2023-03-02 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 3f547dc9a9278..4cab2f5a9cf67 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: 2023-02-27 +date: 2023-03-02 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 ab6126edfa1d0..4f57a93db4a55 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-field'] --- import kbnReactFieldObj from './kbn_react_field.devdocs.json'; diff --git a/api_docs/kbn_repo_file_maps.mdx b/api_docs/kbn_repo_file_maps.mdx index 2ce4e25fe73dd..8ffc7c7df9420 100644 --- a/api_docs/kbn_repo_file_maps.mdx +++ b/api_docs/kbn_repo_file_maps.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-file-maps title: "@kbn/repo-file-maps" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-file-maps plugin -date: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-file-maps'] --- import kbnRepoFileMapsObj from './kbn_repo_file_maps.devdocs.json'; diff --git a/api_docs/kbn_repo_linter.mdx b/api_docs/kbn_repo_linter.mdx index 27117caad1c2a..91d1a67494952 100644 --- a/api_docs/kbn_repo_linter.mdx +++ b/api_docs/kbn_repo_linter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-linter title: "@kbn/repo-linter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-linter plugin -date: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-linter'] --- import kbnRepoLinterObj from './kbn_repo_linter.devdocs.json'; diff --git a/api_docs/kbn_repo_path.mdx b/api_docs/kbn_repo_path.mdx index 07dcc95444a77..c28da4d0951a8 100644 --- a/api_docs/kbn_repo_path.mdx +++ b/api_docs/kbn_repo_path.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-path title: "@kbn/repo-path" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-path plugin -date: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-path'] --- import kbnRepoPathObj from './kbn_repo_path.devdocs.json'; diff --git a/api_docs/kbn_repo_source_classifier.mdx b/api_docs/kbn_repo_source_classifier.mdx index da2e98c4fb0fa..72a1e859e649e 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: 2023-02-27 +date: 2023-03-02 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 1656d07cb758c..31f656256f6e1 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: 2023-02-27 +date: 2023-03-02 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 51429bd28d4a4..0b4f41adabbdc 100644 --- a/api_docs/kbn_rule_data_utils.devdocs.json +++ b/api_docs/kbn_rule_data_utils.devdocs.json @@ -218,7 +218,7 @@ "signature": [ "\"kibana.alert.case_ids\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/default_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -300,13 +300,13 @@ }, { "parentPluginId": "@kbn/rule-data-utils", - "id": "def-common.ALERT_ID", + "id": "def-common.ALERT_FLAPPING_HISTORY", "type": "string", "tags": [], - "label": "ALERT_ID", + "label": "ALERT_FLAPPING_HISTORY", "description": [], "signature": [ - "\"kibana.alert.id\"" + "\"kibana.alert.flapping_history\"" ], "path": "packages/kbn-rule-data-utils/src/default_alerts_as_data.ts", "deprecated": false, @@ -323,7 +323,7 @@ "signature": [ "\"kibana.alert.instance.id\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/default_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -383,7 +383,7 @@ "signature": [ "\"kibana.alert.risk_score\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -398,7 +398,7 @@ "signature": [ "\"kibana.alert.rule.author\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -443,7 +443,7 @@ "signature": [ "\"kibana.alert.rule.created_at\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -458,7 +458,7 @@ "signature": [ "\"kibana.alert.rule.created_by\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -473,7 +473,7 @@ "signature": [ "\"kibana.alert.rule.description\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -488,7 +488,7 @@ "signature": [ "\"kibana.alert.rule.enabled\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -533,7 +533,7 @@ "signature": [ "\"kibana.alert.rule.from\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -548,7 +548,7 @@ "signature": [ "\"kibana.alert.rule.interval\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -563,7 +563,7 @@ "signature": [ "\"kibana.alert.rule.license\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -623,7 +623,7 @@ "signature": [ "\"kibana.alert.rule.note\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -668,7 +668,7 @@ "signature": [ "\"kibana.alert.rule.references\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -683,7 +683,7 @@ "signature": [ "\"kibana.alert.rule.rule_id\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -698,7 +698,7 @@ "signature": [ "\"kibana.alert.rule.rule_name_override\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -728,7 +728,7 @@ "signature": [ "\"kibana.alert.rule.to\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -743,7 +743,7 @@ "signature": [ "\"kibana.alert.rule.type\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -773,7 +773,7 @@ "signature": [ "\"kibana.alert.rule.updated_at\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -788,7 +788,7 @@ "signature": [ "\"kibana.alert.rule.updated_by\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -818,7 +818,7 @@ "signature": [ "\"kibana.alert.rule.version\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -833,7 +833,7 @@ "signature": [ "\"kibana.alert.severity\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -938,7 +938,7 @@ "signature": [ "\"kibana.alert.suppression.docs_count\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -953,7 +953,7 @@ "signature": [ "\"kibana.alert.suppression.end\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -968,7 +968,7 @@ "signature": [ "\"kibana.alert.suppression.terms.field\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -983,7 +983,7 @@ "signature": [ "\"kibana.alert.suppression.start\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -998,7 +998,7 @@ "signature": [ "\"kibana.alert.suppression.terms\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1013,7 +1013,7 @@ "signature": [ "\"kibana.alert.suppression.terms.value\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1028,7 +1028,7 @@ "signature": [ "\"kibana.alert.system_status\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1223,7 +1223,7 @@ "signature": [ "\"kibana.alert.workflow_reason\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1253,7 +1253,7 @@ "signature": [ "\"kibana.alert.workflow_user\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1311,7 +1311,7 @@ "label": "DefaultAlertFieldName", "description": [], "signature": [ - "\"kibana\" | \"kibana.alert.rule.rule_type_id\" | \"kibana.alert.rule.consumer\" | \"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.start\" | \"kibana.alert.time_range\" | \"kibana.alert.end\" | \"kibana.alert.duration.us\" | \"kibana.alert.status\" | \"kibana.alert.flapping\" | \"kibana.version\" | \"kibana.alert.workflow_status\" | \"kibana.alert.action_group\" | \"kibana.alert.reason\" | \"kibana.alert.rule.category\" | \"kibana.alert.rule.uuid\" | \"kibana.alert.rule.name\" | \"kibana.alert.rule.tags\" | \"kibana.alert.last_detected\" | \"kibana.alert.id\"" + "\"@timestamp\" | \"kibana\" | \"kibana.alert.rule.rule_type_id\" | \"kibana.alert.rule.consumer\" | \"kibana.alert.rule.execution.uuid\" | \"kibana.alert\" | \"kibana.alert.action_group\" | \"kibana.alert.case_ids\" | \"kibana.alert.duration.us\" | \"kibana.alert.end\" | \"kibana.alert.flapping\" | \"kibana.alert.flapping_history\" | \"kibana.alert.instance.id\" | \"kibana.alert.last_detected\" | \"kibana.alert.reason\" | \"kibana.alert.rule\" | \"kibana.alert.rule.category\" | \"kibana.alert.rule.name\" | \"kibana.alert.rule.parameters\" | \"kibana.alert.rule.producer\" | \"kibana.alert.rule.tags\" | \"kibana.alert.rule.uuid\" | \"kibana.alert.start\" | \"kibana.alert.status\" | \"kibana.alert.time_range\" | \"kibana.alert.uuid\" | \"kibana.alert.workflow_status\" | \"kibana.space_ids\" | \"kibana.version\"" ], "path": "packages/kbn-rule-data-utils/src/default_alerts_as_data.ts", "deprecated": false, @@ -1328,7 +1328,7 @@ "signature": [ "\"ecs.version\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1343,7 +1343,7 @@ "signature": [ "\"event.action\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1358,7 +1358,7 @@ "signature": [ "\"event.kind\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1463,7 +1463,7 @@ "signature": [ "\"tags\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1476,7 +1476,7 @@ "label": "TechnicalRuleDataFieldName", "description": [], "signature": [ - "\"@timestamp\" | \"event.action\" | \"tags\" | \"kibana\" | \"kibana.alert.rule.rule_type_id\" | \"kibana.alert.rule.consumer\" | \"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.case_ids\" | \"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\"" + "\"@timestamp\" | \"event.action\" | \"tags\" | \"kibana\" | \"kibana.alert.rule.rule_type_id\" | \"kibana.alert.rule.consumer\" | \"kibana.alert.rule.execution.uuid\" | \"kibana.alert\" | \"kibana.alert.action_group\" | \"kibana.alert.case_ids\" | \"kibana.alert.duration.us\" | \"kibana.alert.end\" | \"kibana.alert.flapping\" | \"kibana.alert.instance.id\" | \"kibana.alert.reason\" | \"kibana.alert.rule\" | \"kibana.alert.rule.category\" | \"kibana.alert.rule.name\" | \"kibana.alert.rule.parameters\" | \"kibana.alert.rule.producer\" | \"kibana.alert.rule.tags\" | \"kibana.alert.rule.uuid\" | \"kibana.alert.start\" | \"kibana.alert.status\" | \"kibana.alert.time_range\" | \"kibana.alert.uuid\" | \"kibana.alert.workflow_status\" | \"kibana.space_ids\" | \"kibana.version\" | \"kibana.alert.risk_score\" | \"kibana.alert.rule.author\" | \"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.note\" | \"kibana.alert.rule.references\" | \"kibana.alert.rule.rule_id\" | \"kibana.alert.rule.rule_name_override\" | \"kibana.alert.rule.to\" | \"kibana.alert.rule.type\" | \"kibana.alert.rule.updated_at\" | \"kibana.alert.rule.updated_by\" | \"kibana.alert.rule.version\" | \"kibana.alert.severity\" | \"kibana.alert.suppression.docs_count\" | \"kibana.alert.suppression.end\" | \"kibana.alert.suppression.terms\" | \"kibana.alert.suppression.terms.field\" | \"kibana.alert.suppression.start\" | \"kibana.alert.suppression.terms.value\" | \"kibana.alert.system_status\" | \"kibana.alert.workflow_reason\" | \"kibana.alert.workflow_user\" | \"ecs.version\" | \"event.kind\" | \"kibana.alert.evaluation.threshold\" | \"kibana.alert.evaluation.value\" | \"event.module\" | \"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, @@ -1493,7 +1493,7 @@ "signature": [ "\"@timestamp\"" ], - "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "path": "packages/kbn-rule-data-utils/src/default_alerts_as_data.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false diff --git a/api_docs/kbn_rule_data_utils.mdx b/api_docs/kbn_rule_data_utils.mdx index 24bbcf7806d47..a7d43f07fa3d2 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: 2023-02-27 +date: 2023-03-02 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 280afbc3028e4..9b4a4d1967d67 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-autocomplete'] --- import kbnSecuritysolutionAutocompleteObj from './kbn_securitysolution_autocomplete.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_ecs.mdx b/api_docs/kbn_securitysolution_ecs.mdx index b52227497982a..4265f52b32192 100644 --- a/api_docs/kbn_securitysolution_ecs.mdx +++ b/api_docs/kbn_securitysolution_ecs.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-ecs title: "@kbn/securitysolution-ecs" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-ecs plugin -date: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-ecs'] --- import kbnSecuritysolutionEcsObj from './kbn_securitysolution_ecs.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_es_utils.mdx b/api_docs/kbn_securitysolution_es_utils.mdx index 5e2dbdeb7c034..bdbe6ca5f7874 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: 2023-02-27 +date: 2023-03-02 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 d610a4cd3eb85..d1aba2986d80b 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: 2023-02-27 +date: 2023-03-02 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 50ca1b14e44a3..cf9e283e2ce9d 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: 2023-02-27 +date: 2023-03-02 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 dcb989b2881ad..3cea3ef0fe53b 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: 2023-02-27 +date: 2023-03-02 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 35b7b45d54e6f..ae89b69f48690 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: 2023-02-27 +date: 2023-03-02 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 085842ffd707e..d1526b7f60044 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: 2023-02-27 +date: 2023-03-02 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 7e355cc10b8dc..c2ecb1dbe34d5 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: 2023-02-27 +date: 2023-03-02 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 cd458e3bd8070..6788608bcb84e 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-api'] --- import kbnSecuritysolutionListApiObj from './kbn_securitysolution_list_api.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_constants.mdx b/api_docs/kbn_securitysolution_list_constants.mdx index 27628ddca800f..2ec3f8381831f 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: 2023-02-27 +date: 2023-03-02 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 b33fa79e75be6..0ca59b9fe847c 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: 2023-02-27 +date: 2023-03-02 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 85723f920c3b1..3beb7403b600b 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: 2023-02-27 +date: 2023-03-02 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 8d1de91435814..0ee158f6baaf6 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: 2023-02-27 +date: 2023-03-02 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 58f76d4772be0..a2d1484550fc9 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: 2023-02-27 +date: 2023-03-02 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 919c81d834263..a8005a4385573 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: 2023-02-27 +date: 2023-03-02 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 e069f4f2a258f..063dd41fd98ff 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: 2023-02-27 +date: 2023-03-02 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 c1ea8d74f6da6..737057e8c0e05 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: 2023-02-27 +date: 2023-03-02 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 4673569b1f4e1..b29158d647065 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: 2023-02-27 +date: 2023-03-02 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 bc065cf2a4cba..a3a55240b0af9 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: 2023-02-27 +date: 2023-03-02 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 d553b227b1143..a56cf6c0bfbc3 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: 2023-02-27 +date: 2023-03-02 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 4a39bf9fb3c9e..6819d2c072e27 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: 2023-02-27 +date: 2023-03-02 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 c7deece2ea113..8ab84fc256c3c 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: 2023-02-27 +date: 2023-03-02 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 fa35a43514eeb..95898936e597e 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: 2023-02-27 +date: 2023-03-02 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 3586f053ac47f..7c89c2313936f 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: 2023-02-27 +date: 2023-03-02 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 2e1bffe344d14..409920c862e2f 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: 2023-02-27 +date: 2023-03-02 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.devdocs.json b/api_docs/kbn_shared_ux_file_context.devdocs.json index 02025b778807c..a48e1addf9ede 100644 --- a/api_docs/kbn_shared_ux_file_context.devdocs.json +++ b/api_docs/kbn_shared_ux_file_context.devdocs.json @@ -99,7 +99,13 @@ "\nA files client that will be used process uploads." ], "signature": [ - "BaseFilesClient", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.BaseFilesClient", + "text": "BaseFilesClient" + }, "" ], "path": "packages/shared-ux/file/context/src/index.tsx", diff --git a/api_docs/kbn_shared_ux_file_context.mdx b/api_docs/kbn_shared_ux_file_context.mdx index bae037be686b5..c2e953437d99f 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: 2023-02-27 +date: 2023-03-02 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.devdocs.json b/api_docs/kbn_shared_ux_file_image.devdocs.json index 5d3350f6cf444..4dfade1406167 100644 --- a/api_docs/kbn_shared_ux_file_image.devdocs.json +++ b/api_docs/kbn_shared_ux_file_image.devdocs.json @@ -83,7 +83,13 @@ "description": [], "signature": [ "{ meta?: ", - "FileImageMetadata", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileImageMetadata", + "text": "FileImageMetadata" + }, " | undefined; } & ", "EuiImageProps" ], diff --git a/api_docs/kbn_shared_ux_file_image.mdx b/api_docs/kbn_shared_ux_file_image.mdx index 4dffa9c1aef61..5d7124ba4df40 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: 2023-02-27 +date: 2023-03-02 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 69bfe48a537c0..349b4336f58e2 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: 2023-02-27 +date: 2023-03-02 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.devdocs.json b/api_docs/kbn_shared_ux_file_mocks.devdocs.json index ecdb910452dba..1a16eeed01e56 100644 --- a/api_docs/kbn_shared_ux_file_mocks.devdocs.json +++ b/api_docs/kbn_shared_ux_file_mocks.devdocs.json @@ -36,7 +36,13 @@ "text": "DeeplyMockedKeys" }, "<", - "BaseFilesClient", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.BaseFilesClient", + "text": "BaseFilesClient" + }, ">" ], "path": "packages/shared-ux/file/mocks/index.ts", diff --git a/api_docs/kbn_shared_ux_file_mocks.mdx b/api_docs/kbn_shared_ux_file_mocks.mdx index 6ffefddd67b1e..e0989c1f51cec 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-mocks'] --- import kbnSharedUxFileMocksObj from './kbn_shared_ux_file_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_picker.devdocs.json b/api_docs/kbn_shared_ux_file_picker.devdocs.json index 0838853073257..1ecf0fc909e4e 100644 --- a/api_docs/kbn_shared_ux_file_picker.devdocs.json +++ b/api_docs/kbn_shared_ux_file_picker.devdocs.json @@ -117,7 +117,13 @@ ], "signature": [ "((file: ", - "FileJSON", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, ") => boolean) | undefined" ], "path": "packages/shared-ux/file/file_picker/impl/src/file_picker.tsx", @@ -132,7 +138,13 @@ "label": "file", "description": [], "signature": [ - "FileJSON", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, "" ], "path": "packages/shared-ux/file/file_picker/impl/src/file_picker.tsx", @@ -172,7 +184,13 @@ ], "signature": [ "(files: ", - "FileJSON", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, "[]) => void" ], "path": "packages/shared-ux/file/file_picker/impl/src/file_picker.tsx", @@ -187,7 +205,13 @@ "label": "files", "description": [], "signature": [ - "FileJSON", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, "[]" ], "path": "packages/shared-ux/file/file_picker/impl/src/file_picker.tsx", diff --git a/api_docs/kbn_shared_ux_file_picker.mdx b/api_docs/kbn_shared_ux_file_picker.mdx index c84800f82883a..8d407169060fc 100644 --- a/api_docs/kbn_shared_ux_file_picker.mdx +++ b/api_docs/kbn_shared_ux_file_picker.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-picker title: "@kbn/shared-ux-file-picker" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-picker plugin -date: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-picker'] --- import kbnSharedUxFilePickerObj from './kbn_shared_ux_file_picker.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_types.devdocs.json b/api_docs/kbn_shared_ux_file_types.devdocs.json new file mode 100644 index 0000000000000..de6c162159d55 --- /dev/null +++ b/api_docs/kbn_shared_ux_file_types.devdocs.json @@ -0,0 +1,1581 @@ +{ + "id": "@kbn/shared-ux-file-types", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.Abortable", + "type": "Interface", + "tags": [], + "label": "Abortable", + "description": [], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.Abortable.abortSignal", + "type": "Object", + "tags": [], + "label": "abortSignal", + "description": [], + "signature": [ + "AbortSignal | undefined" + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient", + "type": "Interface", + "tags": [], + "label": "BaseFilesClient", + "description": [], + "signature": [ + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.BaseFilesClient", + "text": "BaseFilesClient" + }, + "" + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.find", + "type": "Function", + "tags": [], + "label": "find", + "description": [ + "\nFind a set of files given some filters.\n" + ], + "signature": [ + "(args: { kind?: string | string[] | undefined; status?: string | string[] | undefined; extension?: string | string[] | undefined; name?: string | string[] | undefined; meta?: M | undefined; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Pagination", + "text": "Pagination" + }, + " & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + }, + ") => Promise<{ files: ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, + "[]; total: number; }>" + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.find.$1", + "type": "CompoundType", + "tags": [], + "label": "args", + "description": [ + "- File filters" + ], + "signature": [ + "{ kind?: string | string[] | undefined; status?: string | string[] | undefined; extension?: string | string[] | undefined; name?: string | string[] | undefined; meta?: M | undefined; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Pagination", + "text": "Pagination" + }, + " & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + } + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.bulkDelete", + "type": "Function", + "tags": [], + "label": "bulkDelete", + "description": [ + "\nBulk a delete a set of files given their IDs.\n" + ], + "signature": [ + "(args: { ids: string[]; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + }, + ") => Promise<{ succeeded: string[]; failed?: [id: string, reason: string][] | undefined; }>" + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.bulkDelete.$1", + "type": "CompoundType", + "tags": [], + "label": "args", + "description": [ + "- Bulk delete args" + ], + "signature": [ + "{ ids: string[]; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + } + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.create", + "type": "Function", + "tags": [], + "label": "create", + "description": [ + "\nCreate a new file object with the provided metadata.\n" + ], + "signature": [ + "(args: { name: string; meta?: M | undefined; alt?: string | undefined; mimeType?: string | undefined; kind: string; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + }, + ") => Promise<{ file: ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, + "; }>" + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.create.$1", + "type": "CompoundType", + "tags": [], + "label": "args", + "description": [ + "- create file args" + ], + "signature": [ + "{ name: string; meta?: M | undefined; alt?: string | undefined; mimeType?: string | undefined; kind: string; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + } + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.delete", + "type": "Function", + "tags": [], + "label": "delete", + "description": [ + "\nDelete a file object and all associated share and content objects.\n" + ], + "signature": [ + "(args: { id: string; kind: string; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + }, + ") => Promise<{ ok: true; }>" + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.delete.$1", + "type": "CompoundType", + "tags": [], + "label": "args", + "description": [ + "- delete file args" + ], + "signature": [ + "{ id: string; kind: string; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + } + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.getById", + "type": "Function", + "tags": [], + "label": "getById", + "description": [ + "\nGet a file object by ID.\n" + ], + "signature": [ + "(args: { id: string; kind: string; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + }, + ") => Promise<{ file: ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, + "; }>" + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.getById.$1", + "type": "CompoundType", + "tags": [], + "label": "args", + "description": [ + "- get file by ID args" + ], + "signature": [ + "{ id: string; kind: string; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + } + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.list", + "type": "Function", + "tags": [], + "label": "list", + "description": [ + "\nList all file objects, of a given {@link FileKindBrowser}.\n" + ], + "signature": [ + "(args: { kind: string; status?: string | string[] | undefined; extension?: string | string[] | undefined; name?: string | string[] | undefined; meta?: M | undefined; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Pagination", + "text": "Pagination" + }, + " & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + }, + ") => Promise<{ files: ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, + "[]; total: number; }>" + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.list.$1", + "type": "CompoundType", + "tags": [], + "label": "args", + "description": [ + "- list files args" + ], + "signature": [ + "{ kind: string; status?: string | string[] | undefined; extension?: string | string[] | undefined; name?: string | string[] | undefined; meta?: M | undefined; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Pagination", + "text": "Pagination" + }, + " & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + } + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.update", + "type": "Function", + "tags": [], + "label": "update", + "description": [ + "\nUpdate a set of of metadata values of the file object.\n" + ], + "signature": [ + "(args: { id: string; kind: string; name?: string | undefined; meta?: M | undefined; alt?: string | undefined; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + }, + ") => Promise<{ file: ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, + "; }>" + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.update.$1", + "type": "CompoundType", + "tags": [], + "label": "args", + "description": [ + "- update file args" + ], + "signature": [ + "{ id: string; kind: string; name?: string | undefined; meta?: M | undefined; alt?: string | undefined; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + } + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.upload", + "type": "Function", + "tags": [], + "label": "upload", + "description": [ + "\nStream the contents of the file to Kibana server for storage.\n" + ], + "signature": [ + "(args: { id: string; body: unknown; kind: string; abortSignal?: AbortSignal | undefined; contentType?: string | undefined; selfDestructOnAbort?: boolean | undefined; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + }, + ") => Promise<{ ok: true; size: number; }>" + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.upload.$1", + "type": "CompoundType", + "tags": [], + "label": "args", + "description": [ + "- upload file args" + ], + "signature": [ + "{ id: string; body: unknown; kind: string; abortSignal?: AbortSignal | undefined; contentType?: string | undefined; selfDestructOnAbort?: boolean | undefined; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + } + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.download", + "type": "Function", + "tags": [], + "label": "download", + "description": [ + "\nStream a download of the file object's content.\n" + ], + "signature": [ + "(args: { fileName?: string | undefined; id: string; kind: string; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + }, + ") => Promise" + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.download.$1", + "type": "CompoundType", + "tags": [], + "label": "args", + "description": [ + "- download file args" + ], + "signature": [ + "{ fileName?: string | undefined; id: string; kind: string; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + } + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.getDownloadHref", + "type": "Function", + "tags": [], + "label": "getDownloadHref", + "description": [ + "\nGet a string for downloading a file that can be passed to a button element's\nhref for download.\n" + ], + "signature": [ + "(args: Pick<", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, + ", \"id\" | \"fileKind\">) => string" + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.getDownloadHref.$1", + "type": "Object", + "tags": [], + "label": "args", + "description": [ + "- get download URL args" + ], + "signature": [ + "Pick<", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, + ", \"id\" | \"fileKind\">" + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.share", + "type": "Function", + "tags": [ + "note" + ], + "label": "share", + "description": [ + "\nShare a file by creating a new file share instance.\n" + ], + "signature": [ + "(args: { name?: string | undefined; validUntil?: number | undefined; fileId: string; kind: string; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + }, + ") => Promise<", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileShareJSONWithToken", + "text": "FileShareJSONWithToken" + }, + ">" + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.share.$1", + "type": "CompoundType", + "tags": [], + "label": "args", + "description": [ + "- File share arguments" + ], + "signature": [ + "{ name?: string | undefined; validUntil?: number | undefined; fileId: string; kind: string; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + } + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.unshare", + "type": "Function", + "tags": [], + "label": "unshare", + "description": [ + "\nDelete a file share instance.\n" + ], + "signature": [ + "(args: { id: string; kind: string; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + }, + ") => Promise<{ ok: true; }>" + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.unshare.$1", + "type": "CompoundType", + "tags": [], + "label": "args", + "description": [ + "- File unshare arguments" + ], + "signature": [ + "{ id: string; kind: string; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + } + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.getShare", + "type": "Function", + "tags": [], + "label": "getShare", + "description": [ + "\nGet a file share instance.\n" + ], + "signature": [ + "(args: { id: string; kind: string; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + }, + ") => Promise<{ share: ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileShareJSON", + "text": "FileShareJSON" + }, + "; }>" + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.getShare.$1", + "type": "CompoundType", + "tags": [], + "label": "args", + "description": [ + "- Get file share arguments" + ], + "signature": [ + "{ id: string; kind: string; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + } + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.listShares", + "type": "Function", + "tags": [], + "label": "listShares", + "description": [ + "\nList all file shares. Optionally scoping to a specific\nfile.\n" + ], + "signature": [ + "(args: { forFileId?: string | undefined; kind: string; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Pagination", + "text": "Pagination" + }, + " & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + }, + ") => Promise<{ shares: ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileShareJSON", + "text": "FileShareJSON" + }, + "[]; }>" + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.listShares.$1", + "type": "CompoundType", + "tags": [], + "label": "args", + "description": [ + "- Get file share arguments" + ], + "signature": [ + "{ forFileId?: string | undefined; kind: string; } & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Pagination", + "text": "Pagination" + }, + " & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.Abortable", + "text": "Abortable" + } + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.getFileKind", + "type": "Function", + "tags": [], + "label": "getFileKind", + "description": [ + "\nGet a file kind" + ], + "signature": [ + "(id: string) => ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileKindBase", + "text": "FileKindBase" + } + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFilesClient.getFileKind.$1", + "type": "string", + "tags": [], + "label": "id", + "description": [ + "The id of the file kind" + ], + "signature": [ + "string" + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileImageMetadata", + "type": "Interface", + "tags": [], + "label": "FileImageMetadata", + "description": [ + "\nSet of metadata captured for every image uploaded via the file services'\npublic components." + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileImageMetadata.blurhash", + "type": "string", + "tags": [], + "label": "blurhash", + "description": [ + "\nThe blurhash that can be displayed while the image is loading" + ], + "signature": [ + "string | undefined" + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileImageMetadata.width", + "type": "number", + "tags": [], + "label": "width", + "description": [ + "\nWidth, in px, of the original image" + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileImageMetadata.height", + "type": "number", + "tags": [], + "label": "height", + "description": [ + "\nHeight, in px, of the original image" + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileJSON", + "type": "Interface", + "tags": [], + "label": "FileJSON", + "description": [ + "\nAttributes of a file that represent a serialised version of the file." + ], + "signature": [ + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, + "" + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileJSON.id", + "type": "string", + "tags": [], + "label": "id", + "description": [ + "\nUnique file ID." + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileJSON.created", + "type": "string", + "tags": [], + "label": "created", + "description": [ + "\nISO string of when this file was created" + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileJSON.updated", + "type": "string", + "tags": [], + "label": "updated", + "description": [ + "\nISO string of when the file was updated" + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileJSON.name", + "type": "string", + "tags": [ + "note" + ], + "label": "name", + "description": [ + "\nFile name.\n" + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileJSON.mimeType", + "type": "string", + "tags": [], + "label": "mimeType", + "description": [ + "\nMIME type of the file's contents." + ], + "signature": [ + "string | undefined" + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileJSON.size", + "type": "number", + "tags": [], + "label": "size", + "description": [ + "\nThe size, in bytes, of the file content." + ], + "signature": [ + "number | undefined" + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileJSON.extension", + "type": "string", + "tags": [ + "note" + ], + "label": "extension", + "description": [ + "\nThe file extension (dot suffix).\n" + ], + "signature": [ + "string | undefined" + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileJSON.meta", + "type": "Uncategorized", + "tags": [], + "label": "meta", + "description": [ + "\nA consumer defined set of attributes.\n\nConsumers of the file service can add their own tags and identifiers to\na file using the \"meta\" object." + ], + "signature": [ + "Meta | undefined" + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileJSON.alt", + "type": "string", + "tags": [], + "label": "alt", + "description": [ + "\nUse this text to describe the file contents for display and accessibility." + ], + "signature": [ + "string | undefined" + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileJSON.fileKind", + "type": "string", + "tags": [ + "note" + ], + "label": "fileKind", + "description": [ + "\nA unique kind that governs various aspects of the file. A consumer of the\nfiles service must register a file kind and link their files to a specific\nkind.\n" + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileJSON.status", + "type": "CompoundType", + "tags": [], + "label": "status", + "description": [ + "\nThe current status of the file.\n\nSee {@link FileStatus} for more details." + ], + "signature": [ + "\"AWAITING_UPLOAD\" | \"UPLOADING\" | \"READY\" | \"UPLOAD_ERROR\" | \"DELETED\"" + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileJSON.user", + "type": "Object", + "tags": [], + "label": "user", + "description": [ + "\nUser data associated with this file" + ], + "signature": [ + "{ name?: string | undefined; id?: string | undefined; } | undefined" + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileKindBase", + "type": "Interface", + "tags": [], + "label": "FileKindBase", + "description": [], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileKindBase.id", + "type": "string", + "tags": [], + "label": "id", + "description": [ + "\nUnique file kind ID" + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileKindBase.allowedMimeTypes", + "type": "Array", + "tags": [ + "default" + ], + "label": "allowedMimeTypes", + "description": [ + "\nThe MIME type of the file content.\n" + ], + "signature": [ + "string[] | undefined" + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileKindBrowser", + "type": "Interface", + "tags": [], + "label": "FileKindBrowser", + "description": [], + "signature": [ + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileKindBrowser", + "text": "FileKindBrowser" + }, + " extends ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileKindBase", + "text": "FileKindBase" + } + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileKindBrowser.maxSizeBytes", + "type": "number", + "tags": [ + "default" + ], + "label": "maxSizeBytes", + "description": [ + "\nMax file contents size, in bytes, enforced for this file kind in the upload\ncomponent.\n" + ], + "signature": [ + "number | undefined" + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileShareJSON", + "type": "Interface", + "tags": [], + "label": "FileShareJSON", + "description": [ + "\nAttributes of a file that represent a serialised version of the file." + ], + "path": "packages/shared-ux/file/types/sharing.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileShareJSON.id", + "type": "string", + "tags": [], + "label": "id", + "description": [ + "\nUnique ID share instance" + ], + "path": "packages/shared-ux/file/types/sharing.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileShareJSON.created", + "type": "string", + "tags": [], + "label": "created", + "description": [ + "\nISO timestamp the share was created" + ], + "path": "packages/shared-ux/file/types/sharing.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileShareJSON.validUntil", + "type": "number", + "tags": [], + "label": "validUntil", + "description": [ + "\nUnix timestamp (in milliseconds) of when this share expires" + ], + "path": "packages/shared-ux/file/types/sharing.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileShareJSON.name", + "type": "string", + "tags": [], + "label": "name", + "description": [ + "\nA user-friendly name for the file share" + ], + "signature": [ + "string | undefined" + ], + "path": "packages/shared-ux/file/types/sharing.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileShareJSON.fileId", + "type": "string", + "tags": [], + "label": "fileId", + "description": [ + "\nThe ID of the file this share is linked to" + ], + "path": "packages/shared-ux/file/types/sharing.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.Pagination", + "type": "Interface", + "tags": [], + "label": "Pagination", + "description": [], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.Pagination.page", + "type": "number", + "tags": [], + "label": "page", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.Pagination.perPage", + "type": "number", + "tags": [], + "label": "perPage", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "packages/shared-ux/file/types/base_file_client.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ], + "enums": [], + "misc": [ + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.BaseFileMetadata", + "type": "Type", + "tags": [], + "label": "BaseFileMetadata", + "description": [ + "\nFile metadata fields are defined per the ECS specification:\n\nhttps://www.elastic.co/guide/en/ecs/current/ecs-file.html\n\nCustom fields are named according to the custom field convention: \"CustomFieldName\"." + ], + "signature": [ + "{ name?: string | undefined; mime_type?: string | undefined; created?: string | undefined; size?: number | undefined; hash?: { [hashName: string]: string | undefined; md5?: string | undefined; sha1?: string | undefined; sha256?: string | undefined; sha384?: string | undefined; sha512?: string | undefined; ssdeep?: string | undefined; tlsh?: string | undefined; } | undefined; user?: { name?: string | undefined; id?: string | undefined; } | undefined; extension?: string | undefined; Alt?: string | undefined; Updated?: string | undefined; Status?: ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileStatus", + "text": "FileStatus" + }, + " | undefined; ChunkSize?: number | undefined; Compression?: ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileCompression", + "text": "FileCompression" + }, + " | undefined; }" + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileCompression", + "type": "Type", + "tags": [], + "label": "FileCompression", + "description": [ + "\nSupported file compression algorithms" + ], + "signature": [ + "\"none\" | \"br\" | \"gzip\" | \"deflate\"" + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileMetadata", + "type": "Type", + "tags": [], + "label": "FileMetadata", + "description": [ + "\nExtra metadata on a file object specific to Kibana implementation." + ], + "signature": [ + "Required> & ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.BaseFileMetadata", + "text": "BaseFileMetadata" + }, + " & { FileKind: string; Meta?: Meta | undefined; }" + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileShare", + "type": "Type", + "tags": [], + "label": "FileShare", + "description": [ + "\nData stored with a file share object" + ], + "signature": [ + "{ created: string; token: string; name?: string | undefined; valid_until: number; }" + ], + "path": "packages/shared-ux/file/types/sharing.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileShareJSONWithToken", + "type": "Type", + "tags": [ + "note" + ], + "label": "FileShareJSONWithToken", + "description": [ + "\nA version of the file share with a token included.\n" + ], + "signature": [ + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileShareJSON", + "text": "FileShareJSON" + }, + " & { token: string; }" + ], + "path": "packages/shared-ux/file/types/sharing.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileStatus", + "type": "Type", + "tags": [], + "label": "FileStatus", + "description": [], + "signature": [ + "\"AWAITING_UPLOAD\" | \"UPLOADING\" | \"READY\" | \"UPLOAD_ERROR\" | \"DELETED\"" + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/kbn_shared_ux_file_types.mdx b/api_docs/kbn_shared_ux_file_types.mdx new file mode 100644 index 0000000000000..09dd4674a2d1f --- /dev/null +++ b/api_docs/kbn_shared_ux_file_types.mdx @@ -0,0 +1,33 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibKbnSharedUxFileTypesPluginApi +slug: /kibana-dev-docs/api/kbn-shared-ux-file-types +title: "@kbn/shared-ux-file-types" +image: https://source.unsplash.com/400x175/?github +description: API docs for the @kbn/shared-ux-file-types plugin +date: 2023-03-02 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-types'] +--- +import kbnSharedUxFileTypesObj from './kbn_shared_ux_file_types.devdocs.json'; + + + +Contact [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 70 | 0 | 9 | 0 | + +## Common + +### Interfaces + + +### Consts, variables and types + + diff --git a/api_docs/kbn_shared_ux_file_upload.devdocs.json b/api_docs/kbn_shared_ux_file_upload.devdocs.json index 143e11c571ef2..82ccfb8816f4f 100644 --- a/api_docs/kbn_shared_ux_file_upload.devdocs.json +++ b/api_docs/kbn_shared_ux_file_upload.devdocs.json @@ -119,7 +119,13 @@ "label": "fileJSON", "description": [], "signature": [ - "FileJSON", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, "" ], "path": "packages/shared-ux/file/file_upload/impl/src/upload_state.ts", diff --git a/api_docs/kbn_shared_ux_file_upload.mdx b/api_docs/kbn_shared_ux_file_upload.mdx index 613cc2d6800b4..4ea964c495788 100644 --- a/api_docs/kbn_shared_ux_file_upload.mdx +++ b/api_docs/kbn_shared_ux_file_upload.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-upload title: "@kbn/shared-ux-file-upload" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-upload plugin -date: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-upload'] --- import kbnSharedUxFileUploadObj from './kbn_shared_ux_file_upload.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_util.devdocs.json b/api_docs/kbn_shared_ux_file_util.devdocs.json index c471a74aeec8e..bf89b1d3e11a3 100644 --- a/api_docs/kbn_shared_ux_file_util.devdocs.json +++ b/api_docs/kbn_shared_ux_file_util.devdocs.json @@ -144,7 +144,13 @@ ], "signature": [ "(file: Blob | File) => Promise<", - "FileImageMetadata", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileImageMetadata", + "text": "FileImageMetadata" + }, " | undefined>" ], "path": "packages/shared-ux/file/util/src/image_metadata.ts", @@ -264,7 +270,13 @@ "description": [], "signature": [ "(file: Blob | File) => Promise<", - "FileImageMetadata", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileImageMetadata", + "text": "FileImageMetadata" + }, " | undefined>" ], "path": "packages/shared-ux/file/util/src/image_metadata.ts", diff --git a/api_docs/kbn_shared_ux_file_util.mdx b/api_docs/kbn_shared_ux_file_util.mdx index 49cba5b2b6b35..f5103df1642ea 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: 2023-02-27 +date: 2023-03-02 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 bc4cd4bb296af..97f45e6f83b2c 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: 2023-02-27 +date: 2023-03-02 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 a1832fbd6b9d0..f67da8f76056e 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: 2023-02-27 +date: 2023-03-02 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 69a7fadee3a7c..5de34430503ce 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: 2023-02-27 +date: 2023-03-02 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 7d39cc8ff942d..b9c09753d8c3c 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: 2023-02-27 +date: 2023-03-02 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 4d299d8c283c8..925939b7b3b14 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: 2023-02-27 +date: 2023-03-02 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 1b96b08fa1505..aead1c8075be1 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: 2023-02-27 +date: 2023-03-02 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 41e4b50335f26..467752087fbd1 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: 2023-02-27 +date: 2023-03-02 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 0b5f529b6fc2e..b8e9b22f1f1e7 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: 2023-02-27 +date: 2023-03-02 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 c092d3a7327e9..18e62b3d81c71 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: 2023-02-27 +date: 2023-03-02 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 5d3e545e34a9a..129bdc8d51894 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: 2023-02-27 +date: 2023-03-02 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 e99ba437ea181..09ae4255ca89b 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data'] --- import kbnSharedUxPageNoDataObj from './kbn_shared_ux_page_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_config.mdx b/api_docs/kbn_shared_ux_page_no_data_config.mdx index e473873e76f9c..1fbeb1e9d68db 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: 2023-02-27 +date: 2023-03-02 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 24adfbf9bab9a..8caedf2cd62ba 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: 2023-02-27 +date: 2023-03-02 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 f374066c21e10..c7bb81745e1b9 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-mocks'] --- import kbnSharedUxPageNoDataMocksObj from './kbn_shared_ux_page_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_solution_nav.mdx b/api_docs/kbn_shared_ux_page_solution_nav.mdx index b8c62ed4bc494..5db172a4f79f4 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-solution-nav'] --- import kbnSharedUxPageSolutionNavObj from './kbn_shared_ux_page_solution_nav.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_no_data_views.mdx b/api_docs/kbn_shared_ux_prompt_no_data_views.mdx index c84c8dd68cbdb..a26fb1bdd2f0f 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: 2023-02-27 +date: 2023-03-02 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 6a617b21a10a9..db19f5d6acdfe 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: 2023-02-27 +date: 2023-03-02 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 e7c675c73947d..f9d52f03f61e1 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: 2023-02-27 +date: 2023-03-02 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 287c4f3b7c580..3235db692a3da 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: 2023-02-27 +date: 2023-03-02 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 52347ab66f141..85372208f09f1 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: 2023-02-27 +date: 2023-03-02 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 d8a7f1f9b7677..93ac6e208bfb2 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: 2023-02-27 +date: 2023-03-02 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 570ef34b459c4..de7c54411e4da 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: 2023-02-27 +date: 2023-03-02 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 2d2152989cec8..167415f51c2a2 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-utility'] --- import kbnSharedUxUtilityObj from './kbn_shared_ux_utility.devdocs.json'; diff --git a/api_docs/kbn_slo_schema.mdx b/api_docs/kbn_slo_schema.mdx index 3d2fba338fcd2..c52546abcb1dd 100644 --- a/api_docs/kbn_slo_schema.mdx +++ b/api_docs/kbn_slo_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-slo-schema title: "@kbn/slo-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/slo-schema plugin -date: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/slo-schema'] --- import kbnSloSchemaObj from './kbn_slo_schema.devdocs.json'; diff --git a/api_docs/kbn_some_dev_log.mdx b/api_docs/kbn_some_dev_log.mdx index 926afb71749c5..10c6b0eaa4a86 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/some-dev-log'] --- import kbnSomeDevLogObj from './kbn_some_dev_log.devdocs.json'; diff --git a/api_docs/kbn_std.mdx b/api_docs/kbn_std.mdx index 783c0f7c701e2..cfa11b343e00c 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: 2023-02-27 +date: 2023-03-02 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 e053e5ac872b2..81664623edf20 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: 2023-02-27 +date: 2023-03-02 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 9f9241b66735f..dba39c49e42a7 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: 2023-02-27 +date: 2023-03-02 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 b5bfc34bfffce..151f72a73b89f 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: 2023-02-27 +date: 2023-03-02 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 e2558983fbebe..3751fd6bbc8e7 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: 2023-02-27 +date: 2023-03-02 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 d843cfc415389..a3f6b269e3d08 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: 2023-02-27 +date: 2023-03-02 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 557f0858d2f54..76fa71100242c 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: 2023-02-27 +date: 2023-03-02 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 bf45f0616677f..9a28336a7b258 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/tooling-log'] --- import kbnToolingLogObj from './kbn_tooling_log.devdocs.json'; diff --git a/api_docs/kbn_ts_projects.mdx b/api_docs/kbn_ts_projects.mdx index 8fd07a8394192..5868864ad22d9 100644 --- a/api_docs/kbn_ts_projects.mdx +++ b/api_docs/kbn_ts_projects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ts-projects title: "@kbn/ts-projects" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ts-projects plugin -date: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ts-projects'] --- import kbnTsProjectsObj from './kbn_ts_projects.devdocs.json'; diff --git a/api_docs/kbn_typed_react_router_config.mdx b/api_docs/kbn_typed_react_router_config.mdx index 30c08ee3d477b..2cceab118c4e8 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/typed-react-router-config'] --- import kbnTypedReactRouterConfigObj from './kbn_typed_react_router_config.devdocs.json'; diff --git a/api_docs/kbn_ui_actions_browser.mdx b/api_docs/kbn_ui_actions_browser.mdx index b7972e4cee4f9..1119cd67540c1 100644 --- a/api_docs/kbn_ui_actions_browser.mdx +++ b/api_docs/kbn_ui_actions_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-actions-browser title: "@kbn/ui-actions-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-actions-browser plugin -date: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-actions-browser'] --- import kbnUiActionsBrowserObj from './kbn_ui_actions_browser.devdocs.json'; diff --git a/api_docs/kbn_ui_shared_deps_src.mdx b/api_docs/kbn_ui_shared_deps_src.mdx index 8b66ee98cff48..0f7c8eaa8b238 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: 2023-02-27 +date: 2023-03-02 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 b21e5ef9bd09d..68900129865df 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: 2023-02-27 +date: 2023-03-02 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 7434590f401f9..768b70d4e2d81 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: 2023-02-27 +date: 2023-03-02 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 bf7caa13eda59..902a7bf6ef8f8 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: 2023-02-27 +date: 2023-03-02 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 76e3f26b7df27..001f3e5200b3d 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: 2023-02-27 +date: 2023-03-02 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 eb3497195d0ae..fcfbba697a2d6 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: 2023-02-27 +date: 2023-03-02 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 070ac1c4a6068..f4a57359de52a 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: 2023-02-27 +date: 2023-03-02 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 73eda7bda3a96..b9ad74d6e1aa8 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: 2023-02-27 +date: 2023-03-02 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 6bf0149dfebb0..5a3615f9f57d7 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: 2023-02-27 +date: 2023-03-02 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 2e54a482ecfa0..81c6306fdfb45 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: 2023-02-27 +date: 2023-03-02 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 072f6b890c97f..c3bd6e26c3d3e 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kubernetesSecurity'] --- import kubernetesSecurityObj from './kubernetes_security.devdocs.json'; diff --git a/api_docs/lens.mdx b/api_docs/lens.mdx index ee4da3c15af13..5ed7138b508e1 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lens'] --- import lensObj from './lens.devdocs.json'; diff --git a/api_docs/license_api_guard.mdx b/api_docs/license_api_guard.mdx index 9ca9dc4debbbe..4e5cb6c23130f 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: 2023-02-27 +date: 2023-03-02 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 49b8bee6aea9c..5048d4ae336be 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: 2023-02-27 +date: 2023-03-02 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 9ba9e133f0eba..e0af238c7a65f 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: 2023-02-27 +date: 2023-03-02 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 9e7a02cf6a059..b680dd963f29d 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: 2023-02-27 +date: 2023-03-02 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 b8a6b1c234a32..4221afac3fc65 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'management'] --- import managementObj from './management.devdocs.json'; diff --git a/api_docs/maps.devdocs.json b/api_docs/maps.devdocs.json index a9dd4fac83ae3..f5b00c78a59d4 100644 --- a/api_docs/maps.devdocs.json +++ b/api_docs/maps.devdocs.json @@ -834,10 +834,10 @@ }, { "parentPluginId": "maps", - "id": "def-public.MapEmbeddable._getFilters", + "id": "def-public.MapEmbeddable._getInputFilters", "type": "Function", "tags": [], - "label": "_getFilters", + "label": "_getInputFilters", "description": [], "signature": [ "() => ", diff --git a/api_docs/maps.mdx b/api_docs/maps.mdx index 2de6fd928dbb4..0135d2c83b89c 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: 2023-02-27 +date: 2023-03-02 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 6ca708031eb71..2ddeb72c315bd 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: 2023-02-27 +date: 2023-03-02 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 84d73e1cc383c..e49b1eb30ea03 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: 2023-02-27 +date: 2023-03-02 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 38b9c7b4b4a02..3873c26812adc 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: 2023-02-27 +date: 2023-03-02 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 56d67962cddb2..d40a6cb637bff 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: 2023-02-27 +date: 2023-03-02 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 cfad19cb77150..53d6138aedc32 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: 2023-02-27 +date: 2023-03-02 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 4300df9620dfa..dc6e65ab31771 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: 2023-02-27 +date: 2023-03-02 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 f5423d859c0c8..f91125db2e4b1 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: 2023-02-27 +date: 2023-03-02 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 1229afb3755be..0bc9d3113ffee 100644 --- a/api_docs/observability.devdocs.json +++ b/api_docs/observability.devdocs.json @@ -4552,7 +4552,8 @@ "docId": "kibSpacesPluginApi", "section": "def-public.SpacesApi", "text": "SpacesApi" - } + }, + " | undefined" ], "path": "x-pack/plugins/observability/public/plugin.ts", "deprecated": false, @@ -4679,7 +4680,7 @@ "label": "format", "description": [], "signature": [ - "(options: { fields: OutputOf> & Record; formatters: { asDuration: (value: ", + "(options: { fields: OutputOf> & Record; formatters: { asDuration: (value: ", "Maybe", ", { defaultValue, extended }?: FormatterOptions) => string; asPercent: (numerator: ", "Maybe", @@ -4698,7 +4699,7 @@ "label": "options", "description": [], "signature": [ - "{ fields: OutputOf> & Record; formatters: { asDuration: (value: ", + "{ fields: OutputOf> & Record; formatters: { asDuration: (value: ", "Maybe", ", { defaultValue, extended }?: FormatterOptions) => string; asPercent: (numerator: ", "Maybe", @@ -6236,7 +6237,7 @@ "label": "ObservabilityRuleTypeFormatter", "description": [], "signature": [ - "(options: { fields: OutputOf> & Record; formatters: { asDuration: (value: ", + "(options: { fields: OutputOf> & Record; formatters: { asDuration: (value: ", "Maybe", ", { defaultValue, extended }?: FormatterOptions) => string; asPercent: (numerator: ", "Maybe", @@ -6255,7 +6256,7 @@ "label": "options", "description": [], "signature": [ - "{ fields: OutputOf> & Record; formatters: { asDuration: (value: ", + "{ fields: OutputOf> & Record; formatters: { asDuration: (value: ", "Maybe", ", { defaultValue, extended }?: FormatterOptions) => string; asPercent: (numerator: ", "Maybe", diff --git a/api_docs/observability.mdx b/api_docs/observability.mdx index ad380f21e8a89..e8133c325a091 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observability'] --- import observabilityObj from './observability.devdocs.json'; diff --git a/api_docs/osquery.devdocs.json b/api_docs/osquery.devdocs.json index 29b2b564836a6..6232998bcc0ea 100644 --- a/api_docs/osquery.devdocs.json +++ b/api_docs/osquery.devdocs.json @@ -262,7 +262,7 @@ "label": "osqueryCreateAction", "description": [], "signature": [ - "(payload: { agent_ids?: string[] | undefined; agent_all?: boolean | undefined; agent_platforms?: string[] | undefined; agent_policy_ids?: string[] | undefined; query?: string | undefined; queries?: { id: string; query: string; ecs_mapping: { [x: string]: { field?: string | undefined; value?: string | string[] | undefined; }; } | undefined; version: string | undefined; platform: string | undefined; removed: boolean | undefined; snapshot: boolean | undefined; }[] | undefined; saved_query_id?: string | undefined; ecs_mapping?: { [x: string]: { field?: string | undefined; value?: string | string[] | undefined; }; } | undefined; pack_id?: string | undefined; alert_ids?: string[] | undefined; case_ids?: string[] | undefined; event_ids?: string[] | undefined; metadata?: object | undefined; }, alertData?: OutputOf> | undefined) => void" + "(payload: { agent_ids?: string[] | undefined; agent_all?: boolean | undefined; agent_platforms?: string[] | undefined; agent_policy_ids?: string[] | undefined; query?: string | undefined; queries?: { id: string; query: string; ecs_mapping: { [x: string]: { field?: string | undefined; value?: string | string[] | undefined; }; } | undefined; version: string | undefined; platform: string | undefined; removed: boolean | undefined; snapshot: boolean | undefined; }[] | undefined; saved_query_id?: string | undefined; ecs_mapping?: { [x: string]: { field?: string | undefined; value?: string | string[] | undefined; }; } | undefined; pack_id?: string | undefined; alert_ids?: string[] | undefined; case_ids?: string[] | undefined; event_ids?: string[] | undefined; metadata?: object | undefined; }, alertData?: OutputOf> | undefined) => void" ], "path": "x-pack/plugins/osquery/server/types.ts", "deprecated": false, @@ -291,7 +291,7 @@ "label": "alertData", "description": [], "signature": [ - "OutputOf> | undefined" + "OutputOf> | undefined" ], "path": "x-pack/plugins/osquery/server/types.ts", "deprecated": false, diff --git a/api_docs/osquery.mdx b/api_docs/osquery.mdx index b3e3702b98a01..4fb5bf0000472 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: 2023-02-27 +date: 2023-03-02 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 241f72e86a5f0..329f8fa2a3f3e 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -15,13 +15,13 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | Count | Plugins or Packages with a
public API | Number of teams | |--------------|----------|------------------------| -| 574 | 469 | 38 | +| 576 | 472 | 38 | ### Public API health stats | API Count | Any Count | Missing comments | Missing exports | |--------------|----------|-----------------|--------| -| 67704 | 515 | 58526 | 1238 | +| 67884 | 515 | 58602 | 1234 | ## Plugin Directory @@ -30,7 +30,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 256 | 8 | 251 | 24 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 36 | 1 | 32 | 2 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | AIOps plugin maintained by ML team. | 12 | 0 | 1 | 2 | -| | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 487 | 1 | 476 | 40 | +| | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 497 | 1 | 486 | 41 | | | [@elastic/apm-ui](https://github.com/orgs/elastic/teams/apm-ui) | The user interface for Elastic APM | 42 | 0 | 42 | 65 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 9 | 0 | 9 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Considering using bfetch capabilities when fetching large amounts of data. This services supports batching HTTP requests and streaming responses back. | 89 | 1 | 74 | 2 | @@ -40,14 +40,14 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 41 | 0 | 11 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | Chat available on Elastic Cloud deployments for quicker assistance. | 1 | 0 | 0 | 0 | | | [@elastic/platform-onboarding](https://github.com/orgs/elastic/teams/platform-onboarding) | Static migration page where self-managed users can see text/copy about migrating to Elastic Cloud | 8 | 1 | 8 | 1 | -| | [@elastic/sec-cloudnative-integrations](https://github.com/orgs/elastic/teams/sec-cloudnative-integrations) | Defend for Containers | 2 | 0 | 2 | 0 | +| | [@elastic/sec-cloudnative-integrations](https://github.com/orgs/elastic/teams/sec-cloudnative-integrations) | Defend for containers (D4C) | 15 | 0 | 4 | 1 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | Provides the necessary APIs to implement A/B testing scenarios, fetching the variations in configuration and reporting back metrics to track conversion rates of the experiments. | 12 | 0 | 0 | 0 | | cloudFullStory | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | When Kibana runs on Elastic Cloud, this plugin registers FullStory as a shipper for telemetry. | 0 | 0 | 0 | 0 | | cloudGainsight | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | When Kibana runs on Elastic Cloud, this plugin registers Gainsight as a shipper for telemetry. | 0 | 0 | 0 | 0 | | cloudLinks | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | Adds the links to the Elastic Cloud console | 0 | 0 | 0 | 0 | | | [@elastic/kibana-cloud-security-posture](https://github.com/orgs/elastic/teams/kibana-cloud-security-posture) | The cloud security posture plugin | 17 | 0 | 2 | 2 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 13 | 0 | 13 | 1 | -| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Content management app | 46 | 0 | 46 | 3 | +| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Content management app | 110 | 0 | 96 | 3 | | | [@elastic/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 | 272 | 0 | 268 | 11 | | crossClusterReplication | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 0 | 0 | 0 | 0 | | customBranding | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Enables customization of Kibana | 0 | 0 | 0 | 0 | @@ -64,7 +64,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | This plugin contains the Discover application and the saved search embeddable. | 97 | 0 | 78 | 7 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 37 | 0 | 35 | 2 | | | [@elastic/security-threat-hunting-investigations](https://github.com/orgs/elastic/teams/security-threat-hunting-investigations) | APIs used to assess the quality of data in Elasticsearch indexes | 2 | 0 | 0 | 0 | -| | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds embeddables service to Kibana | 532 | 8 | 430 | 4 | +| | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds embeddables service to Kibana | 539 | 9 | 436 | 4 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Extends embeddable plugin with more functionality | 14 | 0 | 14 | 0 | | | [@elastic/kibana-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 | | | [@elastic/enterprise-search-frontend](https://github.com/orgs/elastic/teams/enterprise-search-frontend) | Adds dashboards for discovering and managing Enterprise Search products. | 9 | 0 | 9 | 0 | @@ -88,9 +88,9 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 236 | 0 | 100 | 2 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Index pattern fields and ambiguous values formatters | 288 | 26 | 249 | 3 | | | [@elastic/kibana-gis](https://github.com/orgs/elastic/teams/kibana-gis) | The file upload plugin contains components and services for uploading a file, analyzing its data, and then importing the data into an Elasticsearch index. Supported file types include CSV, TSV, newline-delimited JSON and GeoJSON. | 62 | 0 | 62 | 2 | -| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | File upload, download, sharing, and serving over HTTP implementation in Kibana. | 254 | 1 | 45 | 5 | +| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | File upload, download, sharing, and serving over HTTP implementation in Kibana. | 214 | 0 | 10 | 5 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Simple UI for managing files in Kibana | 2 | 1 | 2 | 0 | -| | [@elastic/fleet](https://github.com/orgs/elastic/teams/fleet) | - | 1087 | 3 | 982 | 27 | +| | [@elastic/fleet](https://github.com/orgs/elastic/teams/fleet) | - | 1094 | 3 | 989 | 27 | | ftrApis | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 0 | 0 | 0 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 68 | 0 | 14 | 5 | | globalSearchBar | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 0 | 0 | 0 | 0 | @@ -137,8 +137,8 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 21 | 0 | 21 | 0 | | | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 258 | 0 | 229 | 13 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 24 | 0 | 19 | 2 | -| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 216 | 2 | 175 | 5 | -| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 39 | 0 | 39 | 0 | +| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 231 | 2 | 180 | 5 | +| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 44 | 0 | 44 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 154 | 0 | 140 | 2 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 79 | 0 | 73 | 3 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 100 | 0 | 52 | 1 | @@ -199,6 +199,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 6 | 0 | 6 | 0 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 53 | 0 | 22 | 0 | | | [@elastic/security-solution](https://github.com/orgs/elastic/teams/security-solution) | - | 9 | 1 | 9 | 0 | +| | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 12 | 0 | 12 | 0 | | | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 5 | 0 | 4 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 73 | 0 | 73 | 2 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 98 | 0 | 0 | 0 | @@ -323,8 +324,8 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 6 | 0 | 6 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 11 | 0 | 11 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 6 | 0 | 0 | 0 | -| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 5 | 0 | 0 | 0 | -| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 7 | 0 | 6 | 1 | +| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 6 | 0 | 0 | 0 | +| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 4 | 0 | 3 | 1 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 5 | 0 | 5 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 35 | 4 | 23 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 32 | 0 | 11 | 2 | @@ -341,7 +342,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 2 | 0 | 2 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 2 | 0 | 2 | 1 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 4 | 0 | 4 | 1 | -| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 23 | 1 | 22 | 0 | +| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 25 | 1 | 24 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 107 | 1 | 0 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 334 | 1 | 4 | 1 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 75 | 0 | 54 | 1 | @@ -354,7 +355,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 71 | 0 | 39 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 25 | 0 | 23 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 4 | 0 | 4 | 0 | -| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 112 | 0 | 79 | 45 | +| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 116 | 0 | 81 | 46 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 12 | 0 | 12 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 489 | 1 | 98 | 4 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 69 | 0 | 69 | 4 | @@ -402,6 +403,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 251 | 1 | 193 | 15 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 12 | 0 | 12 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 2 | 0 | 1 | 0 | +| | [@elastic/security-threat-hunting-investigations](https://github.com/orgs/elastic/teams/security-threat-hunting-investigations) | - | 13 | 0 | 4 | 3 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 20 | 0 | 16 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 2 | 0 | 0 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 29 | 0 | 29 | 1 | @@ -480,6 +482,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 2 | 0 | 2 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 1 | 0 | 1 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 14 | 0 | 6 | 0 | +| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 70 | 0 | 9 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 7 | 0 | 7 | 1 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 17 | 0 | 15 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 17 | 0 | 9 | 0 | diff --git a/api_docs/presentation_util.mdx b/api_docs/presentation_util.mdx index 2e4c690bdfde2..bd3fea040e1e2 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'presentationUtil'] --- import presentationUtilObj from './presentation_util.devdocs.json'; diff --git a/api_docs/profiling.mdx b/api_docs/profiling.mdx index 4e7eff9bad9ea..55a72a326e1a1 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'profiling'] --- import profilingObj from './profiling.devdocs.json'; diff --git a/api_docs/remote_clusters.mdx b/api_docs/remote_clusters.mdx index 50384f02b70fd..27991d6f57a32 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: 2023-02-27 +date: 2023-03-02 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 d22bdb346e94b..85cd473879d34 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: 2023-02-27 +date: 2023-03-02 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 9b051aa152030..8acc1623aadc8 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'rollup'] --- import rollupObj from './rollup.devdocs.json'; diff --git a/api_docs/rule_registry.devdocs.json b/api_docs/rule_registry.devdocs.json index e9bed596ec7e7..e0588585ba405 100644 --- a/api_docs/rule_registry.devdocs.json +++ b/api_docs/rule_registry.devdocs.json @@ -107,7 +107,7 @@ "label": "get", "description": [], "signature": [ - "({ id, index }: GetAlertParams) => Promise> | undefined>" + "({ id, index }: GetAlertParams) => Promise> | undefined>" ], "path": "x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts", "deprecated": false, @@ -325,7 +325,7 @@ "SortOptions", "[] | undefined; track_total_hits?: boolean | undefined; _source?: string[] | undefined; }) => Promise<", "SearchResponse", - ">, Record>, Record>>" ], @@ -1901,7 +1901,7 @@ "RuleTypeParamsValidator", " | undefined; } | undefined; cancelAlertsOnRuleTimeout?: boolean | undefined; alerts?: ", "IRuleTypeAlerts", - " | undefined; actionGroups: ", + " | undefined; producer: string; actionGroups: ", { "pluginId": "alerting", "scope": "common", @@ -1917,7 +1917,7 @@ "section": "def-common.ActionGroup", "text": "ActionGroup" }, - " | undefined; producer: string; actionVariables?: { context?: ", + " | undefined; actionVariables?: { context?: ", { "pluginId": "alerting", "scope": "common", @@ -2573,7 +2573,7 @@ "signature": [ "> & OutputOf>>>(request: TSearchRequest) => Promise<", + ", TAlertDoc = Partial> & OutputOf>>>(request: TSearchRequest) => Promise<", { "pluginId": "@kbn/es-types", "scope": "common", @@ -3210,7 +3210,7 @@ "label": "getAlertByAlertUuid", "description": [], "signature": [ - "(alertUuid: string) => Promise> & OutputOf>> | null> | null" + "(alertUuid: string) => Promise> & OutputOf>> | null> | null" ], "path": "x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts", "deprecated": false, @@ -4603,7 +4603,7 @@ "label": "parseTechnicalFields", "description": [], "signature": [ - "(input: unknown, partial?: boolean) => OutputOf>" + "(input: unknown, partial?: boolean) => OutputOf>" ], "path": "x-pack/plugins/rule_registry/common/parse_technical_fields.ts", "deprecated": false, @@ -4979,7 +4979,7 @@ "label": "ParsedTechnicalFields", "description": [], "signature": [ - "{ readonly '@timestamp': string; readonly \"kibana.alert.rule.rule_type_id\": string; readonly \"kibana.alert.rule.consumer\": string; readonly \"kibana.alert.rule.producer\": string; readonly \"kibana.space_ids\": string[]; readonly \"kibana.alert.uuid\": string; readonly \"kibana.alert.instance.id\": string; readonly \"kibana.alert.status\": string; readonly \"kibana.alert.rule.category\": string; readonly \"kibana.alert.rule.uuid\": string; readonly \"kibana.alert.rule.name\": string; readonly 'event.action'?: string | undefined; readonly tags?: string[] | undefined; readonly \"kibana.alert.rule.execution.uuid\"?: string | undefined; readonly \"kibana.alert.rule.parameters\"?: { [key: string]: unknown; } | undefined; readonly \"kibana.alert.start\"?: string | undefined; readonly \"kibana.alert.time_range\"?: unknown; readonly \"kibana.alert.end\"?: string | undefined; readonly \"kibana.alert.duration.us\"?: number | undefined; readonly \"kibana.alert.severity\"?: string | undefined; readonly \"kibana.alert.flapping\"?: boolean | undefined; readonly \"kibana.version\"?: string | undefined; readonly \"ecs.version\"?: string | undefined; readonly \"kibana.alert.risk_score\"?: number | undefined; readonly \"kibana.alert.workflow_status\"?: string | undefined; readonly \"kibana.alert.workflow_user\"?: string | undefined; readonly \"kibana.alert.workflow_reason\"?: string | undefined; readonly \"kibana.alert.system_status\"?: string | undefined; readonly \"kibana.alert.action_group\"?: string | undefined; readonly \"kibana.alert.reason\"?: string | undefined; readonly \"kibana.alert.case_ids\"?: string[] | undefined; readonly \"kibana.alert.rule.author\"?: string | undefined; readonly \"kibana.alert.rule.created_at\"?: string | undefined; readonly \"kibana.alert.rule.created_by\"?: string | undefined; readonly \"kibana.alert.rule.description\"?: string | undefined; readonly \"kibana.alert.rule.enabled\"?: string | undefined; readonly \"kibana.alert.rule.from\"?: string | undefined; readonly \"kibana.alert.rule.interval\"?: string | undefined; readonly \"kibana.alert.rule.license\"?: string | undefined; readonly \"kibana.alert.rule.note\"?: string | undefined; readonly \"kibana.alert.rule.references\"?: string[] | undefined; readonly \"kibana.alert.rule.rule_id\"?: string | undefined; readonly \"kibana.alert.rule.rule_name_override\"?: string | undefined; readonly \"kibana.alert.rule.tags\"?: string[] | undefined; readonly \"kibana.alert.rule.to\"?: string | undefined; readonly \"kibana.alert.rule.type\"?: string | undefined; readonly \"kibana.alert.rule.updated_at\"?: string | undefined; readonly \"kibana.alert.rule.updated_by\"?: string | undefined; readonly \"kibana.alert.rule.version\"?: string | undefined; readonly \"kibana.alert.suppression.terms.field\"?: string[] | undefined; readonly \"kibana.alert.suppression.terms.value\"?: string[] | undefined; readonly \"kibana.alert.suppression.start\"?: string | undefined; readonly \"kibana.alert.suppression.end\"?: string | undefined; readonly \"kibana.alert.suppression.docs_count\"?: number | undefined; readonly \"kibana.alert.last_detected\"?: string | undefined; readonly 'event.kind'?: string | undefined; }" + "{ readonly \"@timestamp\": string; readonly \"kibana.alert.rule.rule_type_id\": string; readonly \"kibana.alert.rule.consumer\": string; readonly \"kibana.alert.instance.id\": string; readonly \"kibana.alert.rule.category\": string; readonly \"kibana.alert.rule.name\": string; readonly \"kibana.alert.rule.producer\": string; readonly \"kibana.alert.rule.uuid\": string; readonly \"kibana.alert.status\": string; readonly \"kibana.alert.uuid\": string; readonly \"kibana.space_ids\": string[]; readonly \"event.action\"?: string | undefined; readonly tags?: string[] | undefined; readonly \"kibana.alert.rule.execution.uuid\"?: string | undefined; readonly \"kibana.alert.action_group\"?: string | undefined; readonly \"kibana.alert.case_ids\"?: string[] | undefined; readonly \"kibana.alert.duration.us\"?: number | undefined; readonly \"kibana.alert.end\"?: string | undefined; readonly \"kibana.alert.flapping\"?: boolean | undefined; readonly \"kibana.alert.flapping_history\"?: boolean[] | undefined; readonly \"kibana.alert.last_detected\"?: string | undefined; readonly \"kibana.alert.reason\"?: string | undefined; readonly \"kibana.alert.rule.parameters\"?: { [key: string]: unknown; } | undefined; readonly \"kibana.alert.rule.tags\"?: string[] | undefined; readonly \"kibana.alert.start\"?: string | undefined; readonly \"kibana.alert.time_range\"?: unknown; readonly \"kibana.alert.workflow_status\"?: string | undefined; readonly \"kibana.version\"?: string | undefined; readonly \"kibana.alert.risk_score\"?: number | undefined; readonly \"kibana.alert.rule.author\"?: string | undefined; readonly \"kibana.alert.rule.created_at\"?: string | undefined; readonly \"kibana.alert.rule.created_by\"?: string | undefined; readonly \"kibana.alert.rule.description\"?: string | undefined; readonly \"kibana.alert.rule.enabled\"?: string | undefined; readonly \"kibana.alert.rule.from\"?: string | undefined; readonly \"kibana.alert.rule.interval\"?: string | undefined; readonly \"kibana.alert.rule.license\"?: string | undefined; readonly \"kibana.alert.rule.note\"?: string | undefined; readonly \"kibana.alert.rule.references\"?: string[] | undefined; readonly \"kibana.alert.rule.rule_id\"?: string | undefined; readonly \"kibana.alert.rule.rule_name_override\"?: string | undefined; readonly \"kibana.alert.rule.to\"?: string | undefined; readonly \"kibana.alert.rule.type\"?: string | undefined; readonly \"kibana.alert.rule.updated_at\"?: string | undefined; readonly \"kibana.alert.rule.updated_by\"?: string | undefined; readonly \"kibana.alert.rule.version\"?: string | undefined; readonly \"kibana.alert.severity\"?: string | undefined; readonly \"kibana.alert.suppression.docs_count\"?: number | undefined; readonly \"kibana.alert.suppression.end\"?: string | undefined; readonly \"kibana.alert.suppression.terms.field\"?: string[] | undefined; readonly \"kibana.alert.suppression.start\"?: string | undefined; readonly \"kibana.alert.suppression.terms.value\"?: string[] | undefined; readonly \"kibana.alert.system_status\"?: string | undefined; readonly \"kibana.alert.workflow_reason\"?: string | undefined; readonly \"kibana.alert.workflow_user\"?: string | undefined; readonly \"ecs.version\"?: string | undefined; readonly \"event.kind\"?: string | undefined; }" ], "path": "x-pack/plugins/rule_registry/common/parse_technical_fields.ts", "deprecated": false, diff --git a/api_docs/rule_registry.mdx b/api_docs/rule_registry.mdx index 01e03df7443c0..f1367bb632c47 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: 2023-02-27 +date: 2023-03-02 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 d97cab5c93af0..40cbcf1dc389a 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'runtimeFields'] --- import runtimeFieldsObj from './runtime_fields.devdocs.json'; diff --git a/api_docs/saved_objects.devdocs.json b/api_docs/saved_objects.devdocs.json index b6ed1819ec684..366db1e5b33db 100644 --- a/api_docs/saved_objects.devdocs.json +++ b/api_docs/saved_objects.devdocs.json @@ -499,6 +499,195 @@ "trackAdoption": false } ] + }, + { + "parentPluginId": "savedObjects", + "id": "def-public.SavedObjectFinderUi.propTypes.euiFormRowProps", + "type": "Function", + "tags": [], + "label": "euiFormRowProps", + "description": [], + "signature": [ + "Requireable", + "" + ], + "path": "src/plugins/saved_objects/public/finder/saved_object_finder.tsx", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "savedObjects", + "id": "def-public.SavedObjectFinderUi.propTypes.euiFormRowProps.$1", + "type": "Object", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "{ [key: string]: any; }" + ], + "path": "node_modules/@types/prop-types/index.d.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "savedObjects", + "id": "def-public.SavedObjectFinderUi.propTypes.euiFormRowProps.$2", + "type": "string", + "tags": [], + "label": "propName", + "description": [], + "path": "node_modules/@types/prop-types/index.d.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "savedObjects", + "id": "def-public.SavedObjectFinderUi.propTypes.euiFormRowProps.$3", + "type": "string", + "tags": [], + "label": "componentName", + "description": [], + "path": "node_modules/@types/prop-types/index.d.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "savedObjects", + "id": "def-public.SavedObjectFinderUi.propTypes.euiFormRowProps.$4", + "type": "string", + "tags": [], + "label": "location", + "description": [], + "path": "node_modules/@types/prop-types/index.d.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "savedObjects", + "id": "def-public.SavedObjectFinderUi.propTypes.euiFormRowProps.$5", + "type": "string", + "tags": [], + "label": "propFullName", + "description": [], + "path": "node_modules/@types/prop-types/index.d.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "savedObjects", + "id": "def-public.SavedObjectFinderUi.propTypes.euiFieldSearchProps", + "type": "Function", + "tags": [], + "label": "euiFieldSearchProps", + "description": [], + "signature": [ + "Requireable", + "" + ], + "path": "src/plugins/saved_objects/public/finder/saved_object_finder.tsx", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "savedObjects", + "id": "def-public.SavedObjectFinderUi.propTypes.euiFieldSearchProps.$1", + "type": "Object", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "{ [key: string]: any; }" + ], + "path": "node_modules/@types/prop-types/index.d.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "savedObjects", + "id": "def-public.SavedObjectFinderUi.propTypes.euiFieldSearchProps.$2", + "type": "string", + "tags": [], + "label": "propName", + "description": [], + "path": "node_modules/@types/prop-types/index.d.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "savedObjects", + "id": "def-public.SavedObjectFinderUi.propTypes.euiFieldSearchProps.$3", + "type": "string", + "tags": [], + "label": "componentName", + "description": [], + "path": "node_modules/@types/prop-types/index.d.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "savedObjects", + "id": "def-public.SavedObjectFinderUi.propTypes.euiFieldSearchProps.$4", + "type": "string", + "tags": [], + "label": "location", + "description": [], + "path": "node_modules/@types/prop-types/index.d.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "savedObjects", + "id": "def-public.SavedObjectFinderUi.propTypes.euiFieldSearchProps.$5", + "type": "string", + "tags": [], + "label": "propFullName", + "description": [], + "path": "node_modules/@types/prop-types/index.d.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ] + }, + { + "parentPluginId": "savedObjects", + "id": "def-public.SavedObjectFinderUi.defaultProps", + "type": "Object", + "tags": [], + "label": "defaultProps", + "description": [], + "path": "src/plugins/saved_objects/public/finder/saved_object_finder.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "savedObjects", + "id": "def-public.SavedObjectFinderUi.defaultProps.euiFormRowProps", + "type": "Object", + "tags": [], + "label": "euiFormRowProps", + "description": [], + "path": "src/plugins/saved_objects/public/finder/saved_object_finder.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [] + }, + { + "parentPluginId": "savedObjects", + "id": "def-public.SavedObjectFinderUi.defaultProps.euiFieldSearchProps", + "type": "Object", + "tags": [], + "label": "euiFieldSearchProps", + "description": [], + "path": "src/plugins/saved_objects/public/finder/saved_object_finder.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [] } ] }, diff --git a/api_docs/saved_objects.mdx b/api_docs/saved_objects.mdx index 174da172a54e0..0d20d5d2b96d1 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjects'] --- import savedObjectsObj from './saved_objects.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 216 | 2 | 175 | 5 | +| 231 | 2 | 180 | 5 | ## Client diff --git a/api_docs/saved_objects_finder.devdocs.json b/api_docs/saved_objects_finder.devdocs.json index 71969f4a97a60..08991612ff496 100644 --- a/api_docs/saved_objects_finder.devdocs.json +++ b/api_docs/saved_objects_finder.devdocs.json @@ -3,6 +3,149 @@ "client": { "classes": [], "functions": [ + { + "parentPluginId": "savedObjectsFinder", + "id": "def-public.getSavedObjectFinder", + "type": "Function", + "tags": [], + "label": "getSavedObjectFinder", + "description": [], + "signature": [ + "(uiSettings: ", + { + "pluginId": "@kbn/core-ui-settings-browser", + "scope": "common", + "docId": "kibKbnCoreUiSettingsBrowserPluginApi", + "section": "def-common.IUiSettingsClient", + "text": "IUiSettingsClient" + }, + ", http: ", + { + "pluginId": "@kbn/core-http-browser", + "scope": "common", + "docId": "kibKbnCoreHttpBrowserPluginApi", + "section": "def-common.HttpSetup", + "text": "HttpSetup" + }, + ", savedObjectsManagement: ", + { + "pluginId": "savedObjectsManagement", + "scope": "public", + "docId": "kibSavedObjectsManagementPluginApi", + "section": "def-public.SavedObjectsManagementPluginStart", + "text": "SavedObjectsManagementPluginStart" + }, + ", savedObjectsTagging?: ", + { + "pluginId": "savedObjectsTaggingOss", + "scope": "public", + "docId": "kibSavedObjectsTaggingOssPluginApi", + "section": "def-public.SavedObjectsTaggingApi", + "text": "SavedObjectsTaggingApi" + }, + " | undefined) => (props: ", + { + "pluginId": "savedObjectsFinder", + "scope": "public", + "docId": "kibSavedObjectsFinderPluginApi", + "section": "def-public.SavedObjectFinderProps", + "text": "SavedObjectFinderProps" + }, + ") => JSX.Element" + ], + "path": "src/plugins/saved_objects_finder/public/finder/index.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "savedObjectsFinder", + "id": "def-public.getSavedObjectFinder.$1", + "type": "Object", + "tags": [], + "label": "uiSettings", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-ui-settings-browser", + "scope": "common", + "docId": "kibKbnCoreUiSettingsBrowserPluginApi", + "section": "def-common.IUiSettingsClient", + "text": "IUiSettingsClient" + } + ], + "path": "src/plugins/saved_objects_finder/public/finder/index.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "savedObjectsFinder", + "id": "def-public.getSavedObjectFinder.$2", + "type": "Object", + "tags": [], + "label": "http", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-http-browser", + "scope": "common", + "docId": "kibKbnCoreHttpBrowserPluginApi", + "section": "def-common.HttpSetup", + "text": "HttpSetup" + } + ], + "path": "src/plugins/saved_objects_finder/public/finder/index.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "savedObjectsFinder", + "id": "def-public.getSavedObjectFinder.$3", + "type": "Object", + "tags": [], + "label": "savedObjectsManagement", + "description": [], + "signature": [ + { + "pluginId": "savedObjectsManagement", + "scope": "public", + "docId": "kibSavedObjectsManagementPluginApi", + "section": "def-public.SavedObjectsManagementPluginStart", + "text": "SavedObjectsManagementPluginStart" + } + ], + "path": "src/plugins/saved_objects_finder/public/finder/index.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "savedObjectsFinder", + "id": "def-public.getSavedObjectFinder.$4", + "type": "Object", + "tags": [], + "label": "savedObjectsTagging", + "description": [], + "signature": [ + { + "pluginId": "savedObjectsTaggingOss", + "scope": "public", + "docId": "kibSavedObjectsTaggingOssPluginApi", + "section": "def-public.SavedObjectsTaggingApi", + "text": "SavedObjectsTaggingApi" + }, + " | undefined" + ], + "path": "src/plugins/saved_objects_finder/public/finder/index.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "savedObjectsFinder", "id": "def-public.SavedObjectFinder", diff --git a/api_docs/saved_objects_finder.mdx b/api_docs/saved_objects_finder.mdx index 0ab98d7d02ba1..a925f9e7e8d1a 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsFinder'] --- import savedObjectsFinderObj from './saved_objects_finder.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/k | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 39 | 0 | 39 | 0 | +| 44 | 0 | 44 | 0 | ## Client diff --git a/api_docs/saved_objects_management.mdx b/api_docs/saved_objects_management.mdx index 526ab50e91670..93eec3c8e2134 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: 2023-02-27 +date: 2023-03-02 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 505f2a42dc503..92bed3afdacfe 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: 2023-02-27 +date: 2023-03-02 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 1ef30026b02e6..5275c2ba2daf3 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: 2023-02-27 +date: 2023-03-02 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 d3e548f519309..c31edc8a13b0e 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: 2023-02-27 +date: 2023-03-02 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 82924478cc916..3010e7478cb21 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: 2023-02-27 +date: 2023-03-02 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 b7b7e30361372..3a4777e421cca 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: 2023-02-27 +date: 2023-03-02 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 005133426198a..2b741f7ba9044 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: 2023-02-27 +date: 2023-03-02 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 037917ccefd0a..f8cb3ecf363dd 100644 --- a/api_docs/security_solution.devdocs.json +++ b/api_docs/security_solution.devdocs.json @@ -95,7 +95,7 @@ "label": "experimentalFeatures", "description": [], "signature": [ - "{ readonly tGridEnabled: boolean; readonly tGridEventRenderedViewEnabled: boolean; readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly disableIsolationUIPendingStatuses: boolean; readonly pendingActionResponsesWithAck: boolean; readonly policyListEnabled: boolean; readonly policyResponseInFleetEnabled: boolean; readonly chartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly responseActionsConsoleEnabled: boolean; readonly insightsRelatedAlertsByProcessAncestry: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointRbacEnabled: boolean; readonly endpointRbacV1Enabled: boolean; readonly alertDetailsPageEnabled: boolean; readonly responseActionGetFileEnabled: boolean; readonly responseActionExecuteEnabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly riskyHostsEnabled: boolean; readonly riskyUsersEnabled: boolean; readonly alertsPageFiltersEnabled: boolean; }" + "{ readonly tGridEnabled: boolean; readonly tGridEventRenderedViewEnabled: boolean; readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly disableIsolationUIPendingStatuses: boolean; readonly pendingActionResponsesWithAck: boolean; readonly policyListEnabled: boolean; readonly policyResponseInFleetEnabled: boolean; readonly chartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly responseActionsConsoleEnabled: boolean; readonly insightsRelatedAlertsByProcessAncestry: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointRbacEnabled: boolean; readonly endpointRbacV1Enabled: boolean; readonly alertDetailsPageEnabled: boolean; readonly responseActionGetFileEnabled: boolean; readonly responseActionExecuteEnabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly securityFlyoutEnabled: boolean; readonly riskyHostsEnabled: boolean; readonly riskyUsersEnabled: boolean; readonly alertsPageFiltersEnabled: boolean; }" ], "path": "x-pack/plugins/security_solution/public/plugin.tsx", "deprecated": false, diff --git a/api_docs/security_solution.mdx b/api_docs/security_solution.mdx index 2b59497a340fd..d51910f728a32 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolution'] --- import securitySolutionObj from './security_solution.devdocs.json'; diff --git a/api_docs/session_view.mdx b/api_docs/session_view.mdx index 02183745d657e..0636e41382b77 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: 2023-02-27 +date: 2023-03-02 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 9d2931bad5e60..c3d6c6539e557 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: 2023-02-27 +date: 2023-03-02 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 39f5d7c1ae7b2..7f6a877f69e9c 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: 2023-02-27 +date: 2023-03-02 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 a9bc92d8a42e0..beb404bc7432d 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: 2023-02-27 +date: 2023-03-02 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 be729d6bc2ecf..55a98d19c1ede 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: 2023-02-27 +date: 2023-03-02 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 5060499f75c43..f779c26c75c72 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackConnectors'] --- import stackConnectorsObj from './stack_connectors.devdocs.json'; diff --git a/api_docs/task_manager.mdx b/api_docs/task_manager.mdx index 860ecb737b655..c22042c62aa79 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'taskManager'] --- import taskManagerObj from './task_manager.devdocs.json'; diff --git a/api_docs/telemetry.devdocs.json b/api_docs/telemetry.devdocs.json index ce31c34065ea5..cca1ac23391c3 100644 --- a/api_docs/telemetry.devdocs.json +++ b/api_docs/telemetry.devdocs.json @@ -119,7 +119,7 @@ "Should the telemetry payloads be sent from the server or the browser?" ], "signature": [ - "\"server\" | \"browser\"" + "\"browser\" | \"server\"" ], "path": "src/plugins/telemetry/public/plugin.ts", "deprecated": false, diff --git a/api_docs/telemetry.mdx b/api_docs/telemetry.mdx index 8949000fbd29f..970b83513b7af 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: 2023-02-27 +date: 2023-03-02 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 5e94c6b49e610..35caf129235d2 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: 2023-02-27 +date: 2023-03-02 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 a19d535900d1f..801e3c0b78b96 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: 2023-02-27 +date: 2023-03-02 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 1d97c165c128a..29a9a59dbc554 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: 2023-02-27 +date: 2023-03-02 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 7623f52a7f706..a8f10c08b35ac 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'threatIntelligence'] --- import threatIntelligenceObj from './threat_intelligence.devdocs.json'; diff --git a/api_docs/timelines.mdx b/api_docs/timelines.mdx index 47c6749027461..4d78358b839ff 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'timelines'] --- import timelinesObj from './timelines.devdocs.json'; diff --git a/api_docs/transform.mdx b/api_docs/transform.mdx index f169e72c7236e..e5cc4d27c7c71 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: 2023-02-27 +date: 2023-03-02 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 acac8c2932fb4..a5736cefd3749 100644 --- a/api_docs/triggers_actions_ui.devdocs.json +++ b/api_docs/triggers_actions_ui.devdocs.json @@ -2703,7 +2703,7 @@ "description": [], "signature": [ "BasicFields", - " & { \"@timestamp\"?: string[] | undefined; \"event.action\"?: string[] | undefined; tags?: string[] | undefined; kibana?: string[] | undefined; \"kibana.alert.rule.rule_type_id\"?: string[] | undefined; \"kibana.alert.rule.consumer\"?: 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.case_ids\"?: 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[]; }" + " & { \"@timestamp\"?: string[] | undefined; \"event.action\"?: string[] | undefined; tags?: string[] | undefined; kibana?: string[] | undefined; \"kibana.alert.rule.rule_type_id\"?: string[] | undefined; \"kibana.alert.rule.consumer\"?: string[] | undefined; \"kibana.alert.rule.execution.uuid\"?: string[] | undefined; \"kibana.alert\"?: string[] | undefined; \"kibana.alert.action_group\"?: string[] | undefined; \"kibana.alert.case_ids\"?: string[] | undefined; \"kibana.alert.duration.us\"?: string[] | undefined; \"kibana.alert.end\"?: string[] | undefined; \"kibana.alert.flapping\"?: string[] | undefined; \"kibana.alert.instance.id\"?: string[] | undefined; \"kibana.alert.reason\"?: string[] | undefined; \"kibana.alert.rule\"?: string[] | undefined; \"kibana.alert.rule.category\"?: string[] | undefined; \"kibana.alert.rule.name\"?: string[] | undefined; \"kibana.alert.rule.parameters\"?: string[] | undefined; \"kibana.alert.rule.producer\"?: string[] | undefined; \"kibana.alert.rule.tags\"?: string[] | undefined; \"kibana.alert.rule.uuid\"?: string[] | undefined; \"kibana.alert.start\"?: string[] | undefined; \"kibana.alert.status\"?: string[] | undefined; \"kibana.alert.time_range\"?: string[] | undefined; \"kibana.alert.uuid\"?: string[] | undefined; \"kibana.alert.workflow_status\"?: string[] | undefined; \"kibana.space_ids\"?: string[] | undefined; \"kibana.version\"?: string[] | undefined; \"kibana.alert.risk_score\"?: string[] | undefined; \"kibana.alert.rule.author\"?: string[] | undefined; \"kibana.alert.rule.created_at\"?: string[] | undefined; \"kibana.alert.rule.created_by\"?: string[] | undefined; \"kibana.alert.rule.description\"?: string[] | undefined; \"kibana.alert.rule.enabled\"?: string[] | undefined; \"kibana.alert.rule.from\"?: string[] | undefined; \"kibana.alert.rule.interval\"?: string[] | undefined; \"kibana.alert.rule.license\"?: string[] | undefined; \"kibana.alert.rule.note\"?: string[] | undefined; \"kibana.alert.rule.references\"?: string[] | undefined; \"kibana.alert.rule.rule_id\"?: string[] | undefined; \"kibana.alert.rule.rule_name_override\"?: string[] | undefined; \"kibana.alert.rule.to\"?: string[] | undefined; \"kibana.alert.rule.type\"?: string[] | undefined; \"kibana.alert.rule.updated_at\"?: string[] | undefined; \"kibana.alert.rule.updated_by\"?: string[] | undefined; \"kibana.alert.rule.version\"?: string[] | undefined; \"kibana.alert.severity\"?: string[] | undefined; \"kibana.alert.suppression.docs_count\"?: string[] | undefined; \"kibana.alert.suppression.end\"?: string[] | undefined; \"kibana.alert.suppression.terms\"?: string[] | undefined; \"kibana.alert.suppression.terms.field\"?: string[] | undefined; \"kibana.alert.suppression.start\"?: string[] | undefined; \"kibana.alert.suppression.terms.value\"?: string[] | undefined; \"kibana.alert.system_status\"?: string[] | undefined; \"kibana.alert.workflow_reason\"?: string[] | undefined; \"kibana.alert.workflow_user\"?: string[] | undefined; \"ecs.version\"?: string[] | undefined; \"event.kind\"?: string[] | undefined; \"kibana.alert.evaluation.threshold\"?: string[] | undefined; \"kibana.alert.evaluation.value\"?: string[] | undefined; \"event.module\"?: 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, @@ -4172,7 +4172,7 @@ "section": "def-common.RuleType", "text": "RuleType" }, - ", \"id\" | \"name\" | \"actionGroups\" | \"defaultActionGroupId\" | \"recoveryActionGroup\" | \"producer\" | \"minimumLicenseRequired\" | \"defaultScheduleInterval\" | \"ruleTaskTimeout\" | \"doesSetRecoveryContext\">" + ", \"id\" | \"name\" | \"producer\" | \"actionGroups\" | \"defaultActionGroupId\" | \"recoveryActionGroup\" | \"minimumLicenseRequired\" | \"defaultScheduleInterval\" | \"ruleTaskTimeout\" | \"doesSetRecoveryContext\">" ], "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 cc5da08f265c9..dd5bbd23bd1c1 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: 2023-02-27 +date: 2023-03-02 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 5f66671a8e6ee..cb576491e68f8 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: 2023-02-27 +date: 2023-03-02 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 7cc0b4f939a2c..a0acb8cefb267 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: 2023-02-27 +date: 2023-03-02 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 0f60b4e9df3a7..e80bd4f23b3f1 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: 2023-02-27 +date: 2023-03-02 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 a521051843765..2d968671567c2 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedHistogram'] --- import unifiedHistogramObj from './unified_histogram.devdocs.json'; diff --git a/api_docs/unified_search.devdocs.json b/api_docs/unified_search.devdocs.json index fb9811aaea206..d0f779daec6fb 100644 --- a/api_docs/unified_search.devdocs.json +++ b/api_docs/unified_search.devdocs.json @@ -377,7 +377,7 @@ "section": "def-common.Filter", "text": "Filter" }, - "[] | undefined; dataTestSubj?: string | undefined; indexPatterns?: ", + "[] | undefined; dataTestSubj?: string | undefined; isLoading?: boolean | undefined; indexPatterns?: ", { "pluginId": "dataViews", "scope": "common", @@ -385,7 +385,7 @@ "section": "def-common.DataView", "text": "DataView" }, - "[] | undefined; isDisabled?: boolean | undefined; isLoading?: boolean | undefined; timeHistory?: ", + "[] | undefined; isDisabled?: boolean | undefined; timeHistory?: ", { "pluginId": "data", "scope": "public", @@ -1937,9 +1937,9 @@ "Omit", ", \"options\" | \"onChange\" | \"selectedOptions\" | \"isLoading\" | \"onSearchChange\">, \"placeholder\"> & Required, \"options\" | \"onChange\" | \"isLoading\" | \"selectedOptions\" | \"onSearchChange\">, \"placeholder\"> & Required, \"options\" | \"onChange\" | \"selectedOptions\" | \"isLoading\" | \"onSearchChange\">, \"placeholder\">> & { onChange: (indexPatternId?: string | undefined) => void; indexPatternId: string; onNoIndexPatterns?: (() => void) | undefined; }" + ", \"options\" | \"onChange\" | \"isLoading\" | \"selectedOptions\" | \"onSearchChange\">, \"placeholder\">> & { onChange: (indexPatternId?: string | undefined) => void; indexPatternId: string; onNoIndexPatterns?: (() => void) | undefined; }" ], "path": "src/plugins/unified_search/public/index_pattern_select/index_pattern_select.tsx", "deprecated": false, diff --git a/api_docs/unified_search.mdx b/api_docs/unified_search.mdx index a9a3f8bb2ca79..7ce7f9776a0df 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: 2023-02-27 +date: 2023-03-02 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 22f36ce73aeea..11bf3eca20206 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: 2023-02-27 +date: 2023-03-02 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 79040e0c7cdd2..00a2a9c375867 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: 2023-02-27 +date: 2023-03-02 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 284a2037ab90b..6ecd92065cad1 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: 2023-02-27 +date: 2023-03-02 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 6fd75ea172182..a384f9f7367b6 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: 2023-02-27 +date: 2023-03-02 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 0be33385141aa..edd9382b98348 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: 2023-02-27 +date: 2023-03-02 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 2c3d8f2e3aeb1..659f1fde7adde 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: 2023-02-27 +date: 2023-03-02 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 c9cbbb1faa746..205dc8e481f17 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeHeatmap'] --- import visTypeHeatmapObj from './vis_type_heatmap.devdocs.json'; diff --git a/api_docs/vis_type_pie.mdx b/api_docs/vis_type_pie.mdx index aa93bc6b4f587..6764e12e55a9b 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypePie'] --- import visTypePieObj from './vis_type_pie.devdocs.json'; diff --git a/api_docs/vis_type_table.mdx b/api_docs/vis_type_table.mdx index 70d76d4c300bb..cb488e139d52e 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: 2023-02-27 +date: 2023-03-02 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 0b2176aad94b4..d962ffe6dec17 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: 2023-02-27 +date: 2023-03-02 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 4312898bd792a..09b5222b7d782 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: 2023-02-27 +date: 2023-03-02 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 5f30340988ad6..e2376c3a4225d 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeVega'] --- import visTypeVegaObj from './vis_type_vega.devdocs.json'; diff --git a/api_docs/vis_type_vislib.mdx b/api_docs/vis_type_vislib.mdx index 9f1265bde99de..a503f4fa09f78 100644 --- a/api_docs/vis_type_vislib.mdx +++ b/api_docs/vis_type_vislib.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeVislib title: "visTypeVislib" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeVislib plugin -date: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeVislib'] --- import visTypeVislibObj from './vis_type_vislib.devdocs.json'; diff --git a/api_docs/vis_type_xy.mdx b/api_docs/vis_type_xy.mdx index fae5f15be6a53..a95ce8496e401 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: 2023-02-27 +date: 2023-03-02 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 ed906cba8ff0f..22c23857d5ec5 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: 2023-02-27 +date: 2023-03-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visualizations'] --- import visualizationsObj from './visualizations.devdocs.json'; diff --git a/dev_docs/tutorials/versioning_interfaces.mdx b/dev_docs/tutorials/versioning_interfaces.mdx new file mode 100644 index 0000000000000..d8ffdb333294a --- /dev/null +++ b/dev_docs/tutorials/versioning_interfaces.mdx @@ -0,0 +1,442 @@ +--- +id: kibDevTutorialVersioningInterfaces +slug: /kibana-dev-docs/versioning-interfaces +title: Versioning interfaces +description: We need to keep old versions of interfaces available. This tutorial describes a strategy for managing versions of your interfaces over time. +date: 2023-02-09 +tags: ['kibana', 'onboarding', 'dev', 'architecture'] +--- + +To support versioned APIs we need to keep past versions of interfaces around. This tutorial presents one strategy to manage your interfaces. + +## The strategy + +Every plugin has a _domain_. A domain consists of one or more concepts, usually objects, that are related in some logical way. + +At a high level the strategy for versioning our interfaces is: + +> Version a collection of related interfaces together. Whenever a single interface changes, increment the version of the entire collection of interfaces. + +Characteristics of this strategy: + +1. Avoid the `extends` keyword for referencing past interfaces. This results in cleaner API docs being generated. +2. Leverage `* as` to create versioned namespaces rather than versioned interfaces. +3. Verbosity is intentional: for a single change in a collection, we create a new version of the related collection. + +### An example: Weather insights unversioned +Consider a fictional Kibana plugin called "Weather insights". It allows users to easily create and curate dashboards that house visualisations and metrics for weather data. The domain contains the following: + +1. **Data source**: an endpoint that is scraped and ingested into ES for aggregation and search. +2. **Weather dashboard**: a set of visualisations and metrics that draws from a data source. + +We have the following `common` interfaces: + +```ts +export interface DataSource { + /** Some URL that provides weather data in an expected format for scraping */ + url: string; + /** The data view data is stored in */ + dataViewId: string; + /** Username to use with source URL */ + username?: string; + /** Password to use with source URL */ + password?: string; +} + +/** Fictional type, only used for this example as an external type */ +import type { PortableDashboard } from '@kbn/dashboard/common'; + +export interface WeatherDashboard { + /** A unique ID for this weather dashboard */ + id: string; + /** Definition of a dashboard imported from the `dashboard` plugin */ + dashboard: PortableDashboard; + /** Data source to use with this dashboard */ + source: DataSource; +} + +type UnsavedWeatherDashboard = Omit; + +/** Below are the HTTP APIs based on the domain types */ + +export interface GetWeatherDashboardHTTPResponse { + weatherDashboard: WeatherDashboard; +} + +export interface CreateWeatherDashboardHTTPBody { + weatherDashboard: UnsavedWeatherDashboard; +} + +export interface CreateWeatherDashboardHTTPResponse { + weatherDashboard: WeatherDashboard; +} + +// Imagine this pattern for the update and list endpoints too. +``` + +These interfaces might be in use by an unversioned HTTP route such as: + +```ts +import type { GetWeatherDashboardHTTPResponse } from '../common'; + +const handler = (ctx, req, res) => { + const body: GetWeatherDashboardHTTPResponse = { ... }; + return res.ok({ body }); +} +``` + +### An example: Weather insights versioned + +The following product requirements are added to the Weather insights plugin: + +1. Data sources need a more user-friendly `name`, currently we just show a URL +2. Some users have said they'd like to give data sources a short `description` too +3. New feature: **Predictions** that will generate forecasts based on past and current weather data + +Before we make these changes, we need to make sure that our current interfaces are not lost. + +#### Preparing our interfaces for versions + +We are going to make our current set of interfaces `v1`. Let's reflect this in our code by creating this file structure in `common`: + +``` +common + index.ts + latest.ts + data_source + v1.ts + weather_dashboard + v1.ts +public + ... +server + ... +``` + +By placing `DataSource` and `WeatherDashboard` in two folders we indicate that they _could_ version independently. Specifically, +`WeatherDashboard` may version independently of `DataSource`. + +**Note:** At this stage, splitting these concepts may be overkill if we expect `WeatherDashboard` to _rarely_ change, but for this example we would like the ability to version them separately. + +The `latest.ts` file is going to act as an alias to the latest set of interfaces. It will look like this: + +```ts +// "export *" is considered safe here because we ONLY have types in these files. +export * from './data_source/v1'; +export * from './weather_data/v1'; +``` + +Our `index.ts` file will contain: + +```ts +// Explicit export of everything from latest +export { DataSource, WeatherDashboard, GetWeatherDashboardHTTPResponse } from './latest'; + +export * as weatherDashboardV1 from './weather_dashboard/v1'; +export * as dataSourceV1 from './data_source/v1'; +``` + +How we use these types depends on whether the code is aware of versions or not. Generally, code that is aware of multiple versions will lead to greater complexity. We might have a logger function that expects the latest dashboards: + +```ts +// This type is being pulled from "latest", so whenever a new latest is linked, +// it will also point to the latest WeatherDashboard. +import type { WeatherDashboard } from '../common'; + +function logIt(dashboard: WeatherDashboard) { + logger.debug(dashboard); +} +``` + +Or we might have a route handler that expects a specific version our domain objects: + +```ts +// Now we bring it all together by using our versioned types in our handler from before: +import type { weatherDashboardV1 } from '../common'; + +const handler = (ctx, req, res) => { + const body: weatherDashboardV1.GetWeatherDashboardHTTPResponse = { ... }; + return res.ok({ body }); +} +``` + + + By referencing types linked from `latest.ts` we will not have to refactor all our app code to point to the latest version of `WeatherDashboard`. This happens by simply re-exporting the newest types from `latest.ts`. Otherwise, code that needs to know about a specific version or versions will have access to it by using the appropriate namespace. + + +### Introducing a new field to `DataSource` + +Now that we have prepared our interfaces to be versioned, let's introduce the `name` field to `DataSource`. + +Create a new `v2.ts` file under the `data_source` directory: + +``` +common + data_source + v1.ts + v2.ts +``` + +The `v2.ts` file will contain: + +```ts +export interface DataSource { + /** Some URL that provides weather data in an expected format for scraping */ + url: string; + /** A user-friendly name */ + name: string; + /** A longer description of this data source */ + description?: string; + /** The data view data is stored in */ + dataViewId: string; + /** Username to use with source URL */ + username?: string; + /** Password to use with source URL */ + password?: string; +} +``` + + + If we had only added the optional `description` field to `DataSource` this would be a backwards-compatible change that does not require a new version. We could have remained on `v1` of the `DataSource` interface. + + +We also need to update the `WeatherDashboard` interface to use our new `DataSource` interface: + +``` +common + weather_dashboard + v1.ts + v2.ts +``` + +```ts +// common/weather_dashboard/v2.ts + +// This is largely a copy of v1.ts, but using a new WeatherDashboard type in all +// the relevant places + +/** Fictional type, only used for this example as an external type */ +import type { PortableDashboard } from '@kbn/dashboard/common'; +import type { DataSource } from '../data_source/v2'; + +// Optionally, re-export the entire set of types. Interfaces and types declared after this will override v1 declarations. +export * from './v1'; + +export interface WeatherDashboard { + /** A unique ID for this weather dashboard */ + id: string; + /** Definition of a dashboard imported from the `dashboard` plugin */ + dashboard: PortableDashboard; + /** Data source to use with this dashboard */ + source: DataSource; +} + +type UnsavedWeatherDashboard = Omit; + +/** Below are the HTTP APIs based on the domain types */ + +export interface GetWeatherDashboardHTTPResponse { + weatherDashboard: WeatherDashboard; +} + +export interface CreateWeatherDashboardHTTPBody { + weatherDashboard: UnsavedWeatherDashboard; +} + +export interface CreateWeatherDashboardHTTPResponse { + weatherDashboard: WeatherDashboard; +} + +// Imagine this pattern for the update and list endpoints too. + +``` + + + It is possible to add `export * from './v1';` to the top of our `weather_dashboard/v2.ts` file to re-export unchanged types and avoid copying the whole file. + + However, copying and adapting the entire set of types is OK too. If we have correctly grouped our sub-domains we should be versioning all collections of types together. + + +Now we update the `latest.ts` and `index.ts` files accordingly: + +```ts +// latest.ts +export * from './data_source/v2'; +export * from './weather_dashboard/v2'; +``` + +```ts +// index.ts +// Explicit export of everything from latest +export { DataSource, WeatherDashboard, GetWeatherDashboardHTTPResponse } from './latest'; + +export * as weatherDashboardV1 from './weather_dashboard/v1'; +export * as dataSourceV1 from './data_source/v1'; + +export * as weatherDashboardV2 from './weather_dashboard/v2'; +export * as dataSourceV2 from './data_source/v2'; +``` + +### Introduce a new sub-domain: `Prediction` + +For our second product requirement we are going to introduce a new concept. Let's start by updating our folder structure: + +``` +common + index.ts + latest.ts + data_source + weather_dashboard + prediction + v1.ts +``` + +```ts +// common/prediction/v1.ts + +import type { DataSource } from '../data_source/v2'; + +/** Random set of weather models, just for illustration */ +type Model = 'GFS' | 'ECMWF' | 'GEM'; + +/** Limited set of options rather than "string" so we can more easily version */ +type TimeFrame = '1d' | '2d' | '3d' | '4d' | '5d' | '6d' | '1w' | '2w'; + +export interface PredictionInput { + /** How far back we should look in our data */ + start_date: string; // !! Introduction of inconsistent snake case naming convention, will need a new version to fix + /** How far out to predict. Note, longer time frames result in lower accuracy */ + timeFrame: TimeFrame; + /** Choice of weather model to use */ + model: Model; + /** Data source for this prediction */ + dataSource: DataSource; +} + +export interface PredictionOutput { ... } + +// Below are HTTP APIs for predictions + +export interface CreatePredictionHTTPBody { + input: PredictionInput; +} + +export interface CreatePredictionHTTPResponse { + result: PredictionOutput; +} + +// And so forth... +``` + + + Both `WeatherDashboard` and `Prediction` reference `DataSource`. Therefore, both will increment their version whenever `DataSource` changes. + + +## Challenge + +Normalize our domain objects by only holding a reference to `DataSource` in `WeatherDashboard` and `Prediction` domain objects. + +
+ Solution +
+ 1. We will add a way to uniquely identify a `DataSource` + 2. `DataSource` is currently specified in both `WeatherDashboard` and `Prediction`, therefore both will need to be updated as well. + + Let's start by updating our folder structure to introduce the new versions. + + ``` + common + data_source + v1.ts + v2.ts + v3.ts + weather_dashboard + v1.ts + v2.ts + v3.ts + prediction + v1.ts + v2.ts + ``` + + Now, let's add our new types : + + ```ts + // common/data_source/v3.ts + export interface DataSource { + /** A string that uniquely identifies a DataSource */ + id: string; + /** Some URL that provides weather data in an expected format for scraping */ + url: string; + /** A user-friendly name */ + name: string; + /** A longer description of this data source */ + description?: string; + /** The data view data is stored in */ + dataViewId: string; + /** Username to use with source URL */ + username?: string; + /** Password to use with source URL */ + password?: string; + } + // ...followed by all other HTTP related interfaces + + // common/weather_dashboard/v3.ts + export interface WeatherDashboard { + /** A unique ID for this weather dashboard */ + id: string; + /** Definition of a dashboard imported from the `dashboard` plugin */ + dashboard: PortableDashboard; + /** ID of the data source to use with this dashboard */ + dataSourceId: string; + } + // ...followed by all other HTTP related interfaces + + // common/prediction/v3.ts + export interface PredictionInput { + /** How far back we should look in our data */ + startDate: string; // Fixed inconsistent naming + /** How far out to predict. Note, longer time frames result in lower accuracy */ + timeFrame: TimeFrame; + /** Choice of weather model to use */ + model: Model; + /** Data source for this prediction */ + dataSourceId: string; + } + // ...followed by all other HTTP related interfaces + ``` + + Next let's update `latest.ts` and `index.ts`: + + ```ts + // latest.ts + export * from './data_source/v3'; + export * from './weather_dashboard/v3'; + export * from './prediction/v3'; + + // index.ts + export { DataSource, WeatherDashboard, GetWeatherDashboardHTTPResponse, Prediction } from './latest'; + export * as weatherDashboardV1 from './weather_dashboard/v1'; + export * as dataSourceV1 from './data_source/v1'; + export * as predictionV1 from './prediction/v1'; + + export * as weatherDashboardV2 from './weather_dashboard/v2'; + export * as dataSourceV2 from './data_source/v2'; + export * as predictionV2 from './prediction/v2'; + + export * as weatherDashboardV3 from './weather_dashboard/v3'; + export * as dataSourceV3 from './data_source/v3'; + ``` + + Refactoring interfaces in this way creates a much larger maintenance burden because: + + 1. application code must now translate between the denormalized and normalized versions of these interfaces + 2. code must remember to support both old and new naming conventions + + ...for as long as these APIs are in use. We will not cover deprecation strategies in this tutorial (incoming). Sufficed to + say: _take care when designing public APIs_. +
+ +## Additional resources + +* [PR to prepare Saved Objects Management](https://github.com/elastic/kibana/pull/149495) for versioning \ No newline at end of file diff --git a/docs/api-generated/cases/case-apis-passthru.asciidoc b/docs/api-generated/cases/case-apis-passthru.asciidoc index d6d5b0d589ac1..1d22d5cd5a906 100644 --- a/docs/api-generated/cases/case-apis-passthru.asciidoc +++ b/docs/api-generated/cases/case-apis-passthru.asciidoc @@ -24,17 +24,17 @@ Any modifications made to this file will be overwritten.
  • delete /s/{spaceId}/api/cases/{caseId}/comments/{commentId}
  • delete /s/{spaceId}/api/cases/{caseId}/comments
  • get /s/{spaceId}/api/cases/{caseId}/user_actions/_find
  • +
  • get /s/{spaceId}/api/cases/configure/connectors/_find
  • +
  • get /s/{spaceId}/api/cases/_find
  • get /s/{spaceId}/api/cases/{caseId}/comments
  • get /s/{spaceId}/api/cases/{caseId}
  • get /s/{spaceId}/api/cases/{caseId}/user_actions
  • get /s/{spaceId}/api/cases/{caseId}/alerts
  • get /s/{spaceId}/api/cases/{caseId}/comments/{commentId}
  • get /s/{spaceId}/api/cases/configure
  • -
  • get /s/{spaceId}/api/cases/configure/connectors/_find
  • get /s/{spaceId}/api/cases/reporters
  • get /s/{spaceId}/api/cases/status
  • get /s/{spaceId}/api/cases/tags
  • -
  • get /s/{spaceId}/api/cases/_find
  • get /s/{spaceId}/api/cases/alerts/{alertId}
  • post /s/{spaceId}/api/cases/{caseId}/connector/{connectorId}/_push
  • post /s/{spaceId}/api/cases/configure
  • @@ -530,12 +530,270 @@ Any modifications made to this file will be overwritten. 4xx_response
    +
    +
    + Up +
    get /s/{spaceId}/api/cases/configure/connectors/_find
    +
    Retrieves information about connectors. (findCaseConnectors)
    +
    In particular, only the connectors that are supported for use in cases are returned. You must have read privileges for the Actions and Connectors feature in the Management section of the Kibana feature privileges.
    + +

    Path parameters

    +
    +
    spaceId (required)
    + +
    Path Parameter — An identifier for the space. If /s/ and the identifier are omitted from the path, the default space is used. default: null
    +
    + + + + + + +

    Return type

    + + + + +

    Example data

    +
    Content-Type: application/json
    +
    {
    +  "isPreconfigured" : true,
    +  "isDeprecated" : true,
    +  "actionTypeId" : ".none",
    +  "referencedByCount" : 0,
    +  "name" : "name",
    +  "id" : "id",
    +  "config" : {
    +    "projectKey" : "projectKey",
    +    "apiUrl" : "apiUrl"
    +  },
    +  "isMissingSecrets" : true
    +}
    + +

    Produces

    + This API call produces the following media types according to the Accept request header; + the media type will be conveyed by the Content-Type response header. +
      +
    • application/json
    • +
    + +

    Responses

    +

    200

    + Indicates a successful call. + +

    401

    + Authorization information is missing or invalid. + 4xx_response +
    +
    +
    +
    + Up +
    get /s/{spaceId}/api/cases/_find
    +
    Retrieves a paginated subset of cases. (findCases)
    +
    You must have read privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the cases you're seeking.
    + +

    Path parameters

    +
    +
    spaceId (required)
    + +
    Path Parameter — An identifier for the space. If /s/ and the identifier are omitted from the path, the default space is used. default: null
    +
    + + + + +

    Query parameters

    +
    +
    assignees (optional)
    + +
    Query Parameter — Filters the returned cases by assignees. Valid values are none or unique identifiers for the user profiles. These identifiers can be found by using the suggest user profile API. default: null
    defaultSearchOperator (optional)
    + +
    Query Parameter — The default operator to use for the simple_query_string. default: OR
    fields (optional)
    + +
    Query Parameter — The fields in the entity to return in the response. default: null
    from (optional)
    + +
    Query Parameter — [preview] Returns only cases that were created after a specific date. The date must be specified as a KQL data range or date match expression. This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. default: null
    owner (optional)
    + +
    Query Parameter — A filter to limit the response to a specific set of applications. If this parameter is omitted, the response contains information about all the cases that the user has access to read. default: null
    page (optional)
    + +
    Query Parameter — The page number to return. default: 1
    perPage (optional)
    + +
    Query Parameter — The number of cases to return per page. default: 20
    reporters (optional)
    + +
    Query Parameter — Filters the returned cases by the user name of the reporter. default: null
    search (optional)
    + +
    Query Parameter — An Elasticsearch simple_query_string query that filters the objects in the response. default: null
    searchFields (optional)
    + +
    Query Parameter — The fields to perform the simple_query_string parsed query against. default: null
    severity (optional)
    + +
    Query Parameter — The severity of the case. default: null
    sortField (optional)
    + +
    Query Parameter — Determines which field is used to sort the results. default: createdAt
    sortOrder (optional)
    + +
    Query Parameter — Determines the sort order. default: desc
    status (optional)
    + +
    Query Parameter — Filters the returned cases by state. default: null
    tags (optional)
    + +
    Query Parameter — Filters the returned cases by tags. default: null
    to (optional)
    + +
    Query Parameter — [preview] Returns only cases that were created before a specific date. The date must be specified as a KQL data range or date match expression. This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. default: null
    +
    + + +

    Return type

    + + + + +

    Example data

    +
    Content-Type: application/json
    +
    {
    +  "count_in_progress_cases" : 6,
    +  "per_page" : 5,
    +  "total" : 2,
    +  "cases" : [ {
    +    "owner" : "cases",
    +    "totalComment" : 0,
    +    "settings" : {
    +      "syncAlerts" : true
    +    },
    +    "totalAlerts" : 0,
    +    "closed_at" : "2000-01-23T04:56:07.000+00:00",
    +    "comments" : [ null, null ],
    +    "assignees" : [ {
    +      "uid" : "u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0"
    +    }, {
    +      "uid" : "u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0"
    +    } ],
    +    "created_at" : "2022-05-13T09:16:17.416Z",
    +    "description" : "A case description.",
    +    "title" : "Case title 1",
    +    "created_by" : {
    +      "full_name" : "full_name",
    +      "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
    +      "email" : "email",
    +      "username" : "elastic"
    +    },
    +    "version" : "WzUzMiwxXQ==",
    +    "closed_by" : {
    +      "full_name" : "full_name",
    +      "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
    +      "email" : "email",
    +      "username" : "elastic"
    +    },
    +    "tags" : [ "tag-1" ],
    +    "duration" : 120,
    +    "updated_at" : "2000-01-23T04:56:07.000+00:00",
    +    "updated_by" : {
    +      "full_name" : "full_name",
    +      "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
    +      "email" : "email",
    +      "username" : "elastic"
    +    },
    +    "id" : "66b9aa00-94fa-11ea-9f74-e7e108796192",
    +    "external_service" : {
    +      "external_title" : "external_title",
    +      "pushed_by" : {
    +        "full_name" : "full_name",
    +        "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
    +        "email" : "email",
    +        "username" : "elastic"
    +      },
    +      "external_url" : "external_url",
    +      "pushed_at" : "2000-01-23T04:56:07.000+00:00",
    +      "connector_id" : "connector_id",
    +      "external_id" : "external_id",
    +      "connector_name" : "connector_name"
    +    }
    +  }, {
    +    "owner" : "cases",
    +    "totalComment" : 0,
    +    "settings" : {
    +      "syncAlerts" : true
    +    },
    +    "totalAlerts" : 0,
    +    "closed_at" : "2000-01-23T04:56:07.000+00:00",
    +    "comments" : [ null, null ],
    +    "assignees" : [ {
    +      "uid" : "u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0"
    +    }, {
    +      "uid" : "u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0"
    +    } ],
    +    "created_at" : "2022-05-13T09:16:17.416Z",
    +    "description" : "A case description.",
    +    "title" : "Case title 1",
    +    "created_by" : {
    +      "full_name" : "full_name",
    +      "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
    +      "email" : "email",
    +      "username" : "elastic"
    +    },
    +    "version" : "WzUzMiwxXQ==",
    +    "closed_by" : {
    +      "full_name" : "full_name",
    +      "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
    +      "email" : "email",
    +      "username" : "elastic"
    +    },
    +    "tags" : [ "tag-1" ],
    +    "duration" : 120,
    +    "updated_at" : "2000-01-23T04:56:07.000+00:00",
    +    "updated_by" : {
    +      "full_name" : "full_name",
    +      "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
    +      "email" : "email",
    +      "username" : "elastic"
    +    },
    +    "id" : "66b9aa00-94fa-11ea-9f74-e7e108796192",
    +    "external_service" : {
    +      "external_title" : "external_title",
    +      "pushed_by" : {
    +        "full_name" : "full_name",
    +        "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
    +        "email" : "email",
    +        "username" : "elastic"
    +      },
    +      "external_url" : "external_url",
    +      "pushed_at" : "2000-01-23T04:56:07.000+00:00",
    +      "connector_id" : "connector_id",
    +      "external_id" : "external_id",
    +      "connector_name" : "connector_name"
    +    }
    +  } ],
    +  "count_open_cases" : 1,
    +  "count_closed_cases" : 0,
    +  "page" : 5
    +}
    + +

    Produces

    + This API call produces the following media types according to the Accept request header; + the media type will be conveyed by the Content-Type response header. +
      +
    • application/json
    • +
    + +

    Responses

    +

    200

    + Indicates a successful call. + findCases_200_response +

    401

    + Authorization information is missing or invalid. + 4xx_response +
    +
    Up
    get /s/{spaceId}/api/cases/{caseId}/comments
    Retrieves all the comments from a case. (getAllCaseComments)
    -
    You must have read privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the cases with the comments you're seeking.
    +
    Deprecated in 8.1.0. This API is deprecated and will be removed in a future release; instead, use the get case comment API, which requires a comment identifier in the path. You must have read privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the cases with the comments you're seeking.

    Path parameters

    @@ -656,7 +914,7 @@ Any modifications made to this file will be overwritten.
    includeComments (optional)
    -
    Query Parameter — Determines whether case comments are returned. default: true
    +
    Query Parameter — Deprecated in 8.1.0. This parameter is deprecated and will be removed in a future release. It determines whether case comments are returned. default: true
    @@ -747,7 +1005,7 @@ Any modifications made to this file will be overwritten. Up
    get /s/{spaceId}/api/cases/{caseId}/user_actions
    Returns all user activity for a case. (getCaseActivity)
    -
    You must have read privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the case you're seeking.
    +
    Deprecated in 8.1.0. This API is deprecated and will be removed in a future release; use the find user actions API instead. You must have read privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the case you're seeking.

    Path parameters

    @@ -997,65 +1255,6 @@ Any modifications made to this file will be overwritten. 4xx_response

    -
    -
    - Up -
    get /s/{spaceId}/api/cases/configure/connectors/_find
    -
    Retrieves information about connectors. (getCaseConnectors)
    -
    In particular, only the connectors that are supported for use in cases are returned. You must have read privileges for the Actions and Connectors feature in the Management section of the Kibana feature privileges.
    - -

    Path parameters

    -
    -
    spaceId (required)
    - -
    Path Parameter — An identifier for the space. If /s/ and the identifier are omitted from the path, the default space is used. default: null
    -
    - - - - - - -

    Return type

    - - - - -

    Example data

    -
    Content-Type: application/json
    -
    {
    -  "isPreconfigured" : true,
    -  "isDeprecated" : true,
    -  "actionTypeId" : ".none",
    -  "referencedByCount" : 0,
    -  "name" : "name",
    -  "id" : "id",
    -  "config" : {
    -    "projectKey" : "projectKey",
    -    "apiUrl" : "apiUrl"
    -  },
    -  "isMissingSecrets" : true
    -}
    - -

    Produces

    - This API call produces the following media types according to the Accept request header; - the media type will be conveyed by the Content-Type response header. -
      -
    • application/json
    • -
    - -

    Responses

    -

    200

    - Indicates a successful call. - -

    401

    - Authorization information is missing or invalid. - 4xx_response -
    -
    Up @@ -1117,66 +1316,9 @@ Any modifications made to this file will be overwritten.
    Up -
    get /s/{spaceId}/api/cases/status
    -
    Returns the number of cases that are open, closed, and in progress. (getCaseStatus)
    -
    You must have read privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the cases you're seeking.
    - -

    Path parameters

    -
    -
    spaceId (required)
    - -
    Path Parameter — An identifier for the space. If /s/ and the identifier are omitted from the path, the default space is used. default: null
    -
    - - - - -

    Query parameters

    -
    -
    owner (optional)
    - -
    Query Parameter — A filter to limit the response to a specific set of applications. If this parameter is omitted, the response contains information about all the cases that the user has access to read. default: null
    -
    - - -

    Return type

    - - - - -

    Example data

    -
    Content-Type: application/json
    -
    {
    -  "count_in_progress_cases" : 6,
    -  "count_open_cases" : 1,
    -  "count_closed_cases" : 0
    -}
    - -

    Produces

    - This API call produces the following media types according to the Accept request header; - the media type will be conveyed by the Content-Type response header. -
      -
    • application/json
    • -
    - -

    Responses

    -

    200

    - Indicates a successful call. - getCaseStatus_200_response -

    401

    - Authorization information is missing or invalid. - 4xx_response -
    -
    -
    -
    - Up -
    get /s/{spaceId}/api/cases/tags
    -
    Aggregates and returns a list of case tags. (getCaseTags)
    -
    You must have read privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the cases you're seeking.
    +
    get /s/{spaceId}/api/cases/status
    +
    Returns the number of cases that are open, closed, and in progress. (getCaseStatus)
    +
    Deprecated in 8.1.0. This API is deprecated and will be removed in a future release; use the find cases API instead. You must have read privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the cases you're seeking.

    Path parameters

    @@ -1192,21 +1334,25 @@ Any modifications made to this file will be overwritten.
    owner (optional)
    -
    Query Parameter — A filter to limit the retrieved case statistics to a specific set of applications. If this parameter is omitted, the response contains tags from all cases that the user has access to read. default: null
    +
    Query Parameter — A filter to limit the response to a specific set of applications. If this parameter is omitted, the response contains information about all the cases that the user has access to read. default: null

    Return type

    + getCaseStatus_200_response - array[String]

    Example data

    Content-Type: application/json
    -
    ""
    +
    {
    +  "count_in_progress_cases" : 6,
    +  "count_open_cases" : 1,
    +  "count_closed_cases" : 0
    +}

    Produces

    This API call produces the following media types according to the Accept request header; @@ -1218,18 +1364,18 @@ Any modifications made to this file will be overwritten.

    Responses

    200

    Indicates a successful call. - + getCaseStatus_200_response

    401

    Authorization information is missing or invalid. 4xx_response

    -
    +
    Up -
    get /s/{spaceId}/api/cases/_find
    -
    Retrieves a paginated subset of cases. (getCases)
    -
    You must have read privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the cases you're seeking.
    +
    get /s/{spaceId}/api/cases/tags
    +
    Aggregates and returns a list of case tags. (getCaseTags)
    +
    You must have read privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the cases you're seeking.

    Path parameters

    @@ -1243,169 +1389,23 @@ Any modifications made to this file will be overwritten.

    Query parameters

    -
    assignees (optional)
    - -
    Query Parameter — Filters the returned cases by assignees. Valid values are none or unique identifiers for the user profiles. These identifiers can be found by using the suggest user profile API. default: null
    defaultSearchOperator (optional)
    - -
    Query Parameter — The default operator to use for the simple_query_string. default: OR
    fields (optional)
    - -
    Query Parameter — The fields in the entity to return in the response. default: null
    from (optional)
    - -
    Query Parameter — [preview] Returns only cases that were created after a specific date. The date must be specified as a KQL data range or date match expression. This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. default: null
    owner (optional)
    - -
    Query Parameter — A filter to limit the response to a specific set of applications. If this parameter is omitted, the response contains information about all the cases that the user has access to read. default: null
    page (optional)
    - -
    Query Parameter — The page number to return. default: 1
    perPage (optional)
    - -
    Query Parameter — The number of cases to return per page. default: 20
    reporters (optional)
    - -
    Query Parameter — Filters the returned cases by the user name of the reporter. default: null
    search (optional)
    - -
    Query Parameter — An Elasticsearch simple_query_string query that filters the objects in the response. default: null
    searchFields (optional)
    - -
    Query Parameter — The fields to perform the simple_query_string parsed query against. default: null
    severity (optional)
    - -
    Query Parameter — The severity of the case. default: null
    sortField (optional)
    - -
    Query Parameter — Determines which field is used to sort the results. default: createdAt
    sortOrder (optional)
    - -
    Query Parameter — Determines the sort order. default: desc
    status (optional)
    - -
    Query Parameter — Filters the returned cases by state. default: null
    tags (optional)
    - -
    Query Parameter — Filters the returned cases by tags. default: null
    to (optional)
    +
    owner (optional)
    -
    Query Parameter — [preview] Returns only cases that were created before a specific date. The date must be specified as a KQL data range or date match expression. This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. default: null
    +
    Query Parameter — A filter to limit the retrieved case statistics to a specific set of applications. If this parameter is omitted, the response contains tags from all cases that the user has access to read. default: null

    Return type

    - getCases_200_response + array[String]

    Example data

    Content-Type: application/json
    -
    {
    -  "count_in_progress_cases" : 6,
    -  "per_page" : 5,
    -  "total" : 2,
    -  "cases" : [ {
    -    "owner" : "cases",
    -    "totalComment" : 0,
    -    "settings" : {
    -      "syncAlerts" : true
    -    },
    -    "totalAlerts" : 0,
    -    "closed_at" : "2000-01-23T04:56:07.000+00:00",
    -    "comments" : [ null, null ],
    -    "assignees" : [ {
    -      "uid" : "u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0"
    -    }, {
    -      "uid" : "u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0"
    -    } ],
    -    "created_at" : "2022-05-13T09:16:17.416Z",
    -    "description" : "A case description.",
    -    "title" : "Case title 1",
    -    "created_by" : {
    -      "full_name" : "full_name",
    -      "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
    -      "email" : "email",
    -      "username" : "elastic"
    -    },
    -    "version" : "WzUzMiwxXQ==",
    -    "closed_by" : {
    -      "full_name" : "full_name",
    -      "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
    -      "email" : "email",
    -      "username" : "elastic"
    -    },
    -    "tags" : [ "tag-1" ],
    -    "duration" : 120,
    -    "updated_at" : "2000-01-23T04:56:07.000+00:00",
    -    "updated_by" : {
    -      "full_name" : "full_name",
    -      "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
    -      "email" : "email",
    -      "username" : "elastic"
    -    },
    -    "id" : "66b9aa00-94fa-11ea-9f74-e7e108796192",
    -    "external_service" : {
    -      "external_title" : "external_title",
    -      "pushed_by" : {
    -        "full_name" : "full_name",
    -        "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
    -        "email" : "email",
    -        "username" : "elastic"
    -      },
    -      "external_url" : "external_url",
    -      "pushed_at" : "2000-01-23T04:56:07.000+00:00",
    -      "connector_id" : "connector_id",
    -      "external_id" : "external_id",
    -      "connector_name" : "connector_name"
    -    }
    -  }, {
    -    "owner" : "cases",
    -    "totalComment" : 0,
    -    "settings" : {
    -      "syncAlerts" : true
    -    },
    -    "totalAlerts" : 0,
    -    "closed_at" : "2000-01-23T04:56:07.000+00:00",
    -    "comments" : [ null, null ],
    -    "assignees" : [ {
    -      "uid" : "u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0"
    -    }, {
    -      "uid" : "u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0"
    -    } ],
    -    "created_at" : "2022-05-13T09:16:17.416Z",
    -    "description" : "A case description.",
    -    "title" : "Case title 1",
    -    "created_by" : {
    -      "full_name" : "full_name",
    -      "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
    -      "email" : "email",
    -      "username" : "elastic"
    -    },
    -    "version" : "WzUzMiwxXQ==",
    -    "closed_by" : {
    -      "full_name" : "full_name",
    -      "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
    -      "email" : "email",
    -      "username" : "elastic"
    -    },
    -    "tags" : [ "tag-1" ],
    -    "duration" : 120,
    -    "updated_at" : "2000-01-23T04:56:07.000+00:00",
    -    "updated_by" : {
    -      "full_name" : "full_name",
    -      "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
    -      "email" : "email",
    -      "username" : "elastic"
    -    },
    -    "id" : "66b9aa00-94fa-11ea-9f74-e7e108796192",
    -    "external_service" : {
    -      "external_title" : "external_title",
    -      "pushed_by" : {
    -        "full_name" : "full_name",
    -        "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
    -        "email" : "email",
    -        "username" : "elastic"
    -      },
    -      "external_url" : "external_url",
    -      "pushed_at" : "2000-01-23T04:56:07.000+00:00",
    -      "connector_id" : "connector_id",
    -      "external_id" : "external_id",
    -      "connector_name" : "connector_name"
    -    }
    -  } ],
    -  "count_open_cases" : 1,
    -  "count_closed_cases" : 0,
    -  "page" : 5
    -}
    +
    ""

    Produces

    This API call produces the following media types according to the Accept request header; @@ -1417,7 +1417,7 @@ Any modifications made to this file will be overwritten.

    Responses

    200

    Indicates a successful call. - getCases_200_response +

    401

    Authorization information is missing or invalid. 4xx_response @@ -2094,19 +2094,19 @@ Any modifications made to this file will be overwritten.
  • create_case_request_connector -
  • external_service -
  • findCaseActivity_200_response -
  • +
  • findCaseConnectors_200_response_inner -
  • +
  • findCaseConnectors_200_response_inner_config -
  • +
  • findCases_200_response -
  • +
  • findCases_assignees_parameter -
  • +
  • findCases_owner_parameter -
  • getCaseComment_200_response -
  • getCaseConfiguration_200_response_inner -
  • getCaseConfiguration_200_response_inner_connector -
  • getCaseConfiguration_200_response_inner_created_by -
  • getCaseConfiguration_200_response_inner_mappings_inner -
  • getCaseConfiguration_200_response_inner_updated_by -
  • -
  • getCaseConnectors_200_response_inner -
  • -
  • getCaseConnectors_200_response_inner_config -
  • getCaseStatus_200_response -
  • getCasesByAlert_200_response_inner -
  • -
  • getCases_200_response -
  • -
  • getCases_assignees_parameter -
  • -
  • getCases_owner_parameter -
  • owners -
  • payload_alert_comment -
  • payload_alert_comment_comment -
  • @@ -2557,6 +2557,53 @@ Any modifications made to this file will be overwritten.
    userActions (optional)
    +
    +

    findCaseConnectors_200_response_inner - Up

    +
    +
    +
    actionTypeId (optional)
    +
    config (optional)
    +
    id (optional)
    +
    isDeprecated (optional)
    +
    isMissingSecrets (optional)
    +
    isPreconfigured (optional)
    +
    name (optional)
    +
    referencedByCount (optional)
    +
    +
    +
    +

    findCaseConnectors_200_response_inner_config - Up

    +
    +
    +
    apiUrl (optional)
    +
    projectKey (optional)
    +
    +
    +
    +

    findCases_200_response - Up

    +
    +
    +
    cases (optional)
    +
    count_closed_cases (optional)
    +
    count_in_progress_cases (optional)
    +
    count_open_cases (optional)
    +
    page (optional)
    +
    per_page (optional)
    +
    total (optional)
    +
    +
    + +

    getCaseComment_200_response - Up

    @@ -2635,28 +2682,6 @@ Any modifications made to this file will be overwritten.
    profile_uid (optional)
    -
    -

    getCaseConnectors_200_response_inner - Up

    -
    -
    -
    actionTypeId (optional)
    -
    config (optional)
    -
    id (optional)
    -
    isDeprecated (optional)
    -
    isMissingSecrets (optional)
    -
    isPreconfigured (optional)
    -
    name (optional)
    -
    referencedByCount (optional)
    -
    -
    -
    -

    getCaseConnectors_200_response_inner_config - Up

    -
    -
    -
    apiUrl (optional)
    -
    projectKey (optional)
    -
    -

    getCaseStatus_200_response - Up

    @@ -2674,31 +2699,6 @@ Any modifications made to this file will be overwritten.
    title (optional)
    String The case title.
    -
    -

    getCases_200_response - Up

    -
    -
    -
    cases (optional)
    -
    count_closed_cases (optional)
    -
    count_in_progress_cases (optional)
    -
    count_open_cases (optional)
    -
    page (optional)
    -
    per_page (optional)
    -
    total (optional)
    -
    -
    - -

    owners - Up

    The application that owns the cases: Stack Management, Observability, or Elastic Security.
    diff --git a/docs/api/cases.asciidoc b/docs/api/cases.asciidoc index 9ffe69997f714..4caef82f3207b 100644 --- a/docs/api/cases.asciidoc +++ b/docs/api/cases.asciidoc @@ -8,6 +8,7 @@ these APIs: * <> * <> * <> +* <> * <> * <> * <> @@ -33,6 +34,7 @@ include::cases/cases-api-create.asciidoc[leveloffset=+1] include::cases/cases-api-delete-cases.asciidoc[leveloffset=+1] include::cases/cases-api-delete-comments.asciidoc[leveloffset=+1] //FIND +include::cases/cases-api-find-case-activity.asciidoc[leveloffset=+1] include::cases/cases-api-find-cases.asciidoc[leveloffset=+1] include::cases/cases-api-find-connectors.asciidoc[leveloffset=+1] //GET diff --git a/docs/api/cases/cases-api-find-case-activity.asciidoc b/docs/api/cases/cases-api-find-case-activity.asciidoc new file mode 100644 index 0000000000000..e59540c654e28 --- /dev/null +++ b/docs/api/cases/cases-api-find-case-activity.asciidoc @@ -0,0 +1,20 @@ +[[cases-api-find-case-activity]] +== Find case activity API +++++ +Find case activity +++++ + +Finds user activity for a case. + +[NOTE] +==== +For the most up-to-date API details, refer to the +{kib-repo}/tree/{branch}/x-pack/plugins/cases/docs/openapi[open API specification]. For a preview, check out <>. +==== + +=== {api-request-title} + +`GET :/api/cases//user_actions/_find` + +`GET :/s//api/cases//user_actions/_find` + diff --git a/docs/api/cases/cases-api-get-case-activity.asciidoc b/docs/api/cases/cases-api-get-case-activity.asciidoc index da23e845164db..db5835709a6ab 100644 --- a/docs/api/cases/cases-api-get-case-activity.asciidoc +++ b/docs/api/cases/cases-api-get-case-activity.asciidoc @@ -6,7 +6,7 @@ Returns all user activity for a case. -deprecated::[8.1.0] +deprecated::[8.1.0,Use <> instead.] [NOTE] ==== diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 3cdca29fcb49d..f420afb81624d 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -535,7 +535,7 @@ activities. |{kib-repo}blob/{branch}/x-pack/plugins/fleet/README.md[fleet] -|Fleet needs to have Elasticsearch API keys enabled, and also to have TLS enabled on kibana, (if you want to run Kibana without TLS you can provide the following config flag --xpack.fleet.agents.tlsCheckDisabled=false) +|Fleet needs to have Elasticsearch API keys enabled. |{kib-repo}blob/{branch}/x-pack/plugins/global_search/README.md[globalSearch] diff --git a/docs/user/alerting/alerting-getting-started.asciidoc b/docs/user/alerting/alerting-getting-started.asciidoc index e169bcfc24869..b7ca2f3c58b55 100644 --- a/docs/user/alerting/alerting-getting-started.asciidoc +++ b/docs/user/alerting/alerting-getting-started.asciidoc @@ -3,7 +3,7 @@ -- -Alerting allows you to define _rules_ to detect complex conditions within different {kib} apps and trigger actions when those conditions are met. Alerting is integrated with {observability-guide}/create-alerts.html[*Observability*], {security-guide}/prebuilt-rules.html[*Security*], <> and {ml-docs}/ml-configuring-alerts.html[*{ml-app}*], can be centrally managed from the <> UI, and provides a set of built-in <> and <> (known as stack rules) for you to use. +Alerting enables you to define _rules_, which detect complex conditions within different {kib} apps and trigger actions when those conditions are met. Alerting is integrated with {observability-guide}/create-alerts.html[*{observability}*], {security-guide}/prebuilt-rules.html[*Security*], <> and {ml-docs}/ml-configuring-alerts.html[*{ml-app}*]. It can be centrally managed from *{stack-manage-app}* and provides a set of built-in <> and <> for you to use. image::images/alerting-overview.png[{rules-ui} UI] @@ -12,15 +12,12 @@ image::images/alerting-overview.png[{rules-ui} UI] To make sure you can access alerting and actions, see the <> section. ============================================== -[float] -== Concepts and terminology - Alerting works by running checks on a schedule to detect conditions defined by a rule. When a condition is met, the rule tracks it as an _alert_ and responds by triggering one or more _actions_. -Actions typically involve interaction with {kib} services or third party integrations. _Connectors_ allow actions to talk to these services and integrations. +Actions typically involve interaction with {kib} services or third party integrations. _Connectors_ enable actions to talk to these services and integrations. This section describes all of these elements and how they operate together. [float] -=== Rules +== Rules A rule specifies a background task that runs on the {kib} server to check for specific conditions. {kib} provides two types of rules: stack rules that are built into {kib} and the rules that are registered by {kib} apps. For more information, refer to <>. @@ -42,7 +39,7 @@ The following sections describe each part of the rule in more detail. [float] [[alerting-concepts-conditions]] -==== Conditions +=== Conditions Under the hood, {kib} rules detect conditions by running a JavaScript function on the {kib} server, which gives it the flexibility to support a wide range of conditions, anything from the results of a simple {es} query to heavy computations involving data from multiple sources or external systems. @@ -55,58 +52,47 @@ See <> for the rules provided by {kib} and how they express their co [float] [[alerting-concepts-scheduling]] -==== Schedule +=== Schedule Rule schedules are defined as an interval between subsequent checks, and can range from a few seconds to months. [IMPORTANT] ============================================== -The intervals of rule checks in {kib} are approximate. Their timing is affected by factors such as the frequency at which tasks are claimed and the task load on the system. Refer to <> for more information. +The intervals of rule checks in {kib} are approximate. Their timing is affected by factors such as the frequency at which tasks are claimed and the task load on the system. Refer to <> for more information. ============================================== [float] [[alerting-concepts-actions]] -==== Actions +=== Actions -Actions are invocations of connectors, which allow interaction with {kib} services or integrations with third-party systems. Actions run as background tasks on the {kib} server when rule conditions are met. +Actions run as background tasks on the {kib} server when rule conditions are met. Recovery actions likewise run when rule conditions are no longer met. They send notifications by connecting with services inside {kib} or integrating with third-party systems. When defining actions in a rule, you specify: -* The _connector type_: the type of service or integration to use -* The connection for that type by referencing a <> +* A connector +* An action frequency * A mapping of rule values to properties exposed for that type of action -The result is a template: all the parameters needed to invoke a service are supplied except for specific values that are only known at the time the rule condition is detected. +Rather than repeatedly entering connection information and credentials for each action, {kib} simplifies action setup using <>. For example if four rules send email notifications via the same SMTP service, they can all reference the same SMTP connector. -In the server monitoring example, the `email` connector type is used, and `server` is mapped to the body of the email, using the template string `CPU on {{server}} is high`. +The _action frequency_ defines when the action runs (for example, only when the alert status changes or at specific time intervals). Each rule type also has a set of the _action groups_ that affects when the action runs (for example, when the threshold is met or when the alert is recovered). If you want to reduce the number of notifications you receive without affecting their timeliness, some rule types support alert summaries. You can set the action frequency such that you receive notifications that summarize the new, ongoing, and recovered alerts at your preferred time intervals. -When the rule detects the condition, it creates an <> containing the details of the condition, renders the template with these details such as server name, and runs the action on the {kib} server by invoking the `email` connector type. +Each action definition is therefore a template: all the parameters needed to invoke a service are supplied except for specific values that are only known at the time the rule condition is detected. -image::images/what-is-an-action.svg[Actions are like templates that are rendered when an alert detects a condition] +In the server monitoring example, the `email` connector type is used, and `server` is mapped to the body of the email, using the template string `CPU on {{server}} is high`. -See <> for details on the types of connectors provided by {kib}. +When the rule detects the condition, it creates an alert containing the details of the condition. [float] [[alerting-concepts-alerts]] -=== Alerts +== Alerts -When checking for a condition, a rule might identify multiple occurrences of the condition. {kib} tracks each of these *alerts* separately and takes an action per alert. +When checking for a condition, a rule might identify multiple occurrences of the condition. {kib} tracks each of these alerts separately. Depending on the action frequency, an action occurs per alert or at the specified alert summary interval. -Using the server monitoring example, each server with average CPU > 0.9 is tracked as an alert. This means a separate email is sent for each server that exceeds the threshold. +Using the server monitoring example, each server with average CPU > 0.9 is tracked as an alert. This means a separate email is sent for each server that exceeds the threshold whenever the alert status changes. image::images/alerts.svg[{kib} tracks each detected condition as an alert and takes action on each alert] -[float] -[[alerting-concepts-connectors]] -=== Connectors - -Actions often involve connecting with services inside {kib} or integrating with third-party systems. -Rather than repeatedly entering connection information and credentials for each action, {kib} simplifies action setup using connectors. - -Connectors provide a central place to store connection information for services and integrations. For example if four rules send email notifications via the same SMTP service, they can all reference the same SMTP connector. When the SMTP settings change, you can update them once in the connector, instead of having to update four rules. - -image::images/rule-concepts-connectors.svg[Connectors provide a central place to store service connection settings] - [float] == Putting it all together @@ -114,10 +100,10 @@ A rule consists of conditions, actions, and a schedule. When conditions are met, image::images/rule-concepts-summary.svg[Rules, connectors, alerts and actions work together to convert detection into action] -. Anytime a rule's conditions are met, an alert is created. This example checks for servers with average CPU > 0.9. Three servers meet the condition, so three alerts are created. -. Alerts create actions as long as they are not muted or throttled. When actions are created, the template that was setup in the rule is filled with actual values. In this example, three actions are created, and the template string {{server}} is replaced with the server name for each alert. -. {kib} invokes the actions, sending them to a third party integration like an email service. -. If the third party integration has connection parameters or credentials, {kib} will fetch these from the connector referenced in the action. +. Any time a rule's conditions are met, an alert is created. This example checks for servers with average CPU > 0.9. Three servers meet the condition, so three alerts are created. +. Alerts create actions according to the action frequency, as long as they are not muted or throttled. When actions are created, its properties are filled with actual values. In this example, three actions are created when the threshold is met, and the template string {{server}} is replaced with the appropriate server name for each alert. +. {kib} runs the actions, sending notifications by using a third party integration like an email service. +. If the third party integration has connection parameters or credentials, {kib} fetches these from the appropriate connector. [float] [[alerting-concepts-differences]] @@ -135,7 +121,7 @@ Functionally, the {alert-features} differ in that: * Scheduled checks are run on {kib} instead of {es} * {kib} <> through rule types, whereas watches provide low-level control over inputs, conditions, and transformations. * {kib} rules track and persist the state of each detected condition through alerts. This makes it possible to mute and throttle individual alerts, and detect changes in state such as resolution. -* Actions are linked to alerts in Alerting. Actions are fired for each occurrence of a detected condition, rather than for the entire rule. +* Actions are linked to alerts. Actions are fired for each occurrence of a detected condition, rather than for the entire rule. At a higher level, the {alert-features} allow rich integrations across use cases like <>, <>, <>, and <>. Prepackaged rule types simplify setup and hide the details of complex, domain-specific detections, while providing a consistent interface across {kib}. diff --git a/docs/user/alerting/create-and-manage-rules.asciidoc b/docs/user/alerting/create-and-manage-rules.asciidoc index d381087809620..86716a99f51ab 100644 --- a/docs/user/alerting/create-and-manage-rules.asciidoc +++ b/docs/user/alerting/create-and-manage-rules.asciidoc @@ -79,21 +79,21 @@ Each connector enables different action properties. For example, an email connec [[alerting-concepts-suppressing-duplicate-notifications]] [TIP] ============================================== -If you are not using alert summaries, actions are triggered per alert and a rule can end up generating a large number of actions. Take the following example where a rule is monitoring three servers every minute for CPU usage > 0.9, and the rule is set to notify `On check intervals`: +If you are not using alert summaries, actions are triggered per alert and a rule can end up generating a large number of actions. Take the following example where a rule is monitoring three servers every minute for CPU usage > 0.9, and the action frequency is `On check intervals`: * Minute 1: server X123 > 0.9. _One email_ is sent for server X123. * Minute 2: X123 and Y456 > 0.9. _Two emails_ are sent, one for X123 and one for Y456. * Minute 3: X123, Y456, Z789 > 0.9. _Three emails_ are sent, one for each of X123, Y456, Z789. In this example, three emails are sent for server X123 in the span of 3 minutes for the same rule. Often, it's desirable to suppress these re-notifications. If -you set the rule notify setting to `On custom action intervals` with an interval of 5 minutes, you reduce noise by getting emails only every 5 minutes for +you set the action frequency to `On custom action intervals` with an interval of 5 minutes, you reduce noise by getting emails only every 5 minutes for servers that continue to exceed the threshold: -* Minute 1: server X123 > 0.9. _One email_ is sent for server X123. -* Minute 2: X123 and Y456 > 0.9. _One email_ is sent for Y456. -* Minute 3: X123, Y456, Z789 > 0.9. _One email_ is sent for Z789. +* Minute 1: server X123 > 0.9. _One email_ will be sent for server X123. +* Minute 2: X123 and Y456 > 0.9. _One email_ will be sent for Y456. +* Minute 3: X123, Y456, Z789 > 0.9. _One email_ will be sent for Z789. -To get notified only once when a server exceeds the threshold, you can set the rule notify setting to `On status changes`. +To get notified only once when a server exceeds the threshold, you can set the action frequency to `On status changes`. Alternatively, if the rule type supports alert summaries, consider using them to reduce the volume of notifications. ============================================== [float] diff --git a/docs/user/alerting/images/alerts.svg b/docs/user/alerting/images/alerts.svg index 022b3106ae802..5e7819dd583f1 100644 --- a/docs/user/alerting/images/alerts.svg +++ b/docs/user/alerting/images/alerts.svg @@ -1 +1,62 @@ - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/user/alerting/images/rule-concepts-connectors.svg b/docs/user/alerting/images/rule-concepts-connectors.svg deleted file mode 100644 index caee5f858fea9..0000000000000 --- a/docs/user/alerting/images/rule-concepts-connectors.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/user/alerting/images/rule-concepts-summary.svg b/docs/user/alerting/images/rule-concepts-summary.svg index aed7020b9d3e2..d7fd2c5806b39 100644 --- a/docs/user/alerting/images/rule-concepts-summary.svg +++ b/docs/user/alerting/images/rule-concepts-summary.svg @@ -1 +1,109 @@ - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/user/alerting/images/rule-types-index-threshold-conditions.png b/docs/user/alerting/images/rule-types-index-threshold-conditions.png index 062b0a426b5d8..9aac7bd26c3c0 100644 Binary files a/docs/user/alerting/images/rule-types-index-threshold-conditions.png and b/docs/user/alerting/images/rule-types-index-threshold-conditions.png differ diff --git a/docs/user/alerting/images/rule-types-index-threshold-example-action.png b/docs/user/alerting/images/rule-types-index-threshold-example-action.png new file mode 100644 index 0000000000000..3544514574891 Binary files /dev/null and b/docs/user/alerting/images/rule-types-index-threshold-example-action.png differ diff --git a/docs/user/alerting/images/rule-types-index-threshold-example-aggregation.png b/docs/user/alerting/images/rule-types-index-threshold-example-aggregation.png index a43c4bf1f0d37..68b21a62d8a9b 100644 Binary files a/docs/user/alerting/images/rule-types-index-threshold-example-aggregation.png and b/docs/user/alerting/images/rule-types-index-threshold-example-aggregation.png differ diff --git a/docs/user/alerting/images/rule-types-index-threshold-example-alerts.png b/docs/user/alerting/images/rule-types-index-threshold-example-alerts.png new file mode 100644 index 0000000000000..f183a3744db92 Binary files /dev/null and b/docs/user/alerting/images/rule-types-index-threshold-example-alerts.png differ diff --git a/docs/user/alerting/images/rule-types-index-threshold-example-comparison.png b/docs/user/alerting/images/rule-types-index-threshold-example-comparison.png deleted file mode 100644 index 5e7c65e1247d8..0000000000000 Binary files a/docs/user/alerting/images/rule-types-index-threshold-example-comparison.png and /dev/null differ diff --git a/docs/user/alerting/images/rule-types-index-threshold-example-grouping.png b/docs/user/alerting/images/rule-types-index-threshold-example-grouping.png index 9f4c2ccbec3c0..aaed0acfbf863 100644 Binary files a/docs/user/alerting/images/rule-types-index-threshold-example-grouping.png and b/docs/user/alerting/images/rule-types-index-threshold-example-grouping.png differ diff --git a/docs/user/alerting/images/rule-types-index-threshold-example-index.png b/docs/user/alerting/images/rule-types-index-threshold-example-index.png index b2f1c78f7add8..29a9c9520d69e 100644 Binary files a/docs/user/alerting/images/rule-types-index-threshold-example-index.png and b/docs/user/alerting/images/rule-types-index-threshold-example-index.png differ diff --git a/docs/user/alerting/images/rule-types-index-threshold-example-preview.png b/docs/user/alerting/images/rule-types-index-threshold-example-preview.png index 19ad52c45da1c..a7e656297a3f0 100644 Binary files a/docs/user/alerting/images/rule-types-index-threshold-example-preview.png and b/docs/user/alerting/images/rule-types-index-threshold-example-preview.png differ diff --git a/docs/user/alerting/images/rule-types-index-threshold-example-threshold.png b/docs/user/alerting/images/rule-types-index-threshold-example-threshold.png index 9d9262dd96a1e..5a1bc35bd22c9 100644 Binary files a/docs/user/alerting/images/rule-types-index-threshold-example-threshold.png and b/docs/user/alerting/images/rule-types-index-threshold-example-threshold.png differ diff --git a/docs/user/alerting/images/rule-types-index-threshold-example-timefield.png b/docs/user/alerting/images/rule-types-index-threshold-example-timefield.png deleted file mode 100644 index e7b13ed6e2cc0..0000000000000 Binary files a/docs/user/alerting/images/rule-types-index-threshold-example-timefield.png and /dev/null differ diff --git a/docs/user/alerting/images/rule-types-index-threshold-example-window.png b/docs/user/alerting/images/rule-types-index-threshold-example-window.png deleted file mode 100644 index 9b8e9a47ae91e..0000000000000 Binary files a/docs/user/alerting/images/rule-types-index-threshold-example-window.png and /dev/null differ diff --git a/docs/user/alerting/images/rule-types-index-threshold-preview.png b/docs/user/alerting/images/rule-types-index-threshold-preview.png deleted file mode 100644 index 2065cbd117b75..0000000000000 Binary files a/docs/user/alerting/images/rule-types-index-threshold-preview.png and /dev/null differ diff --git a/docs/user/alerting/images/rule-types-index-threshold-select.png b/docs/user/alerting/images/rule-types-index-threshold-select.png index aeb9de279b3a1..3a00f5da6aa1a 100644 Binary files a/docs/user/alerting/images/rule-types-index-threshold-select.png and b/docs/user/alerting/images/rule-types-index-threshold-select.png differ diff --git a/docs/user/alerting/images/what-is-a-rule.svg b/docs/user/alerting/images/what-is-a-rule.svg index 2117e448ba136..2f89c2b4edd59 100644 --- a/docs/user/alerting/images/what-is-a-rule.svg +++ b/docs/user/alerting/images/what-is-a-rule.svg @@ -1 +1,24 @@ - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/user/alerting/images/what-is-an-action.svg b/docs/user/alerting/images/what-is-an-action.svg deleted file mode 100644 index f8435ee24fc19..0000000000000 --- a/docs/user/alerting/images/what-is-an-action.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/user/alerting/rule-types.asciidoc b/docs/user/alerting/rule-types.asciidoc index 7aab91b27e030..e33dc4214dd8c 100644 --- a/docs/user/alerting/rule-types.asciidoc +++ b/docs/user/alerting/rule-types.asciidoc @@ -86,6 +86,6 @@ Alerts associated with security rules are visible only in the {security-app}; they are not visible in *{stack-manage-app} > {rules-ui}*. ============================================== -include::rule-types/index-threshold.asciidoc[] +include::rule-types/index-threshold.asciidoc[leveloffset=+1] include::rule-types/es-query.asciidoc[leveloffset=+1] include::rule-types/geo-rule-types.asciidoc[] diff --git a/docs/user/alerting/rule-types/index-threshold.asciidoc b/docs/user/alerting/rule-types/index-threshold.asciidoc index 870c53f69b9a1..531d56ef7e697 100644 --- a/docs/user/alerting/rule-types/index-threshold.asciidoc +++ b/docs/user/alerting/rule-types/index-threshold.asciidoc @@ -1,21 +1,16 @@ -[role="xpack"] [[rule-type-index-threshold]] -=== Index threshold +== Index threshold The index threshold rule type runs an {es} query. It aggregates field values from documents, compares them to threshold values, and schedules actions to run when the thresholds are met. [float] -==== Create the rule - -Fill in the name and optional tags, then select *Index Threshold*. - -[float] -==== Define the conditions - -Define properties to detect the condition. +=== Rule conditions [role="screenshot"] -image::user/alerting/images/rule-types-index-threshold-conditions.png[Five clauses define the condition to detect] +image::user/alerting/images/rule-types-index-threshold-conditions.png[Defining index threshold rule conditions in {kib}] +// NOTE: This is an autogenerated screenshot. Do not edit it directly. + +When you create an index threshold rule, you must define the conditions for the rule to detect. For example: Index:: This clause requires an *index or data view* and a *time field* that will be used for the *time window*. When:: This clause specifies how the value to be compared to the threshold is calculated. The value is calculated by aggregating a numeric field a the *time window*. The aggregation options are: `count`, `average`, `sum`, `min`, and `max`. When using `count` the document count is used, and an aggregation field is not necessary. @@ -23,78 +18,97 @@ Over/Grouped Over:: This clause lets you configure whether the aggregation is ap Threshold:: This clause defines a threshold value and a comparison operator (one of `is above`, `is above or equals`, `is below`, `is below or equals`, or `is between`). The result of the aggregation is compared to this threshold. Time window:: This clause determines how far back to search for documents, using the *time field* set in the *index* clause. Generally this value should be to a value higher than the *check every* value, to avoid gaps in detection. -If data is available and all clauses have been defined, a preview chart will render the threshold value and display a line chart showing the value for the last 30 intervals. This can provide an indication of recent values and their proximity to the threshold, and help you tune the clauses. - -[role="screenshot"] -image::user/alerting/images/rule-types-index-threshold-preview.png[Five clauses define the condition to detect] +If data is available and all clauses have been defined, a preview chart will render the threshold value and display a line chart showing the value for the last 30 intervals. This can provide an indication of recent values and their proximity to the threshold, and help you tune the clauses. [float] -==== Add action variables +[[action-variables-index-threshold]] +=== Action variables -<> to run when the rule condition is met. The following variables are specific to the index threshold rule. You can also specify <>. +The following action variables are specific to the index threshold rule. You can also specify <>. -`context.title`:: A preconstructed title for the rule. Example: `rule kibana sites - high egress met threshold`. +`context.conditions`:: A description of the threshold condition. Example: `count greater than 4` +`context.date`:: The date, in ISO format, that the rule met the threshold condition. Example: `2020-01-01T00:00:00.000Z`. +`context.group`:: The name of the action group associated with the threshold condition. Example: `threshold met`. `context.message`:: A preconstructed message for the rule. Example: + `rule 'kibana sites - high egress' is active for group 'threshold met':` + `- Value: 42` + `- Conditions Met: count greater than 4 over 5m` + `- Timestamp: 2020-01-01T00:00:00.000Z` - -`context.group`:: The name of the action group associated with the threshold condition. Example: `threshold met`. -`context.date`:: The date, in ISO format, that the rule met the threshold condition. Example: `2020-01-01T00:00:00.000Z`. +`context.title`:: A preconstructed title for the rule. Example: `rule kibana sites - high egress met threshold`. `context.value`:: The value for the rule that met the threshold condition. -`context.conditions`:: A description of the threshold condition. Example: `count greater than 4` [float] -==== Example +=== Example -In this example, you will use the {kib} <> to set up and tune the conditions on an index threshold rule. For this example, you want to detect when any of the top four sites serve more than 420,000 bytes over a 24 hour period. +In this example, you will use the {kib} <> to set up and tune the conditions on an index threshold rule. For this example, you want to detect when any of the top four sites serve more than 420,000 bytes over a 24 hour period. . Open the main menu, then click *{stack-manage-app} > {rules-ui}*. -. Create a new rule that is checked every four hours and triggers actions when the rule status changes. +. Create a new rule. + +.. Provide a rule name and select the **Index threshold** rule type. + [role="screenshot"] image::user/alerting/images/rule-types-index-threshold-select.png[Choosing an index threshold rule type] +// NOTE: This is an autogenerated screenshot. Do not edit it directly. -. Select the **Index threshold** rule type. - -. Click *Index*, and set *Indices to query* to *kibana_sample_data_logs*. +.. Select an index. Click *Index*, and set *Indices to query* to `kibana_sample_data_logs`. Set the *Time field* to `@timestamp`. + [role="screenshot"] image::user/alerting/images/rule-types-index-threshold-example-index.png[Choosing an index] +// NOTE: This is an autogenerated screenshot. Do not edit it directly. -. Set the *Time field* to *@timestamp*. -+ -[role="screenshot"] -image::user/alerting/images/rule-types-index-threshold-example-timefield.png[Choosing a time field] - -. To detect the number of bytes served during the time window, click *When* and select `sum` as the aggregation, and bytes as the field to aggregate. +.. To detect the number of bytes served during the time window, click *When* and select `sum` as the aggregation, and `bytes` as the field to aggregate. + [role="screenshot"] image::user/alerting/images/rule-types-index-threshold-example-aggregation.png[Choosing the aggregation] +// NOTE: This is an autogenerated screenshot. Do not edit it directly. -. To detect the four sites that have the most traffic, click *Over* and select `top`, enter `4`, and select `host.keyword` as the field. +.. To detect the four sites that have the most traffic, click *Over* and select `top`, enter `4`, and select `host.keyword` as the field. + [role="screenshot"] image::user/alerting/images/rule-types-index-threshold-example-grouping.png[Choosing the groups] +// NOTE: This is an autogenerated screenshot. Do not edit it directly. -. To trigger the rule when any of the top four sites exceeds 420,000 bytes over a 24 hour period, select `is above` and enter `420000`. +.. Define the condition. To trigger the rule when any of the top four sites exceeds 420,000 bytes over a 24 hour period, select `is above` and enter `420000`. Then click *For the last*, enter `24`, and select `hours`. + [role="screenshot"] image::user/alerting/images/rule-types-index-threshold-example-threshold.png[Setting the threshold] +// NOTE: This is an autogenerated screenshot. Do not edit it directly. -. Finally, click *For the last*, enter `24` and select `hours` to complete the rule configuration. +.. Schedule the rule to check every four hours. + +-- [role="screenshot"] -image::user/alerting/images/rule-types-index-threshold-example-window.png[Setting the time window] +image::user/alerting/images/rule-types-index-threshold-example-preview.png[Setting the check interval] +// NOTE: This is an autogenerated screenshot. Do not edit it directly. + +The preview chart will render showing the 24 hour sum of bytes at 4 hours intervals (the _check interval_) for the past 120 hours (the last 30 intervals). +-- -. The preview chart will render showing the 24 hour sum of bytes at 4 hours intervals (the *check every* interval) for the past 120 hours (the last 30 intervals). +.. Change the time window and observe the effect it has on the chart. Compare a 24 window to a 12 hour window. Notice the variability in the sum of bytes, due to different traffic levels during the day compared to at night. This variability would result in noisy rules, so the 24 hour window is better. The preview chart can help you find the right values for your rule. + +.. Define the actions for your rule. + +-- +You can add one or more actions to your rule to generate notifications when its conditions are met and when they are no longer met. For each action, you must select a connector, set the action frequency, and compose the notification details. +For example, add an action that uses a server log connector to write an entry to the Kibana server log: + [role="screenshot"] -image::user/alerting/images/rule-types-index-threshold-example-preview.png[Setting the time window] +image::user/alerting/images/rule-types-index-threshold-example-action.png[Add an action to the rule] +// NOTE: This is an autogenerated screenshot. Do not edit it directly. + +NOTE: The index threshold rule does not support alert summaries; therefore they do not appear in the action frequency options. + +The unique action variables that you can use in the notification are listed in <>. For more information, refer to <> and <>. +-- -. Change the time window and observe the effect it has on the chart. Compare a 24 window to a 12 hour window. Notice the variability in the sum of bytes, due to different traffic levels during the day compared to at night. This variability would result in noisy rules, so the 24 hour window is better. The preview chart can help you find the right values for your rule. +.. Save the rule. + +. Find the rule and view its details in *{stack-manage-app} > {rules-ui}*. For example, you can see the status of the rule and its alerts: + [role="screenshot"] -image::user/alerting/images/rule-types-index-threshold-example-comparison.png[Comparing two time windows] \ No newline at end of file +image::user/alerting/images/rule-types-index-threshold-example-alerts.png[View the list of alerts for the rule] + +. Delete or disable this example rule when it's no longer useful. In the detailed rule view, select *Delete rule* from the actions menu. + diff --git a/docs/user/dashboard/create-panels-with-editors.asciidoc b/docs/user/dashboard/create-panels-with-editors.asciidoc index ff61e03b381fe..9b55a86784636 100644 --- a/docs/user/dashboard/create-panels-with-editors.asciidoc +++ b/docs/user/dashboard/create-panels-with-editors.asciidoc @@ -62,7 +62,7 @@ | | Gauge and Goal -| +| ✓ | ✓ | ✓ | ✓ @@ -136,7 +136,7 @@ | ✓ | Synchronized tooltips -| +| ✓ | ✓ | | @@ -196,7 +196,7 @@ | | Annotations -| +| ✓ | ✓ | | @@ -256,11 +256,11 @@ | ✓ | <> -| +| Use <> | ✓ | Static value -| +| ✓ | ✓ |=== @@ -301,7 +301,7 @@ Metric aggregations are calculated from the values in the aggregated documents. | ✓ | Percentiles Rank -| +| ✓ | ✓ | ✓ | ✓ @@ -325,13 +325,13 @@ Metric aggregations are calculated from the values in the aggregated documents. | ✓ | Value count -| +| ✓ | | ✓ | ✓ | Variance -| +| ✓ | ✓ | | ✓ @@ -364,7 +364,7 @@ Bucket aggregations group, or bucket, documents based on the aggregation type. T | Date range | Use filters -| +| ✓ | ✓ | ✓ diff --git a/docs/user/dashboard/images/lens_lineChartMetricOverTimeBottomAxis_8.3.png b/docs/user/dashboard/images/lens_lineChartMetricOverTimeBottomAxis_8.3.png deleted file mode 100644 index 5a6cfb2bf740a..0000000000000 Binary files a/docs/user/dashboard/images/lens_lineChartMetricOverTimeBottomAxis_8.3.png and /dev/null differ diff --git a/docs/user/dashboard/images/lens_lineChartMetricOverTimeBottomAxis_8.7.png b/docs/user/dashboard/images/lens_lineChartMetricOverTimeBottomAxis_8.7.png new file mode 100644 index 0000000000000..64062478abe28 Binary files /dev/null and b/docs/user/dashboard/images/lens_lineChartMetricOverTimeBottomAxis_8.7.png differ diff --git a/docs/user/dashboard/images/lens_lineChartMetricOverTimeLeftAxis_8.3.png b/docs/user/dashboard/images/lens_lineChartMetricOverTimeLeftAxis_8.3.png deleted file mode 100644 index 9c2e3626bc07f..0000000000000 Binary files a/docs/user/dashboard/images/lens_lineChartMetricOverTimeLeftAxis_8.3.png and /dev/null differ diff --git a/docs/user/dashboard/images/lens_lineChartMetricOverTimeLeftAxis_8.7.png b/docs/user/dashboard/images/lens_lineChartMetricOverTimeLeftAxis_8.7.png new file mode 100644 index 0000000000000..19b2d51216491 Binary files /dev/null and b/docs/user/dashboard/images/lens_lineChartMetricOverTimeLeftAxis_8.7.png differ diff --git a/docs/user/dashboard/lens.asciidoc b/docs/user/dashboard/lens.asciidoc index ff6ffe230f7e7..eafe5cbf076c3 100644 --- a/docs/user/dashboard/lens.asciidoc +++ b/docs/user/dashboard/lens.asciidoc @@ -21,7 +21,7 @@ With *Lens*, you can: If you're unsure about the visualization type you want to use, or how you want to display the data, drag the fields you want to visualize onto the workspace, then let *Lens* choose for you. -If you already know the visualization type you want to use, and how you want to display the data, use the following process: +If you already know the visualization type you want to use, and how you want to display the data, use the following process. Choose the visualization type. diff --git a/docs/user/dashboard/tutorial-create-a-dashboard-of-lens-panels.asciidoc b/docs/user/dashboard/tutorial-create-a-dashboard-of-lens-panels.asciidoc index 0c183b91fc495..18b2b0a148154 100644 --- a/docs/user/dashboard/tutorial-create-a-dashboard-of-lens-panels.asciidoc +++ b/docs/user/dashboard/tutorial-create-a-dashboard-of-lens-panels.asciidoc @@ -130,12 +130,12 @@ To save space on the dashboard, hide the axis labels. . Open the *Left axis* menu, then select *None* from the *Axis title* dropdown. + [role="screenshot"] -image::images/lens_lineChartMetricOverTimeLeftAxis_8.3.png[Left axis menu] +image::images/lens_lineChartMetricOverTimeLeftAxis_8.7.png[Left axis menu] . Open the *Bottom axis* menu, then select *None* from the *Axis title* dropdown. + [role="screenshot"] -image::images/lens_lineChartMetricOverTimeBottomAxis_8.3.png[Bottom axis menu] +image::images/lens_lineChartMetricOverTimeBottomAxis_8.7.png[Bottom axis menu] . Click *Save and return* diff --git a/docs/user/reporting/index.asciidoc b/docs/user/reporting/index.asciidoc index a10c3a6ec8737..e85c46d04ed38 100644 --- a/docs/user/reporting/index.asciidoc +++ b/docs/user/reporting/index.asciidoc @@ -19,17 +19,17 @@ You access the options from the *Share* menu in the toolbar. The sharing options * *CSV Reports* — Generate and download a CSV file of a *Discover* saved search. -* *Permalinks* — Share a direct link to a *Discover* saved search, dashboard, or visualization. +* *Get links* — Share a direct link to a *Discover* saved search, dashboard, or visualization. * *Download as JSON* — Generate and download a JSON file of a *Canvas* workpad. * beta[] *Share on a website* — Download and securely share *Canvas* workpads on any website. -* *Embed code* — Embed a fully interactive dashboard or visualization as an iframe on a web page. +* *Embed code* — Embed a fully interactive dashboard as an iframe on a web page. [[reporting-on-cloud-resource-requirements]] -NOTE: For Elastic Cloud deployments, Kibana instances require a minimum of 2GB RAM to generate PDF or PNG reports. To -change Kibana sizing, {ess-console}[edit the deployment]. +NOTE: For Elastic Cloud deployments, {kib} instances require a minimum of 2GB RAM to generate PDF or PNG reports. To +change {kib} sizing, {ess-console}[edit the deployment]. [float] [[manually-generate-reports]] @@ -84,7 +84,7 @@ Share a direct link to a saved search, dashboard, or visualization. To access th . Open the main menu, then open the saved search, dashboard, or visualization you want to share. -. From the toolbar, click *Share*, then select *Permalinks*. +. From the toolbar, click *Share*, then select *Get links*. . Specify how you want to generate the link: @@ -142,11 +142,11 @@ NOTE: Shareable workpads encode the current state of the workpad in a JSON file. [[embed-code]] == Embed code -Display your dashboard or visualization on an internal company website or personal web page with an iframe. Embedding other {kib} objects is generally supported, but you might need to manually craft the proper HTML code. +Display your dashboard on an internal company website or personal web page with an iframe. Embedding other {kib} objects is generally supported, but you might need to manually craft the proper HTML code. Some users might not have access to the dashboard or visualization. For more information, refer to <> and <>. -. Open the main menu, then open the dashboard or visualization you want to share. +. Open the main menu, then open the dashboard you want to share. . Click *Share > Embed code*. @@ -156,7 +156,7 @@ Some users might not have access to the dashboard or visualization. For more inf * To display up-to-date changes, select *Saved object*. -* Select the dashboard or visualization elements you want to include. +* Select the dashboard components you want to include. * To generate a shortened link, select *Short URL*. diff --git a/src/plugins/content_management/.storybook/main.js b/examples/content_management_examples/.storybook/main.js similarity index 100% rename from src/plugins/content_management/.storybook/main.js rename to examples/content_management_examples/.storybook/main.js diff --git a/examples/content_management_examples/README.md b/examples/content_management_examples/README.md new file mode 100644 index 0000000000000..a4b0486903dba --- /dev/null +++ b/examples/content_management_examples/README.md @@ -0,0 +1,3 @@ +# Content Management Examples + +An example plugin that shows how to integrate with the Kibana "content management" plugin. diff --git a/src/plugins/telemetry/common/types.ts b/examples/content_management_examples/common/examples/todos/index.ts similarity index 66% rename from src/plugins/telemetry/common/types.ts rename to examples/content_management_examples/common/examples/todos/index.ts index aefbbd2358861..9ef986ec2032f 100644 --- a/src/plugins/telemetry/common/types.ts +++ b/examples/content_management_examples/common/examples/todos/index.ts @@ -6,5 +6,4 @@ * Side Public License, v 1. */ -export type EncryptedTelemetryPayload = Array<{ clusterUuid: string; stats: string }>; -export type UnencryptedTelemetryPayload = Array<{ clusterUuid: string; stats: object }>; +export * from './todos'; diff --git a/examples/content_management_examples/common/examples/todos/todos.ts b/examples/content_management_examples/common/examples/todos/todos.ts new file mode 100644 index 0000000000000..0371651c128f9 --- /dev/null +++ b/examples/content_management_examples/common/examples/todos/todos.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { + CreateIn, + DeleteIn, + GetIn, + SearchIn, + UpdateIn, +} from '@kbn/content-management-plugin/common'; + +export const TODO_CONTENT_ID = 'todos'; +export interface Todo { + id: string; + title: string; + completed: boolean; +} +const todoSchema = schema.object({ + id: schema.string(), + title: schema.string(), + completed: schema.boolean(), +}); + +export type TodoCreateIn = CreateIn<'todos', { title: string }>; +export type TodoCreateOut = Todo; // TODO: Is this correct? +export const createInSchema = schema.object({ title: schema.string() }); +export const createOutSchema = todoSchema; + +export type TodoUpdateIn = UpdateIn<'todos', Partial>>; +export type TodoUpdateOut = Todo; +export const updateInSchema = schema.object({ + title: schema.maybe(schema.string()), + completed: schema.maybe(schema.boolean()), +}); +export const updateOutSchema = todoSchema; + +export type TodoDeleteIn = DeleteIn<'todos', { id: string }>; +export type TodoDeleteOut = void; + +export type TodoGetIn = GetIn<'todos'>; +export type TodoGetOut = Todo; +export const getOutSchema = todoSchema; + +export type TodoSearchIn = SearchIn<'todos', { filter?: 'todo' | 'completed' }>; +export interface TodoSearchOut { + hits: Todo[]; +} +export const searchInSchema = schema.object({ + filter: schema.maybe( + schema.oneOf([schema.literal('todo'), schema.literal('completed')], { + defaultValue: undefined, + }) + ), +}); +export const searchOutSchema = schema.object({ + hits: schema.arrayOf(todoSchema), +}); diff --git a/examples/content_management_examples/jest.config.js b/examples/content_management_examples/jest.config.js new file mode 100644 index 0000000000000..9f84c97053475 --- /dev/null +++ b/examples/content_management_examples/jest.config.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/examples/content_management_examples'], +}; diff --git a/examples/content_management_examples/kibana.jsonc b/examples/content_management_examples/kibana.jsonc new file mode 100644 index 0000000000000..24759d7abdfb6 --- /dev/null +++ b/examples/content_management_examples/kibana.jsonc @@ -0,0 +1,15 @@ +{ + "type": "plugin", + "id": "@kbn/content-management-examples-plugin", + "owner": "@elastic/appex-sharedux", + "description": "Example plugin integrating with content management plugin", + "plugin": { + "id": "contentManagementExamples", + "server": true, + "browser": true, + "requiredPlugins": [ + "contentManagement", + "developerExamples" + ] + } +} diff --git a/examples/content_management_examples/public/examples/index.tsx b/examples/content_management_examples/public/examples/index.tsx new file mode 100644 index 0000000000000..715f3a6809581 --- /dev/null +++ b/examples/content_management_examples/public/examples/index.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import { EuiPageTemplate } from '@elastic/eui'; +import { AppMountParameters, CoreStart } from '@kbn/core/public'; +import { StartDeps } from '../types'; +import { TodoApp } from './todos'; + +export const renderApp = ( + { notifications }: CoreStart, + { contentManagement }: StartDeps, + { element }: AppMountParameters +) => { + ReactDOM.render( + + + + + , + element + ); + + return () => ReactDOM.unmountComponentAtNode(element); +}; diff --git a/examples/content_management_examples/public/examples/todos/index.tsx b/examples/content_management_examples/public/examples/todos/index.tsx new file mode 100644 index 0000000000000..dd19feb3e0a80 --- /dev/null +++ b/examples/content_management_examples/public/examples/todos/index.tsx @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { TodoApp } from './todo_app'; diff --git a/src/plugins/content_management/demo/todo/todo.stories.test.tsx b/examples/content_management_examples/public/examples/todos/stories/todo.stories.test.tsx similarity index 100% rename from src/plugins/content_management/demo/todo/todo.stories.test.tsx rename to examples/content_management_examples/public/examples/todos/stories/todo.stories.test.tsx diff --git a/src/plugins/content_management/demo/todo/todo.stories.tsx b/examples/content_management_examples/public/examples/todos/stories/todo.stories.tsx similarity index 88% rename from src/plugins/content_management/demo/todo/todo.stories.tsx rename to examples/content_management_examples/public/examples/todos/stories/todo.stories.tsx index 23cd2a194cf6d..5b7b8f1e23823 100644 --- a/src/plugins/content_management/demo/todo/todo.stories.tsx +++ b/examples/content_management_examples/public/examples/todos/stories/todo.stories.tsx @@ -7,8 +7,9 @@ */ import * as React from 'react'; -import { Todos } from './todos'; -import { ContentClientProvider, ContentClient } from '../../public/content_client'; +import { ContentClientProvider, ContentClient } from '@kbn/content-management-plugin/public'; + +import { Todos } from '../todos'; import { TodosClient } from './todos_client'; export default { diff --git a/src/plugins/content_management/demo/todo/todos_client.ts b/examples/content_management_examples/public/examples/todos/stories/todos_client.ts similarity index 62% rename from src/plugins/content_management/demo/todo/todos_client.ts rename to examples/content_management_examples/public/examples/todos/stories/todos_client.ts index c6f5fe4bf5f36..04d1f6fb990ed 100644 --- a/src/plugins/content_management/demo/todo/todos_client.ts +++ b/examples/content_management_examples/public/examples/todos/stories/todos_client.ts @@ -7,28 +7,32 @@ */ import { v4 as uuidv4 } from 'uuid'; -import type { CrudClient } from '../../public/crud_client'; -import type { CreateIn, DeleteIn, GetIn, SearchIn, UpdateIn } from '../../common'; - -export interface Todo { - id: string; - title: string; - completed: boolean; -} - -export type TodoCreateIn = CreateIn<'todos', { title: string }>; -export type TodoUpdateIn = UpdateIn<'todos', Partial>>; -export type TodoDeleteIn = DeleteIn<'todos', { id: string }>; -export type TodoGetIn = GetIn<'todos'>; -export type TodoSearchIn = SearchIn<'todos', { filter?: 'todo' | 'completed' }>; +import type { CrudClient } from '@kbn/content-management-plugin/public'; +import type { + TodoCreateIn, + TodoUpdateIn, + TodoDeleteIn, + TodoGetIn, + TodoSearchIn, + TodoUpdateOut, + TodoCreateOut, + TodoSearchOut, + TodoDeleteOut, + Todo, + TodoGetOut, +} from '../../../../common/examples/todos'; +/** + * This client is used in the storybook examples to simulate a server-side registry client + * and to show how a content type can have a custom client-side CRUD client without using the server-side registry + */ export class TodosClient implements CrudClient { private todos: Todo[] = [ { id: uuidv4(), title: 'Learn Elasticsearch', completed: true }, { id: uuidv4(), title: 'Learn Kibana', completed: false }, ]; - async create(input: TodoCreateIn): Promise { + async create(input: TodoCreateIn): Promise { const todo = { id: uuidv4(), title: input.data.title, @@ -38,22 +42,22 @@ export class TodosClient implements CrudClient { return todo; } - async delete(input: TodoDeleteIn): Promise { + async delete(input: TodoDeleteIn): Promise { this.todos = this.todos.filter((todo) => todo.id !== input.id); } - async get(input: TodoGetIn): Promise { + async get(input: TodoGetIn): Promise { return this.todos.find((todo) => todo.id === input.id)!; } - async search(input: TodoSearchIn): Promise<{ hits: Todo[] }> { + async search(input: TodoSearchIn): Promise { const filter = input.query.filter; if (filter === 'todo') return { hits: this.todos.filter((t) => !t.completed) }; if (filter === 'completed') return { hits: this.todos.filter((t) => t.completed) }; return { hits: [...this.todos] }; } - async update(input: TodoUpdateIn): Promise { + async update(input: TodoUpdateIn): Promise { const idToUpdate = input.id; const todoToUpdate = this.todos.find((todo) => todo.id === idToUpdate)!; if (todoToUpdate) { diff --git a/examples/content_management_examples/public/examples/todos/todo_app.tsx b/examples/content_management_examples/public/examples/todos/todo_app.tsx new file mode 100644 index 0000000000000..d2fae22bb41a1 --- /dev/null +++ b/examples/content_management_examples/public/examples/todos/todo_app.tsx @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { ContentClientProvider, type ContentClient } from '@kbn/content-management-plugin/public'; +import { Todos } from './todos'; + +export const TodoApp = (props: { contentClient: ContentClient }) => { + return ( + + + + ); +}; diff --git a/src/plugins/content_management/demo/todo/todos.tsx b/examples/content_management_examples/public/examples/todos/todos.tsx similarity index 80% rename from src/plugins/content_management/demo/todo/todos.tsx rename to examples/content_management_examples/public/examples/todos/todos.tsx index 667e455029ab4..84c48aa24c88b 100644 --- a/src/plugins/content_management/demo/todo/todos.tsx +++ b/examples/content_management_examples/public/examples/todos/todos.tsx @@ -7,22 +7,32 @@ */ import React from 'react'; import { EuiButtonGroup, EuiButtonIcon, EuiCheckbox, EuiFieldText, EuiSpacer } from '@elastic/eui'; - import { useCreateContentMutation, useDeleteContentMutation, useSearchContentQuery, useUpdateContentMutation, - // eslint-disable-next-line @kbn/imports/no_boundary_crossing -} from '../../public/content_client'; -import type { Todo, TodoCreateIn, TodoDeleteIn, TodoSearchIn, TodoUpdateIn } from './todos_client'; +} from '@kbn/content-management-plugin/public'; + +import { + TODO_CONTENT_ID, + Todo, + TodoCreateIn, + TodoDeleteIn, + TodoSearchIn, + TodoUpdateIn, + TodoUpdateOut, + TodoCreateOut, + TodoSearchOut, + TodoDeleteOut, +} from '../../../common/examples/todos'; -const useCreateTodoMutation = () => useCreateContentMutation(); -const useDeleteTodoMutation = () => useDeleteContentMutation(); -const useUpdateTodoMutation = () => useUpdateContentMutation(); +const useCreateTodoMutation = () => useCreateContentMutation(); +const useDeleteTodoMutation = () => useDeleteContentMutation(); +const useUpdateTodoMutation = () => useUpdateContentMutation(); const useSearchTodosQuery = ({ filter }: { filter: TodoSearchIn['query']['filter'] }) => - useSearchContentQuery({ - contentTypeId: 'todos', + useSearchContentQuery({ + contentTypeId: TODO_CONTENT_ID, query: { filter }, }); @@ -70,14 +80,17 @@ export const Todos = () => {
      {data.hits.map((todo: Todo) => ( -
    • +
    • { updateTodoMutation.mutate({ - contentTypeId: 'todos', + contentTypeId: TODO_CONTENT_ID, id: todo.id, data: { completed: e.target.checked, @@ -85,7 +98,7 @@ export const Todos = () => { }); }} label={todo.title} - data-test-subj={`todoCheckbox-${todo.id}`} + data-test-subj={`todoCheckbox todoCheckbox-${todo.id}`} /> { aria-label="Delete" color="danger" onClick={() => { - deleteTodoMutation.mutate({ contentTypeId: 'todos', id: todo.id }); + deleteTodoMutation.mutate({ contentTypeId: TODO_CONTENT_ID, id: todo.id }); }} />
    • @@ -112,7 +125,7 @@ export const Todos = () => { if (!inputRef || !inputRef.value) return; createTodoMutation.mutate({ - contentTypeId: 'todos', + contentTypeId: TODO_CONTENT_ID, data: { title: inputRef.value, }, diff --git a/examples/content_management_examples/public/index.ts b/examples/content_management_examples/public/index.ts new file mode 100644 index 0000000000000..ba96df2040435 --- /dev/null +++ b/examples/content_management_examples/public/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 { ContentManagementExamplesPlugin } from './plugin'; + +export function plugin() { + return new ContentManagementExamplesPlugin(); +} diff --git a/examples/content_management_examples/public/plugin.ts b/examples/content_management_examples/public/plugin.ts new file mode 100644 index 0000000000000..8731ef667650a --- /dev/null +++ b/examples/content_management_examples/public/plugin.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { AppNavLinkStatus } from '@kbn/core-application-browser'; +import { AppMountParameters, CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; +import { StartDeps, SetupDeps } from './types'; + +export class ContentManagementExamplesPlugin + implements Plugin +{ + public setup(core: CoreSetup, { contentManagement, developerExamples }: SetupDeps) { + developerExamples.register({ + appId: `contentManagementExamples`, + title: `Content Management Examples`, + description: 'Example plugin for the content management plugin', + }); + + core.application.register({ + id: `contentManagementExamples`, + title: `Content Management Examples`, + navLinkStatus: AppNavLinkStatus.hidden, + async mount(params: AppMountParameters) { + const { renderApp } = await import('./examples'); + const [coreStart, deps] = await core.getStartServices(); + return renderApp(coreStart, deps, params); + }, + }); + + return {}; + } + + public start(core: CoreStart) { + return {}; + } + + public stop() {} +} diff --git a/examples/content_management_examples/public/types.ts b/examples/content_management_examples/public/types.ts new file mode 100644 index 0000000000000..83e65c5455afd --- /dev/null +++ b/examples/content_management_examples/public/types.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { + ContentManagementPublicSetup, + ContentManagementPublicStart, +} from '@kbn/content-management-plugin/public'; +import { DeveloperExamplesSetup } from '@kbn/developer-examples-plugin/public'; + +export interface SetupDeps { + contentManagement: ContentManagementPublicSetup; + developerExamples: DeveloperExamplesSetup; +} + +export interface StartDeps { + contentManagement: ContentManagementPublicStart; +} diff --git a/examples/content_management_examples/server/examples/todos/index.ts b/examples/content_management_examples/server/examples/todos/index.ts new file mode 100644 index 0000000000000..bf065ba480736 --- /dev/null +++ b/examples/content_management_examples/server/examples/todos/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 { registerTodoContentType } from './todos'; diff --git a/examples/content_management_examples/server/examples/todos/todos.ts b/examples/content_management_examples/server/examples/todos/todos.ts new file mode 100644 index 0000000000000..fe8a7826be655 --- /dev/null +++ b/examples/content_management_examples/server/examples/todos/todos.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 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 { + ContentStorage, + StorageContext, + ContentManagementServerSetup, +} from '@kbn/content-management-plugin/server'; +import { v4 } from 'uuid'; +import { + createInSchema, + searchInSchema, + Todo, + TODO_CONTENT_ID, + updateInSchema, + TodoSearchOut, + TodoCreateOut, + TodoUpdateOut, + TodoDeleteOut, + TodoGetOut, + createOutSchema, + getOutSchema, + updateOutSchema, + searchOutSchema, + TodoUpdateIn, + TodoSearchIn, + TodoCreateIn, +} from '../../../common/examples/todos'; + +export const registerTodoContentType = ({ + contentManagement, +}: { + contentManagement: ContentManagementServerSetup; +}) => { + contentManagement.register({ + id: TODO_CONTENT_ID, + schemas: { + content: { + create: { + in: { + data: createInSchema, + }, + out: { + result: createOutSchema, + }, + }, + update: { + in: { + data: updateInSchema, + }, + out: { + result: updateOutSchema, + }, + }, + search: { + in: { + query: searchInSchema, + }, + out: { + result: searchOutSchema, + }, + }, + get: { + out: { + result: getOutSchema, + }, + }, + }, + }, + storage: new TodosStorage(), + }); +}; + +class TodosStorage implements ContentStorage { + private db: Map = new Map(); + + constructor() { + const id1 = v4(); + this.db.set(id1, { + id: id1, + title: 'Learn Elasticsearch', + completed: true, + }); + const id2 = v4(); + this.db.set(id2, { + id: id2, + title: 'Learn Kibana', + completed: false, + }); + } + + async get(ctx: StorageContext, id: string): Promise { + return this.db.get(id)!; + } + + async bulkGet(ctx: StorageContext, ids: string[]): Promise { + return ids.map((id) => this.db.get(id)!); + } + + async create(ctx: StorageContext, data: TodoCreateIn['data']): Promise { + const todo: Todo = { + ...data, + completed: false, + id: v4(), + }; + + this.db.set(todo.id, todo); + + return todo; + } + + async update( + ctx: StorageContext, + id: string, + data: TodoUpdateIn['data'] + ): Promise { + const content = this.db.get(id); + if (!content) { + throw new Error(`Content to update not found [${id}].`); + } + + const updatedContent = { + ...content, + ...data, + }; + + this.db.set(id, updatedContent); + + return updatedContent; + } + + async delete(ctx: StorageContext, id: string): Promise { + this.db.delete(id); + } + + async search(ctx: StorageContext, query: TodoSearchIn['query']): Promise { + const hits = Array.from(this.db.values()); + if (query.filter === 'todo') return { hits: hits.filter((t) => !t.completed) }; + if (query.filter === 'completed') return { hits: hits.filter((t) => t.completed) }; + return { hits }; + } +} diff --git a/examples/content_management_examples/server/index.ts b/examples/content_management_examples/server/index.ts new file mode 100644 index 0000000000000..d75af7bacf224 --- /dev/null +++ b/examples/content_management_examples/server/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { PluginInitializerContext } from '@kbn/core/server'; +import { ContentManagementExamplesPlugin } from './plugin'; + +export function plugin(initializerContext: PluginInitializerContext) { + return new ContentManagementExamplesPlugin(initializerContext); +} diff --git a/examples/content_management_examples/server/plugin.ts b/examples/content_management_examples/server/plugin.ts new file mode 100644 index 0000000000000..bf66de46e1251 --- /dev/null +++ b/examples/content_management_examples/server/plugin.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 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 { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/server'; +import type { SetupDeps, StartDeps } from './types'; +import { registerTodoContentType } from './examples/todos'; + +export class ContentManagementExamplesPlugin implements Plugin { + constructor(initializerContext: PluginInitializerContext) {} + + public setup(core: CoreSetup, { contentManagement }: SetupDeps) { + registerTodoContentType({ contentManagement }); + return {}; + } + + public start(core: CoreStart, { contentManagement }: StartDeps) { + return {}; + } + + public stop() {} +} diff --git a/examples/content_management_examples/server/types.ts b/examples/content_management_examples/server/types.ts new file mode 100644 index 0000000000000..57427001461b0 --- /dev/null +++ b/examples/content_management_examples/server/types.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 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 { + ContentManagementServerSetup, + ContentManagementServerStart, +} from '@kbn/content-management-plugin/server'; + +export interface SetupDeps { + contentManagement: ContentManagementServerSetup; +} + +export interface StartDeps { + contentManagement: ContentManagementServerStart; +} diff --git a/examples/content_management_examples/tsconfig.json b/examples/content_management_examples/tsconfig.json new file mode 100644 index 0000000000000..1e899345b0bf4 --- /dev/null +++ b/examples/content_management_examples/tsconfig.json @@ -0,0 +1,24 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types" + }, + "include": [ + "index.ts", + "common/**/*", + "public/**/*.ts", + "public/**/*.tsx", + "server/**/*.ts", + "../../typings/**/*" + ], + "exclude": [ + "target/**/*", + ], + "kbn_references": [ + "@kbn/core", + "@kbn/developer-examples-plugin", + "@kbn/config-schema", + "@kbn/content-management-plugin", + "@kbn/core-application-browser", + ] +} diff --git a/nav-kibana-dev.docnav.json b/nav-kibana-dev.docnav.json index 490718262a816..cfee44522daad 100644 --- a/nav-kibana-dev.docnav.json +++ b/nav-kibana-dev.docnav.json @@ -128,6 +128,10 @@ { "id": "kibDevTutorialSavedObject" }, + { + "id": "kibDevTutorialVersioningInterfaces", + "label": "Versioning interfaces" + }, { "id": "kibDevTutorialSubmitPullRequest" }, diff --git a/package.json b/package.json index 6c532d3af8dec..c81b11551cff8 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,7 @@ }, "dependencies": { "@appland/sql-parser": "^1.5.1", - "@babel/runtime": "^7.20.13", + "@babel/runtime": "^7.21.0", "@dnd-kit/core": "^3.1.1", "@dnd-kit/sortable": "^4.0.0", "@dnd-kit/utilities": "^2.0.0", @@ -96,7 +96,7 @@ "@elastic/datemath": "5.0.3", "@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@8.6.0-canary.3", "@elastic/ems-client": "8.4.0", - "@elastic/eui": "75.1.0", + "@elastic/eui": "75.1.2", "@elastic/filesaver": "1.1.2", "@elastic/node-crypto": "1.2.1", "@elastic/numeral": "^2.5.1", @@ -134,6 +134,7 @@ "@kbn/alerting-fixture-plugin": "link:x-pack/test/functional_with_es_ssl/plugins/alerts", "@kbn/alerting-plugin": "link:x-pack/plugins/alerting", "@kbn/alerts": "link:packages/kbn-alerts", + "@kbn/alerts-as-data-utils": "link:packages/kbn-alerts-as-data-utils", "@kbn/alerts-restricted-fixtures-plugin": "link:x-pack/test/alerting_api_integration/common/plugins/alerts_restricted", "@kbn/alerts-ui-shared": "link:packages/kbn-alerts-ui-shared", "@kbn/analytics": "link:packages/kbn-analytics", @@ -181,6 +182,7 @@ "@kbn/config-schema": "link:packages/kbn-config-schema", "@kbn/console-plugin": "link:src/plugins/console", "@kbn/content-management-content-editor": "link:packages/content-management/content_editor", + "@kbn/content-management-examples-plugin": "link:examples/content_management_examples", "@kbn/content-management-plugin": "link:src/plugins/content_management", "@kbn/content-management-table-list": "link:packages/content-management/table_list", "@kbn/controls-example-plugin": "link:examples/controls_example", @@ -369,6 +371,7 @@ "@kbn/event-annotation-plugin": "link:src/plugins/event_annotation", "@kbn/event-log-fixture-plugin": "link:x-pack/test/plugin_api_integration/plugins/event_log", "@kbn/event-log-plugin": "link:x-pack/plugins/event_log", + "@kbn/expandable-flyout": "link:packages/kbn-expandable-flyout", "@kbn/exploratory-view-example-plugin": "link:x-pack/examples/exploratory_view_example", "@kbn/expression-error-plugin": "link:src/plugins/expression_error", "@kbn/expression-gauge-plugin": "link:src/plugins/chart_expressions/expression_gauge", @@ -853,7 +856,7 @@ "react-fast-compare": "^2.0.4", "react-focus-on": "^3.7.0", "react-grid-layout": "^1.3.4", - "react-hook-form": "^7.43.1", + "react-hook-form": "^7.43.2", "react-intl": "^2.8.0", "react-is": "^17.0.2", "react-markdown": "^6.0.3", @@ -933,26 +936,26 @@ }, "devDependencies": { "@apidevtools/swagger-parser": "^10.0.3", - "@babel/cli": "^7.20.7", - "@babel/core": "^7.20.12", + "@babel/cli": "^7.21.0", + "@babel/core": "^7.21.0", "@babel/eslint-parser": "^7.19.1", "@babel/eslint-plugin": "^7.19.1", - "@babel/generator": "^7.20.14", + "@babel/generator": "^7.21.1", "@babel/helper-plugin-utils": "^7.20.2", - "@babel/parser": "^7.20.15", + "@babel/parser": "^7.21.2", "@babel/plugin-proposal-class-properties": "^7.18.6", "@babel/plugin-proposal-export-namespace-from": "^7.18.9", "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", "@babel/plugin-proposal-object-rest-spread": "^7.20.7", - "@babel/plugin-proposal-optional-chaining": "^7.20.7", + "@babel/plugin-proposal-optional-chaining": "^7.21.0", "@babel/plugin-proposal-private-methods": "^7.18.6", - "@babel/plugin-transform-runtime": "^7.19.6", + "@babel/plugin-transform-runtime": "^7.21.0", "@babel/preset-env": "^7.20.2", "@babel/preset-react": "^7.18.6", - "@babel/preset-typescript": "^7.18.6", - "@babel/register": "^7.18.9", - "@babel/traverse": "^7.20.13", - "@babel/types": "^7.20.7", + "@babel/preset-typescript": "^7.21.0", + "@babel/register": "^7.21.0", + "@babel/traverse": "^7.21.2", + "@babel/types": "^7.21.2", "@bazel/ibazel": "^0.16.2", "@bazel/typescript": "4.6.2", "@cypress/code-coverage": "^3.10.0", @@ -1430,7 +1433,7 @@ "resolve": "^1.22.0", "rxjs-marbles": "^7.0.1", "sass-loader": "^10.4.1", - "selenium-webdriver": "^4.8.0", + "selenium-webdriver": "^4.8.1", "simple-git": "^3.16.0", "sinon": "^7.4.2", "sort-package-json": "^1.53.1", @@ -1445,7 +1448,7 @@ "svgo": "^2.8.0", "tape": "^5.0.1", "tempy": "^0.3.0", - "terser": "^5.16.3", + "terser": "^5.16.5", "terser-webpack-plugin": "^4.2.3", "tough-cookie": "^4.1.2", "tree-kill": "^1.2.2", diff --git a/packages/core/http/core-http-router-server-internal/src/request.ts b/packages/core/http/core-http-router-server-internal/src/request.ts index 7e8e927bd4671..26ac377c00bf3 100644 --- a/packages/core/http/core-http-router-server-internal/src/request.ts +++ b/packages/core/http/core-http-router-server-internal/src/request.ts @@ -201,6 +201,7 @@ export class CoreKibanaRequest< xsrfRequired: ((request.route?.settings as RouteOptions)?.app as KibanaRouteOptions)?.xsrfRequired ?? true, // some places in LP call KibanaRequest.from(request) manually. remove fallback to true before v8 + access: this.getAccess(request), tags: request.route?.settings?.tags || [], timeout: { payload: payloadTimeout, @@ -222,6 +223,13 @@ export class CoreKibanaRequest< options, }; } + /** infer route access from path if not declared */ + private getAccess(request: RawRequest): 'internal' | 'public' { + return ( + ((request.route?.settings as RouteOptions)?.app as KibanaRouteOptions)?.access ?? + (request.path.startsWith('/internal') ? 'internal' : 'public') + ); + } private getAuthRequired(request: RawRequest): boolean | 'optional' { if (isFakeRawRequest(request)) { diff --git a/packages/core/http/core-http-router-server-mocks/src/router.mock.ts b/packages/core/http/core-http-router-server-mocks/src/router.mock.ts index 1a3262fdf1f80..9b2d90e18640b 100644 --- a/packages/core/http/core-http-router-server-mocks/src/router.mock.ts +++ b/packages/core/http/core-http-router-server-mocks/src/router.mock.ts @@ -71,7 +71,7 @@ function createKibanaRequestMock

      ({ routeTags, routeAuthRequired, validation = {}, - kibanaRouteOptions = { xsrfRequired: true }, + kibanaRouteOptions = { xsrfRequired: true, access: 'public' }, kibanaRequestState = { requestId: '123', requestUuid: '123e4567-e89b-12d3-a456-426614174000', diff --git a/packages/core/http/core-http-server-internal/src/http_server.test.ts b/packages/core/http/core-http-server-internal/src/http_server.test.ts index 92fa63c502558..b6a120e06ab8d 100644 --- a/packages/core/http/core-http-server-internal/src/http_server.test.ts +++ b/packages/core/http/core-http-server-internal/src/http_server.test.ts @@ -817,6 +817,56 @@ test('allows attaching metadata to attach meta-data tag strings to a route', asy await supertest(innerServer.listener).get('/without-tags').expect(200, { tags: [] }); }); +test('allows declaring route access to flag a route as public or internal', async () => { + const access = 'internal'; + const { registerRouter, server: innerServer } = await server.setup(config); + + const router = new Router('', logger, enhanceWithContext); + router.get({ path: '/with-access', validate: false, options: { access } }, (context, req, res) => + res.ok({ body: { access: req.route.options.access } }) + ); + router.get({ path: '/without-access', validate: false }, (context, req, res) => + res.ok({ body: { access: req.route.options.access } }) + ); + registerRouter(router); + + await server.start(); + await supertest(innerServer.listener).get('/with-access').expect(200, { access }); + + await supertest(innerServer.listener).get('/without-access').expect(200, { access: 'public' }); +}); + +test('infers access flag from path if not defined', async () => { + const { registerRouter, server: innerServer } = await server.setup(config); + + const router = new Router('', logger, enhanceWithContext); + router.get({ path: '/internal/foo', validate: false }, (context, req, res) => + res.ok({ body: { access: req.route.options.access } }) + ); + router.get({ path: '/random/foo', validate: false }, (context, req, res) => + res.ok({ body: { access: req.route.options.access } }) + ); + router.get({ path: '/random/internal/foo', validate: false }, (context, req, res) => + res.ok({ body: { access: req.route.options.access } }) + ); + + router.get({ path: '/api/foo/internal/my-foo', validate: false }, (context, req, res) => + res.ok({ body: { access: req.route.options.access } }) + ); + registerRouter(router); + + await server.start(); + await supertest(innerServer.listener).get('/internal/foo').expect(200, { access: 'internal' }); + + await supertest(innerServer.listener).get('/random/foo').expect(200, { access: 'public' }); + await supertest(innerServer.listener) + .get('/random/internal/foo') + .expect(200, { access: 'public' }); + await supertest(innerServer.listener) + .get('/api/foo/internal/my-foo') + .expect(200, { access: 'public' }); +}); + test('exposes route details of incoming request to a route handler', async () => { const { registerRouter, server: innerServer } = await server.setup(config); @@ -833,6 +883,7 @@ test('exposes route details of incoming request to a route handler', async () => options: { authRequired: true, xsrfRequired: false, + access: 'public', tags: [], timeout: {}, }, @@ -1010,6 +1061,7 @@ test('exposes route details of incoming request to a route handler (POST + paylo options: { authRequired: true, xsrfRequired: true, + access: 'public', tags: [], timeout: { payload: 10000, diff --git a/packages/core/http/core-http-server-internal/src/http_server.ts b/packages/core/http/core-http-server-internal/src/http_server.ts index fb19795d77dce..1ef5be6c67a54 100644 --- a/packages/core/http/core-http-server-internal/src/http_server.ts +++ b/packages/core/http/core-http-server-internal/src/http_server.ts @@ -524,6 +524,7 @@ export class HttpServer { const kibanaRouteOptions: KibanaRouteOptions = { xsrfRequired: route.options.xsrfRequired ?? !isSafeMethod(route.method), + access: route.options.access ?? (route.path.startsWith('/internal') ? 'internal' : 'public'), }; this.server!.route({ diff --git a/packages/core/http/core-http-server-internal/src/lifecycle_handlers.test.ts b/packages/core/http/core-http-server-internal/src/lifecycle_handlers.test.ts index 5e182005fd40c..d13bd001bbbb9 100644 --- a/packages/core/http/core-http-server-internal/src/lifecycle_handlers.test.ts +++ b/packages/core/http/core-http-server-internal/src/lifecycle_handlers.test.ts @@ -167,6 +167,7 @@ describe('xsrf post-auth handler', () => { path: '/some-path', kibanaRouteOptions: { xsrfRequired: false, + access: 'public', }, }); diff --git a/packages/core/http/core-http-server-internal/src/lifecycle_handlers.ts b/packages/core/http/core-http-server-internal/src/lifecycle_handlers.ts index 3fe9c8ac727ff..af148413265e8 100644 --- a/packages/core/http/core-http-server-internal/src/lifecycle_handlers.ts +++ b/packages/core/http/core-http-server-internal/src/lifecycle_handlers.ts @@ -60,6 +60,7 @@ export const createVersionCheckPostAuthHandler = (kibanaVersion: string): OnPost }; }; +// TODO: implement header required for accessing internal routes. See https://github.com/elastic/kibana/issues/151940 export const createCustomHeadersPreResponseHandler = (config: HttpConfig): OnPreResponseHandler => { const { name: serverName, diff --git a/packages/core/http/core-http-server/src/router/request.ts b/packages/core/http/core-http-server/src/router/request.ts index ef33bec14f841..e0664cb1ea29a 100644 --- a/packages/core/http/core-http-server/src/router/request.ts +++ b/packages/core/http/core-http-server/src/router/request.ts @@ -19,6 +19,7 @@ import type { Headers } from './headers'; */ export interface KibanaRouteOptions extends RouteOptionsApp { xsrfRequired: boolean; + access: 'internal' | 'public'; } /** diff --git a/packages/core/http/core-http-server/src/router/route.ts b/packages/core/http/core-http-server/src/router/route.ts index 78d76bb4ba7b8..e2b11aec08e1a 100644 --- a/packages/core/http/core-http-server/src/router/route.ts +++ b/packages/core/http/core-http-server/src/router/route.ts @@ -120,6 +120,18 @@ export interface RouteConfigOptions { */ xsrfRequired?: Method extends 'get' ? never : boolean; + /** + * Defines intended request origin of the route: + * - public. The route is public, declared stable and intended for external access. + * In the future, may require an incomming request to contain a specified header. + * - internal. The route is internal and intended for internal access only. + * + * If not declared, infers access from route path: + * - access =`internal` for '/internal' route path prefix + * - access = `public` for everything else + */ + access?: 'public' | 'internal'; + /** * Additional metadata tag strings to attach to the route. */ diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/index.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/index.ts index 9a4a728184388..61856a30cfc10 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/index.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/index.ts @@ -28,7 +28,7 @@ export { cloneIndex, waitForTask, updateAndPickupMappings, - updateTargetMappingsMeta, + updateMappings, updateAliases, transformDocs, setWriteBlock, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/index.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/index.ts index 2593ac7867d1e..7e380d3a7ad17 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/index.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/index.ts @@ -6,7 +6,8 @@ * Side Public License, v 1. */ -import { type Either, right } from 'fp-ts/lib/Either'; +import type { Either } from 'fp-ts/lib/Either'; +import { right } from 'fp-ts/lib/Either'; import type { RetryableEsClientError } from './catch_retryable_es_client_errors'; import type { DocumentsTransformFailed } from '../core/migrate_raw_docs'; @@ -37,11 +38,8 @@ export type { CloneIndexResponse, CloneIndexParams } from './clone_index'; export { cloneIndex } from './clone_index'; export type { WaitForIndexStatusParams, IndexNotYellowTimeout } from './wait_for_index_status'; -import { - type IndexNotGreenTimeout, - type IndexNotYellowTimeout, - waitForIndexStatus, -} from './wait_for_index_status'; +import type { IndexNotGreenTimeout, IndexNotYellowTimeout } from './wait_for_index_status'; +import { waitForIndexStatus } from './wait_for_index_status'; export type { WaitForTaskResponse, WaitForTaskCompletionTimeout } from './wait_for_task'; import { waitForTask, WaitForTaskCompletionTimeout } from './wait_for_task'; @@ -85,8 +83,6 @@ export { createIndex } from './create_index'; export { checkTargetMappings } from './check_target_mappings'; -export { updateTargetMappingsMeta } from './update_target_mappings_meta'; - export const noop = async (): Promise> => right('noop' as const); export type { @@ -95,6 +91,8 @@ export type { } from './update_and_pickup_mappings'; export { updateAndPickupMappings } from './update_and_pickup_mappings'; +export { updateMappings } from './update_mappings'; + import type { UnknownDocsFound } from './check_for_unknown_docs'; import type { IncompatibleClusterRoutingAllocation } from './initialize_action'; import { ClusterShardLimitExceeded } from './create_index'; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_and_pickup_mappings.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_and_pickup_mappings.test.ts index c1fd2f7b0b0fb..da243af9a7ebc 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_and_pickup_mappings.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_and_pickup_mappings.test.ts @@ -46,7 +46,7 @@ describe('updateAndPickupMappings', () => { expect(catchRetryableEsClientErrors).toHaveBeenCalledWith(retryableError); }); - it('updates the _mapping properties but not the _meta information', async () => { + it('calls the indices.putMapping with the mapping properties as well as the _meta information', async () => { const task = updateAndPickupMappings({ client, index: 'new_index', @@ -82,6 +82,13 @@ describe('updateAndPickupMappings', () => { dynamic: false, }, }, + _meta: { + migrationMappingPropertyHashes: { + references: '7997cf5a56cc02bdc9c93361bde732b0', + 'epm-packages': '860e23f4404fa1c33f430e6dad5d8fa2', + 'cases-connector-mappings': '17d2e9e0e170a21a471285a5d845353c', + }, + }, }); }); }); diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_and_pickup_mappings.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_and_pickup_mappings.ts index 7f89f862ce128..653a90746dea0 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_and_pickup_mappings.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_and_pickup_mappings.ts @@ -45,13 +45,11 @@ export const updateAndPickupMappings = ({ RetryableEsClientError, 'update_mappings_succeeded' > = () => { - // ._meta property will be updated on a later step - const { _meta, ...mappingsWithoutMeta } = mappings; return client.indices .putMapping({ index, timeout: DEFAULT_TIMEOUT, - ...mappingsWithoutMeta, + ...mappings, }) .then(() => { // Ignore `acknowledged: false`. When the coordinating node accepts diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_mappings.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_mappings.test.ts new file mode 100644 index 0000000000000..133f07d7460e5 --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_mappings.test.ts @@ -0,0 +1,147 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import * as Either from 'fp-ts/lib/Either'; +import type { TransportResult } from '@elastic/elasticsearch'; +import { errors as EsErrors } from '@elastic/elasticsearch'; +import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; +import { catchRetryableEsClientErrors } from './catch_retryable_es_client_errors'; +import { updateMappings } from './update_mappings'; +import { DEFAULT_TIMEOUT } from './constants'; + +jest.mock('./catch_retryable_es_client_errors'); + +describe('updateMappings', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + const createErrorClient = (response: Partial>>) => { + // Create a mock client that returns the desired response + const apiResponse = elasticsearchClientMock.createApiResponse(response); + const error = new EsErrors.ResponseError(apiResponse); + const client = elasticsearchClientMock.createInternalClient( + elasticsearchClientMock.createErrorTransportRequestPromise(error) + ); + + return { client, error }; + }; + + it('resolves left if the mappings are not compatible (aka 400 illegal_argument_exception from ES)', async () => { + const { client } = createErrorClient({ + statusCode: 400, + body: { + error: { + type: 'illegal_argument_exception', + reason: 'mapper [action.actionTypeId] cannot be changed from type [keyword] to [text]', + }, + }, + }); + + const task = updateMappings({ + client, + index: 'new_index', + mappings: { + properties: { + created_at: { + type: 'date', + }, + }, + _meta: { + migrationMappingPropertyHashes: { + references: '7997cf5a56cc02bdc9c93361bde732b0', + 'epm-packages': '860e23f4404fa1c33f430e6dad5d8fa2', + 'cases-connector-mappings': '17d2e9e0e170a21a471285a5d845353c', + }, + }, + }, + }); + + const res = await task(); + + expect(Either.isLeft(res)).toEqual(true); + expect(res).toMatchInlineSnapshot(` + Object { + "_tag": "Left", + "left": Object { + "type": "incompatible_mapping_exception", + }, + } + `); + }); + + it('calls catchRetryableEsClientErrors when the promise rejects', async () => { + const { client, error: retryableError } = createErrorClient({ + statusCode: 503, + body: { error: { type: 'es_type', reason: 'es_reason' } }, + }); + + const task = updateMappings({ + client, + index: 'new_index', + mappings: { + properties: { + created_at: { + type: 'date', + }, + }, + _meta: {}, + }, + }); + try { + await task(); + } catch (e) { + /** ignore */ + } + + expect(catchRetryableEsClientErrors).toHaveBeenCalledWith(retryableError); + }); + + it('updates the mapping information of the desired index', async () => { + const client = elasticsearchClientMock.createInternalClient(); + + const task = updateMappings({ + client, + index: 'new_index', + mappings: { + properties: { + created_at: { + type: 'date', + }, + }, + _meta: { + migrationMappingPropertyHashes: { + references: '7997cf5a56cc02bdc9c93361bde732b0', + 'epm-packages': '860e23f4404fa1c33f430e6dad5d8fa2', + 'cases-connector-mappings': '17d2e9e0e170a21a471285a5d845353c', + }, + }, + }, + }); + + const res = await task(); + expect(Either.isRight(res)).toBe(true); + expect(client.indices.putMapping).toHaveBeenCalledTimes(1); + expect(client.indices.putMapping).toHaveBeenCalledWith({ + index: 'new_index', + timeout: DEFAULT_TIMEOUT, + properties: { + created_at: { + type: 'date', + }, + }, + _meta: { + migrationMappingPropertyHashes: { + references: '7997cf5a56cc02bdc9c93361bde732b0', + 'epm-packages': '860e23f4404fa1c33f430e6dad5d8fa2', + 'cases-connector-mappings': '17d2e9e0e170a21a471285a5d845353c', + }, + }, + }); + }); +}); diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_mappings.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_mappings.ts new file mode 100644 index 0000000000000..4cf57f3ce7a8d --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_mappings.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import * as Either from 'fp-ts/lib/Either'; +import * as TaskEither from 'fp-ts/lib/TaskEither'; +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; +import { catchRetryableEsClientErrors } from './catch_retryable_es_client_errors'; +import type { RetryableEsClientError } from './catch_retryable_es_client_errors'; +import { DEFAULT_TIMEOUT } from './constants'; + +/** @internal */ +export interface UpdateMappingsParams { + client: ElasticsearchClient; + index: string; + mappings: IndexMapping; +} + +/** @internal */ +export interface IncompatibleMappingException { + type: 'incompatible_mapping_exception'; +} + +/** + * Updates an index's mappings and runs an pickupUpdatedMappings task so that the mapping + * changes are "picked up". Returns a taskId to track progress. + */ +export const updateMappings = ({ + client, + index, + mappings, +}: UpdateMappingsParams): TaskEither.TaskEither< + RetryableEsClientError | IncompatibleMappingException, + 'update_mappings_succeeded' +> => { + return () => { + return client.indices + .putMapping({ + index, + timeout: DEFAULT_TIMEOUT, + ...mappings, + }) + .then(() => Either.right('update_mappings_succeeded' as const)) + .catch((res) => { + const errorType = res?.body?.error?.type; + // ES throws this exact error when attempting to make incompatible updates to the mappigns + if ( + res?.statusCode === 400 && + (errorType === 'illegal_argument_exception' || + errorType === 'strict_dynamic_mapping_exception' || + errorType === 'mapper_parsing_exception') + ) { + return Either.left({ type: 'incompatible_mapping_exception' }); + } + return catchRetryableEsClientErrors(res); + }); + }; +}; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_target_mappings_meta.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_target_mappings_meta.test.ts deleted file mode 100644 index 9116d5389f2ec..0000000000000 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_target_mappings_meta.test.ts +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 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 { catchRetryableEsClientErrors } from './catch_retryable_es_client_errors'; -import { errors as EsErrors } from '@elastic/elasticsearch'; -import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; -import { updateTargetMappingsMeta } from './update_target_mappings_meta'; -import { DEFAULT_TIMEOUT } from './constants'; - -jest.mock('./catch_retryable_es_client_errors'); - -describe('updateTargetMappingsMeta', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - // Create a mock client that rejects all methods with a 503 status code - // response. - const retryableError = new EsErrors.ResponseError( - elasticsearchClientMock.createApiResponse({ - statusCode: 503, - body: { error: { type: 'es_type', reason: 'es_reason' } }, - }) - ); - const client = elasticsearchClientMock.createInternalClient( - elasticsearchClientMock.createErrorTransportRequestPromise(retryableError) - ); - - it('calls catchRetryableEsClientErrors when the promise rejects', async () => { - const task = updateTargetMappingsMeta({ - client, - index: 'new_index', - meta: {}, - }); - try { - await task(); - } catch (e) { - /** ignore */ - } - - expect(catchRetryableEsClientErrors).toHaveBeenCalledWith(retryableError); - }); - - it('updates the _meta information of the desired index', async () => { - const task = updateTargetMappingsMeta({ - client, - index: 'new_index', - meta: { - migrationMappingPropertyHashes: { - references: '7997cf5a56cc02bdc9c93361bde732b0', - 'epm-packages': '860e23f4404fa1c33f430e6dad5d8fa2', - 'cases-connector-mappings': '17d2e9e0e170a21a471285a5d845353c', - }, - }, - }); - try { - await task(); - } catch (e) { - /** ignore */ - } - - expect(client.indices.putMapping).toHaveBeenCalledTimes(1); - expect(client.indices.putMapping).toHaveBeenCalledWith({ - index: 'new_index', - timeout: DEFAULT_TIMEOUT, - _meta: { - migrationMappingPropertyHashes: { - references: '7997cf5a56cc02bdc9c93361bde732b0', - 'epm-packages': '860e23f4404fa1c33f430e6dad5d8fa2', - 'cases-connector-mappings': '17d2e9e0e170a21a471285a5d845353c', - }, - }, - }); - }); -}); diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_target_mappings_meta.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_target_mappings_meta.ts deleted file mode 100644 index 05f954b38fe71..0000000000000 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_target_mappings_meta.ts +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ -import * as Either from 'fp-ts/lib/Either'; -import * as TaskEither from 'fp-ts/lib/TaskEither'; - -import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import { IndexMappingMeta } from '@kbn/core-saved-objects-base-server-internal'; - -import { - catchRetryableEsClientErrors, - RetryableEsClientError, -} from './catch_retryable_es_client_errors'; -import { DEFAULT_TIMEOUT } from './constants'; - -/** @internal */ -export interface UpdateTargetMappingsMetaParams { - client: ElasticsearchClient; - index: string; - meta?: IndexMappingMeta; -} -/** - * Updates an index's mappings _meta information - */ -export const updateTargetMappingsMeta = - ({ - client, - index, - meta, - }: UpdateTargetMappingsMetaParams): TaskEither.TaskEither< - RetryableEsClientError, - 'update_mappings_meta_succeeded' - > => - () => { - return client.indices - .putMapping({ - index, - timeout: DEFAULT_TIMEOUT, - _meta: meta || {}, - }) - .then(() => { - // Ignore `acknowledged: false`. When the coordinating node accepts - // the new cluster state update but not all nodes have applied the - // update within the timeout `acknowledged` will be false. However, - // retrying this update will always immediately result in `acknowledged: - // true` even if there are still nodes which are falling behind with - // cluster state updates. - return Either.right('update_mappings_meta_succeeded' as const); - }) - .catch(catchRetryableEsClientErrors); - }; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.ts index 19ef5d66c0eb5..15691632a4399 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.ts @@ -107,12 +107,12 @@ export function addExcludedTypesToBoolQuery( /** * Add the given clauses to the 'must' of the given query + * @param filterClauses the clauses to be added to a 'must' * @param boolQuery the bool query to be enriched - * @param mustClauses the clauses to be added to a 'must' * @returns a new query container with the enriched query */ export function addMustClausesToBoolQuery( - mustClauses: QueryDslQueryContainer[], + filterClauses: QueryDslQueryContainer[], boolQuery?: QueryDslBoolQuery ): QueryDslQueryContainer { let must: QueryDslQueryContainer[] = []; @@ -121,7 +121,7 @@ export function addMustClausesToBoolQuery( must = must.concat(boolQuery.must); } - must.push(...mustClauses); + must.push(...filterClauses); return { bool: { @@ -133,8 +133,8 @@ export function addMustClausesToBoolQuery( /** * Add the given clauses to the 'must_not' of the given query - * @param boolQuery the bool query to be enriched * @param filterClauses the clauses to be added to a 'must_not' + * @param boolQuery the bool query to be enriched * @returns a new query container with the enriched query */ export function addMustNotClausesToBoolQuery( diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.test.ts index c07538d1c1184..4eccf11e6a65b 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.test.ts @@ -13,6 +13,7 @@ import type { IndexMapping } from '@kbn/core-saved-objects-base-server-internal' import type { BaseState, CalculateExcludeFiltersState, + UpdateSourceMappingsState, CheckTargetMappingsState, CheckUnknownDocumentsState, CheckVersionIndexReadyActions, @@ -1298,13 +1299,12 @@ describe('migrations v2 model', () => { sourceIndexMappings: actualMappings, }; - test('WAIT_FOR_YELLOW_SOURCE -> CHECK_UNKNOWN_DOCUMENTS', () => { + test('WAIT_FOR_YELLOW_SOURCE -> UPDATE_SOURCE_MAPPINGS', () => { const res: ResponseType<'WAIT_FOR_YELLOW_SOURCE'> = Either.right({}); const newState = model(changedMappingsState, res); - expect(newState.controlState).toEqual('CHECK_UNKNOWN_DOCUMENTS'); expect(newState).toMatchObject({ - controlState: 'CHECK_UNKNOWN_DOCUMENTS', + controlState: 'UPDATE_SOURCE_MAPPINGS', sourceIndex: Option.some('.kibana_7.11.0_001'), sourceIndexMappings: actualMappings, }); @@ -1330,6 +1330,49 @@ describe('migrations v2 model', () => { }); }); + describe('UPDATE_SOURCE_MAPPINGS', () => { + const checkCompatibleMappingsState: UpdateSourceMappingsState = { + ...baseState, + controlState: 'UPDATE_SOURCE_MAPPINGS', + sourceIndex: Option.some('.kibana_7.11.0_001') as Option.Some, + sourceIndexMappings: baseState.targetIndexMappings, + aliases: { + '.kibana': '.kibana_7.11.0_001', + '.kibana_7.11.0': '.kibana_7.11.0_001', + }, + }; + + describe('if action succeeds', () => { + test('UPDATE_SOURCE_MAPPINGS -> CLEANUP_UNKNOWN_AND_EXCLUDED', () => { + const res: ResponseType<'UPDATE_SOURCE_MAPPINGS'> = Either.right( + 'update_mappings_succeeded' as const + ); + const newState = model(checkCompatibleMappingsState, res); + + expect(newState).toMatchObject({ + controlState: 'CLEANUP_UNKNOWN_AND_EXCLUDED', + targetIndex: '.kibana_7.11.0_001', + versionIndexReadyActions: Option.none, + }); + }); + }); + + describe('if action fails', () => { + test('UPDATE_SOURCE_MAPPINGS -> CHECK_UNKNOWN_DOCUMENTS', () => { + const res: ResponseType<'UPDATE_SOURCE_MAPPINGS'> = Either.left({ + type: 'incompatible_mapping_exception', + }); + const newState = model(checkCompatibleMappingsState, res); + + expect(newState).toMatchObject({ + controlState: 'CHECK_UNKNOWN_DOCUMENTS', + sourceIndex: Option.some('.kibana_7.11.0_001'), + sourceIndexMappings: baseState.targetIndexMappings, + }); + }); + }); + }); + describe('CLEANUP_UNKNOWN_AND_EXCLUDED', () => { const cleanupUnknownAndExcluded: CleanupUnknownAndExcluded = { ...baseState, @@ -2693,7 +2736,7 @@ describe('migrations v2 model', () => { test('UPDATE_TARGET_MAPPINGS_META -> CHECK_VERSION_INDEX_READY_ACTIONS if the mapping _meta information is successfully updated', () => { const res: ResponseType<'UPDATE_TARGET_MAPPINGS_META'> = Either.right( - 'update_mappings_meta_succeeded' + 'update_mappings_succeeded' ); const newState = model(updateTargetMappingsMetaState, res) as CheckVersionIndexReadyActions; expect(newState.controlState).toBe('CHECK_VERSION_INDEX_READY_ACTIONS'); diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.ts index 4c2a9147eb125..f1cb94d276b35 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.ts @@ -424,7 +424,6 @@ export const model = (currentState: State, resW: ResponseType): } else if (stateP.controlState === 'WAIT_FOR_YELLOW_SOURCE') { const res = resW as ExcludeRetryableEsError>; if (Either.isRight(res)) { - // check the existing mappings to see if we can avoid reindexing if ( // source exists Boolean(stateP.sourceIndexMappings._meta?.migrationMappingPropertyHashes) && @@ -434,9 +433,9 @@ export const model = (currentState: State, resW: ResponseType): stateP.sourceIndexMappings, /* expected */ stateP.targetIndexMappings - ) && - Math.random() < 10 + ) ) { + // the existing mappings match, we can avoid reindexing return { ...stateP, controlState: 'CLEANUP_UNKNOWN_AND_EXCLUDED', @@ -446,7 +445,7 @@ export const model = (currentState: State, resW: ResponseType): } else { return { ...stateP, - controlState: 'CHECK_UNKNOWN_DOCUMENTS', + controlState: 'UPDATE_SOURCE_MAPPINGS', }; } } else if (Either.isLeft(res)) { @@ -465,6 +464,28 @@ export const model = (currentState: State, resW: ResponseType): } else { return throwBadResponse(stateP, res); } + } else if (stateP.controlState === 'UPDATE_SOURCE_MAPPINGS') { + const res = resW as ExcludeRetryableEsError>; + if (Either.isRight(res)) { + return { + ...stateP, + controlState: 'CLEANUP_UNKNOWN_AND_EXCLUDED', + targetIndex: stateP.sourceIndex.value!, // We preserve the same index, source == target (E.g: ".xx8.7.0_001") + versionIndexReadyActions: Option.none, + }; + } else if (Either.isLeft(res)) { + const left = res.left; + if (isTypeof(left, 'incompatible_mapping_exception')) { + return { + ...stateP, + controlState: 'CHECK_UNKNOWN_DOCUMENTS', + }; + } else { + return throwBadResponse(stateP, left as never); + } + } else { + return throwBadResponse(stateP, res); + } } else if (stateP.controlState === 'CLEANUP_UNKNOWN_AND_EXCLUDED') { const res = resW as ExcludeRetryableEsError>; if (Either.isRight(res)) { diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.ts index 605dd149855e7..8cebce9995900 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.ts @@ -7,44 +7,46 @@ */ import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { omit } from 'lodash'; import type { AllActionStates, - ReindexSourceToTempOpenPit, - ReindexSourceToTempRead, - ReindexSourceToTempClosePit, - ReindexSourceToTempTransform, - MarkVersionIndexReady, + CalculateExcludeFiltersState, + UpdateSourceMappingsState, + CheckTargetMappingsState, + CheckUnknownDocumentsState, + CleanupUnknownAndExcluded, + CleanupUnknownAndExcludedWaitForTaskState, + CloneTempToSource, + CreateNewTargetState, + CreateReindexTempState, InitState, LegacyCreateReindexTargetState, LegacyDeleteState, LegacyReindexState, LegacyReindexWaitForTaskState, LegacySetWriteBlockState, + MarkVersionIndexReady, + MarkVersionIndexReadyConflict, + OutdatedDocumentsRefresh, + OutdatedDocumentsSearchClosePit, + OutdatedDocumentsSearchOpenPit, + OutdatedDocumentsSearchRead, OutdatedDocumentsTransform, + PrepareCompatibleMigration, + RefreshTarget, + ReindexSourceToTempClosePit, + ReindexSourceToTempIndexBulk, + ReindexSourceToTempOpenPit, + ReindexSourceToTempRead, + ReindexSourceToTempTransform, SetSourceWriteBlockState, + SetTempWriteBlock, State, + TransformedDocumentsBulkIndex, UpdateTargetMappingsState, UpdateTargetMappingsWaitForTaskState, - CreateReindexTempState, - MarkVersionIndexReadyConflict, - CreateNewTargetState, - CloneTempToSource, - SetTempWriteBlock, - WaitForYellowSourceState, - TransformedDocumentsBulkIndex, - ReindexSourceToTempIndexBulk, - OutdatedDocumentsSearchOpenPit, - OutdatedDocumentsSearchRead, - OutdatedDocumentsSearchClosePit, - RefreshTarget, - OutdatedDocumentsRefresh, - CheckUnknownDocumentsState, - CalculateExcludeFiltersState, WaitForMigrationCompletionState, - CheckTargetMappingsState, - PrepareCompatibleMigration, - CleanupUnknownAndExcluded, - CleanupUnknownAndExcludedWaitForTaskState, + WaitForYellowSourceState, } from './state'; import type { TransformRawDocs } from './types'; import * as Actions from './actions'; @@ -70,6 +72,12 @@ export const nextActionMap = (client: ElasticsearchClient, transformRawDocs: Tra Actions.fetchIndices({ client, indices: [state.currentAlias, state.versionAlias] }), WAIT_FOR_YELLOW_SOURCE: (state: WaitForYellowSourceState) => Actions.waitForIndexStatus({ client, index: state.sourceIndex.value, status: 'yellow' }), + UPDATE_SOURCE_MAPPINGS: (state: UpdateSourceMappingsState) => + Actions.updateMappings({ + client, + index: state.sourceIndex.value, // attempt to update source mappings in-place + mappings: omit(state.targetIndexMappings, ['_meta']), // ._meta property will be updated on a later step + }), CLEANUP_UNKNOWN_AND_EXCLUDED: (state: CleanupUnknownAndExcluded) => Actions.cleanupUnknownAndExcluded({ client, @@ -163,7 +171,7 @@ export const nextActionMap = (client: ElasticsearchClient, transformRawDocs: Tra Actions.updateAndPickupMappings({ client, index: state.targetIndex, - mappings: state.targetIndexMappings, + mappings: omit(state.targetIndexMappings, ['_meta']), // ._meta property will be updated on a later step }), UPDATE_TARGET_MAPPINGS_WAIT_FOR_TASK: (state: UpdateTargetMappingsWaitForTaskState) => Actions.waitForPickupUpdatedMappingsTask({ @@ -172,10 +180,10 @@ export const nextActionMap = (client: ElasticsearchClient, transformRawDocs: Tra timeout: '60s', }), UPDATE_TARGET_MAPPINGS_META: (state: UpdateTargetMappingsState) => - Actions.updateTargetMappingsMeta({ + Actions.updateMappings({ client, index: state.targetIndex, - meta: state.targetIndexMappings._meta, + mappings: state.targetIndexMappings, }), CHECK_VERSION_INDEX_READY_ACTIONS: () => Actions.noop, OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT: (state: OutdatedDocumentsSearchOpenPit) => diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/state.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/state.ts index a091a2972343f..4ac550c89ff58 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/state.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/state.ts @@ -243,6 +243,13 @@ export interface WaitForYellowSourceState extends BaseWithSource { readonly aliases: Record; } +export interface UpdateSourceMappingsState extends BaseState { + readonly controlState: 'UPDATE_SOURCE_MAPPINGS'; + readonly sourceIndex: Option.Some; + readonly sourceIndexMappings: IndexMapping; + readonly aliases: Record; +} + export interface CheckUnknownDocumentsState extends BaseWithSource { /** Check if any unknown document is present in the source index */ readonly controlState: 'CHECK_UNKNOWN_DOCUMENTS'; @@ -493,6 +500,7 @@ export type State = Readonly< | WaitForMigrationCompletionState | DoneState | WaitForYellowSourceState + | UpdateSourceMappingsState | CheckUnknownDocumentsState | SetSourceWriteBlockState | CalculateExcludeFiltersState diff --git a/packages/core/versioning/core-version-http-server/src/example.ts b/packages/core/versioning/core-version-http-server/src/example.ts index de529ccb07d9d..b63c75e86a562 100644 --- a/packages/core/versioning/core-version-http-server/src/example.ts +++ b/packages/core/versioning/core-version-http-server/src/example.ts @@ -22,7 +22,7 @@ const versionedRouter = vtk.createVersionedRouter({ router }); const versionedRoute = versionedRouter .post({ path: '/api/my-app/foo/{id?}', - options: { timeout: { payload: 60000 } }, + options: { timeout: { payload: 60000 }, access: 'public' }, }) .addVersion( { diff --git a/packages/core/versioning/core-version-http-server/src/version_http_toolkit.ts b/packages/core/versioning/core-version-http-server/src/version_http_toolkit.ts index 719e0075c0070..7d8dd7765e476 100644 --- a/packages/core/versioning/core-version-http-server/src/version_http_toolkit.ts +++ b/packages/core/versioning/core-version-http-server/src/version_http_toolkit.ts @@ -13,6 +13,7 @@ import type { RequestHandler, RouteValidatorFullConfig, RequestHandlerContextBase, + RouteConfigOptions, } from '@kbn/core-http-server'; type RqCtx = RequestHandlerContextBase; @@ -45,7 +46,7 @@ export interface CreateVersionedRouterArgs { * const versionedRoute = versionedRouter * .post({ * path: '/api/my-app/foo/{id?}', - * options: { timeout: { payload: 60000 } }, + * options: { timeout: { payload: 60000 }, access: 'public' }, * }) * .addVersion( * { @@ -99,14 +100,28 @@ export interface VersionHTTPToolkit { ): VersionedRouter; } +/** + * Converts an input property from optional to required. Needed for making RouteConfigOptions['access'] required. + */ +type WithRequiredProperty = Type & { + [Property in Key]-?: Type[Property]; +}; + +/** + * Versioned route access flag, required + * - '/api/foo' is 'public' + * - '/internal/my-foo' is 'internal' + * Required + */ +type VersionedRouteConfigOptions = WithRequiredProperty, 'access'>; /** * Configuration for a versioned route * @experimental */ export type VersionedRouteConfig = Omit< RouteConfig, - 'validate' ->; + 'validate' | 'options' +> & { options: VersionedRouteConfigOptions }; /** * Create an {@link VersionedRoute | versioned route}. diff --git a/packages/kbn-alerts-as-data-utils/index.ts b/packages/kbn-alerts-as-data-utils/index.ts new file mode 100644 index 0000000000000..7b2cad2ca5440 --- /dev/null +++ b/packages/kbn-alerts-as-data-utils/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 * from './src/field_maps'; diff --git a/packages/kbn-alerts-as-data-utils/kibana.jsonc b/packages/kbn-alerts-as-data-utils/kibana.jsonc new file mode 100644 index 0000000000000..07e8490dde7b5 --- /dev/null +++ b/packages/kbn-alerts-as-data-utils/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/alerts-as-data-utils", + "owner": "@elastic/response-ops" +} diff --git a/packages/kbn-alerts-as-data-utils/package.json b/packages/kbn-alerts-as-data-utils/package.json new file mode 100644 index 0000000000000..25aa26b3d435c --- /dev/null +++ b/packages/kbn-alerts-as-data-utils/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/alerts-as-data-utils", + "private": true, + "version": "1.0.0", + "license": "SSPL-1.0 OR Elastic License 2.0" +} \ No newline at end of file diff --git a/x-pack/plugins/alerting/common/alert_schema/field_maps/alert_field_map.ts b/packages/kbn-alerts-as-data-utils/src/field_maps/alert_field_map.ts similarity index 77% rename from x-pack/plugins/alerting/common/alert_schema/field_maps/alert_field_map.ts rename to packages/kbn-alerts-as-data-utils/src/field_maps/alert_field_map.ts index 4613415e0fa00..8e5a606a91017 100644 --- a/x-pack/plugins/alerting/common/alert_schema/field_maps/alert_field_map.ts +++ b/packages/kbn-alerts-as-data-utils/src/field_maps/alert_field_map.ts @@ -1,16 +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. + * 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 { ALERT_ACTION_GROUP, + ALERT_CASE_IDS, ALERT_DURATION, ALERT_END, ALERT_FLAPPING, - ALERT_ID, + ALERT_FLAPPING_HISTORY, + ALERT_INSTANCE_ID, + ALERT_LAST_DETECTED, ALERT_REASON, ALERT_RULE_CATEGORY, ALERT_RULE_CONSUMER, @@ -27,92 +31,93 @@ import { ALERT_UUID, ALERT_WORKFLOW_STATUS, SPACE_IDS, + TIMESTAMP, VERSION, } from '@kbn/rule-data-utils'; export const alertFieldMap = { - [ALERT_RULE_PARAMETERS]: { - type: 'object', - enabled: false, + [ALERT_ACTION_GROUP]: { + type: 'keyword', + array: false, required: false, }, - [ALERT_RULE_TYPE_ID]: { + [ALERT_CASE_IDS]: { type: 'keyword', + array: true, + required: false, + }, + [ALERT_DURATION]: { + type: 'long', array: false, - required: true, + required: false, }, - [ALERT_RULE_CONSUMER]: { - type: 'keyword', + [ALERT_END]: { + type: 'date', array: false, - required: true, + required: false, }, - [ALERT_RULE_PRODUCER]: { - type: 'keyword', + [ALERT_FLAPPING]: { + type: 'boolean', array: false, - required: true, + required: false, }, - [SPACE_IDS]: { - type: 'keyword', + [ALERT_FLAPPING_HISTORY]: { + type: 'boolean', array: true, - required: true, - }, - [ALERT_UUID]: { - type: 'keyword', - array: false, - required: true, + required: false, }, - [ALERT_ID]: { + [ALERT_INSTANCE_ID]: { type: 'keyword', array: false, required: true, }, - [ALERT_START]: { + [ALERT_LAST_DETECTED]: { type: 'date', - array: false, required: false, - }, - [ALERT_TIME_RANGE]: { - type: 'date_range', - format: 'epoch_millis||strict_date_optional_time', array: false, - required: false, }, - [ALERT_END]: { - type: 'date', + [ALERT_REASON]: { + type: 'keyword', array: false, required: false, }, - [ALERT_DURATION]: { - type: 'long', + [ALERT_RULE_CATEGORY]: { + type: 'keyword', array: false, - required: false, + required: true, }, - [ALERT_STATUS]: { + [ALERT_RULE_CONSUMER]: { type: 'keyword', array: false, required: true, }, - [VERSION]: { - type: 'version', + [ALERT_RULE_EXECUTION_UUID]: { + type: 'keyword', array: false, required: false, }, - [ALERT_WORKFLOW_STATUS]: { + [ALERT_RULE_NAME]: { type: 'keyword', array: false, - required: false, + required: true, }, - [ALERT_ACTION_GROUP]: { - type: 'keyword', + [ALERT_RULE_PARAMETERS]: { array: false, + type: 'flattened', + ignore_above: 4096, required: false, }, - [ALERT_REASON]: { + [ALERT_RULE_PRODUCER]: { type: 'keyword', array: false, + required: true, + }, + [ALERT_RULE_TAGS]: { + type: 'keyword', + array: true, required: false, }, - [ALERT_RULE_CATEGORY]: { + [ALERT_RULE_TYPE_ID]: { type: 'keyword', array: false, required: true, @@ -122,26 +127,47 @@ export const alertFieldMap = { array: false, required: true, }, - [ALERT_RULE_EXECUTION_UUID]: { + [ALERT_START]: { + type: 'date', + array: false, + required: false, + }, + [ALERT_STATUS]: { type: 'keyword', array: false, + required: true, + }, + [ALERT_TIME_RANGE]: { + type: 'date_range', + format: 'epoch_millis||strict_date_optional_time', + array: false, required: false, }, - [ALERT_RULE_NAME]: { + [ALERT_UUID]: { type: 'keyword', array: false, required: true, }, - [ALERT_RULE_TAGS]: { + [ALERT_WORKFLOW_STATUS]: { type: 'keyword', - array: true, + array: false, required: false, }, - [ALERT_FLAPPING]: { - type: 'boolean', + [SPACE_IDS]: { + type: 'keyword', + array: true, + required: true, + }, + [TIMESTAMP]: { + type: 'date', + required: true, + array: false, + }, + [VERSION]: { + type: 'version', array: false, required: false, }, -}; +} as const; export type AlertFieldMap = typeof alertFieldMap; diff --git a/packages/kbn-alerts-as-data-utils/src/field_maps/ecs_field_map.ts b/packages/kbn-alerts-as-data-utils/src/field_maps/ecs_field_map.ts new file mode 100644 index 0000000000000..9294a12b4ce50 --- /dev/null +++ b/packages/kbn-alerts-as-data-utils/src/field_maps/ecs_field_map.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { EcsFlat } from '@kbn/ecs'; +import { EcsMetadata, FieldMap } from './types'; + +export const ecsFieldMap: FieldMap = Object.keys(EcsFlat).reduce((acc, currKey) => { + const value: EcsMetadata = EcsFlat[currKey as keyof typeof EcsFlat]; + return { + ...acc, + [currKey]: { + type: value.type, + array: value.normalize.includes('array'), + required: !!value.required, + ...(value.scaling_factor ? { scaling_factor: value.scaling_factor } : {}), + ...(value.ignore_above ? { ignore_above: value.ignore_above } : {}), + ...(value.multi_fields ? { multi_fields: value.multi_fields } : {}), + }, + }; +}, {}); + +export type EcsFieldMap = typeof ecsFieldMap; diff --git a/packages/kbn-alerts-as-data-utils/src/field_maps/index.ts b/packages/kbn-alerts-as-data-utils/src/field_maps/index.ts new file mode 100644 index 0000000000000..9aef7690b343c --- /dev/null +++ b/packages/kbn-alerts-as-data-utils/src/field_maps/index.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 * from './alert_field_map'; +export * from './ecs_field_map'; +export * from './legacy_alert_field_map'; +export type { FieldMap, MultiField } from './types'; diff --git a/packages/kbn-alerts-as-data-utils/src/field_maps/legacy_alert_field_map.ts b/packages/kbn-alerts-as-data-utils/src/field_maps/legacy_alert_field_map.ts new file mode 100644 index 0000000000000..6faa403188fdb --- /dev/null +++ b/packages/kbn-alerts-as-data-utils/src/field_maps/legacy_alert_field_map.ts @@ -0,0 +1,202 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { + ALERT_RISK_SCORE, + ALERT_RULE_AUTHOR, + ALERT_RULE_CREATED_AT, + ALERT_RULE_CREATED_BY, + ALERT_RULE_DESCRIPTION, + ALERT_RULE_ENABLED, + ALERT_RULE_FROM, + ALERT_RULE_INTERVAL, + ALERT_RULE_LICENSE, + ALERT_RULE_NOTE, + ALERT_RULE_REFERENCES, + ALERT_RULE_RULE_ID, + ALERT_RULE_RULE_NAME_OVERRIDE, + ALERT_RULE_TO, + ALERT_RULE_TYPE, + ALERT_RULE_UPDATED_AT, + ALERT_RULE_UPDATED_BY, + ALERT_RULE_VERSION, + ALERT_SEVERITY, + ALERT_SUPPRESSION_DOCS_COUNT, + ALERT_SUPPRESSION_END, + ALERT_SUPPRESSION_FIELD, + ALERT_SUPPRESSION_START, + ALERT_SUPPRESSION_VALUE, + ALERT_SYSTEM_STATUS, + ALERT_WORKFLOW_REASON, + ALERT_WORKFLOW_USER, + ECS_VERSION, + EVENT_ACTION, + EVENT_KIND, + TAGS, +} from '@kbn/rule-data-utils'; + +export const legacyAlertFieldMap = { + [ALERT_RISK_SCORE]: { + type: 'float', + array: false, + required: false, + }, + [ALERT_RULE_AUTHOR]: { + type: 'keyword', + array: false, + required: false, + }, + [ALERT_RULE_CREATED_AT]: { + type: 'date', + array: false, + required: false, + }, + [ALERT_RULE_CREATED_BY]: { + type: 'keyword', + array: false, + required: false, + }, + [ALERT_RULE_DESCRIPTION]: { + type: 'keyword', + array: false, + required: false, + }, + [ALERT_RULE_ENABLED]: { + type: 'keyword', + array: false, + required: false, + }, + [ALERT_RULE_FROM]: { + type: 'keyword', + array: false, + required: false, + }, + [ALERT_RULE_INTERVAL]: { + type: 'keyword', + array: false, + required: false, + }, + [ALERT_RULE_LICENSE]: { + type: 'keyword', + array: false, + required: false, + }, + [ALERT_RULE_NOTE]: { + type: 'keyword', + array: false, + required: false, + }, + [ALERT_RULE_REFERENCES]: { + type: 'keyword', + array: true, + required: false, + }, + [ALERT_RULE_RULE_ID]: { + type: 'keyword', + array: false, + required: false, + }, + [ALERT_RULE_RULE_NAME_OVERRIDE]: { + type: 'keyword', + array: false, + required: false, + }, + [ALERT_RULE_TO]: { + type: 'keyword', + array: false, + required: false, + }, + [ALERT_RULE_TYPE]: { + type: 'keyword', + array: false, + required: false, + }, + [ALERT_RULE_UPDATED_AT]: { + type: 'date', + array: false, + required: false, + }, + [ALERT_RULE_UPDATED_BY]: { + type: 'keyword', + array: false, + required: false, + }, + [ALERT_RULE_VERSION]: { + type: 'keyword', + array: false, + required: false, + }, + [ALERT_SEVERITY]: { + type: 'keyword', + array: false, + required: false, + }, + [ALERT_SUPPRESSION_DOCS_COUNT]: { + type: 'long', + array: false, + required: false, + }, + [ALERT_SUPPRESSION_END]: { + type: 'date', + array: false, + required: false, + }, + [ALERT_SUPPRESSION_FIELD]: { + type: 'keyword', + array: true, + required: false, + }, + [ALERT_SUPPRESSION_START]: { + type: 'date', + array: false, + required: false, + }, + [ALERT_SUPPRESSION_VALUE]: { + type: 'keyword', + array: true, + required: false, + }, + [ALERT_SYSTEM_STATUS]: { + type: 'keyword', + array: false, + required: false, + }, + [ALERT_WORKFLOW_REASON]: { + type: 'keyword', + array: false, + required: false, + }, + [ALERT_WORKFLOW_USER]: { + type: 'keyword', + array: false, + required: false, + }, + // get these from ecs field map when available + [ECS_VERSION]: { + type: 'keyword', + array: false, + required: false, + }, + [EVENT_ACTION]: { + type: 'keyword', + array: false, + required: false, + }, + [EVENT_KIND]: { + type: 'keyword', + array: false, + required: false, + }, + [TAGS]: { + type: 'keyword', + array: true, + required: false, + }, +} as const; + +export type LegacyAlertFieldMap = typeof legacyAlertFieldMap; diff --git a/packages/kbn-alerts-as-data-utils/src/field_maps/types.ts b/packages/kbn-alerts-as-data-utils/src/field_maps/types.ts new file mode 100644 index 0000000000000..04f9d045f6e28 --- /dev/null +++ b/packages/kbn-alerts-as-data-utils/src/field_maps/types.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export interface AllowedValue { + description?: string; + name?: string; +} + +export interface MultiField { + flat_name: string; + name: string; + type: string; +} + +export interface EcsMetadata { + allowed_values?: AllowedValue[]; + dashed_name: string; + description: string; + doc_values?: boolean; + example?: string | number | boolean; + flat_name: string; + ignore_above?: number; + index?: boolean; + level: string; + multi_fields?: MultiField[]; + name: string; + normalize: string[]; + required?: boolean; + scaling_factor?: number; + short: string; + type: string; +} + +export interface FieldMap { + [key: string]: { + type: string; + required: boolean; + array?: boolean; + doc_values?: boolean; + enabled?: boolean; + format?: string; + ignore_above?: number; + multi_fields?: MultiField[]; + index?: boolean; + path?: string; + scaling_factor?: number; + dynamic?: boolean | 'strict'; + }; +} diff --git a/packages/kbn-alerts-as-data-utils/tsconfig.json b/packages/kbn-alerts-as-data-utils/tsconfig.json new file mode 100644 index 0000000000000..00b7ffc082c95 --- /dev/null +++ b/packages/kbn-alerts-as-data-utils/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node" + ] + }, + "include": [ + "**/*.ts" + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [ + "@kbn/ecs", + "@kbn/rule-data-utils", + ] +} diff --git a/packages/kbn-apm-synthtrace-client/index.ts b/packages/kbn-apm-synthtrace-client/index.ts index 82f8efe28b40a..1868cb188582e 100644 --- a/packages/kbn-apm-synthtrace-client/index.ts +++ b/packages/kbn-apm-synthtrace-client/index.ts @@ -20,6 +20,7 @@ export type { } from './src/lib/apm/mobile_device'; export { httpExitSpan } from './src/lib/apm/span'; export { DistributedTrace } from './src/lib/dsl/distributed_trace_client'; +export { serviceMap } from './src/lib/dsl/service_map'; export type { Fields } from './src/lib/entity'; export type { Serializable } from './src/lib/serializable'; export { timerange } from './src/lib/timerange'; diff --git a/packages/kbn-apm-synthtrace-client/src/lib/dsl/service_map.test.ts b/packages/kbn-apm-synthtrace-client/src/lib/dsl/service_map.test.ts new file mode 100644 index 0000000000000..90078f584c172 --- /dev/null +++ b/packages/kbn-apm-synthtrace-client/src/lib/dsl/service_map.test.ts @@ -0,0 +1,278 @@ +/* + * Copyright 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 { pick } from 'lodash'; +import { ApmFields } from '../apm/apm_fields'; +import { BaseSpan } from '../apm/base_span'; +import { serviceMap, ServiceMapOpts } from './service_map'; + +describe('serviceMap', () => { + const TIMESTAMP = 1677693600000; + + describe('Basic definition', () => { + const BASIC_SERVICE_MAP_OPTS: ServiceMapOpts = { + services: [ + 'frontend-rum', + 'frontend-node', + 'advertService', + 'checkoutService', + 'cartService', + 'paymentService', + 'productCatalogService', + ], + definePaths([rum, node, adv, chk, cart, pay, prod]) { + return [ + [rum, node, adv, 'elasticsearch'], + [rum, node, cart, 'redis'], + [rum, node, chk, pay], + [chk, cart, 'redis'], + [rum, node, prod, 'elasticsearch'], + [chk, prod], + ]; + }, + }; + + it('should create an accurate set of trace paths', () => { + const serviceMapGenerator = serviceMap(BASIC_SERVICE_MAP_OPTS); + const transactions = serviceMapGenerator(TIMESTAMP); + expect(transactions.map(getTracePathLabel)).toMatchInlineSnapshot(` + Array [ + "frontend-rum → frontend-node → advertService → elasticsearch", + "frontend-rum → frontend-node → cartService → redis", + "frontend-rum → frontend-node → checkoutService → paymentService", + "checkoutService → cartService → redis", + "frontend-rum → frontend-node → productCatalogService → elasticsearch", + "checkoutService → productCatalogService", + ] + `); + }); + + it('should use a default agent name if not defined', () => { + const serviceMapGenerator = serviceMap(BASIC_SERVICE_MAP_OPTS); + const transactions = serviceMapGenerator(TIMESTAMP); + const traceDocs = transactions.flatMap(getTraceDocsSubset); + for (const doc of traceDocs) { + expect(doc).toHaveProperty(['agent.name'], 'nodejs'); + } + }); + + it('should use a default transaction/span names if not defined', () => { + const serviceMapGenerator = serviceMap(BASIC_SERVICE_MAP_OPTS); + const transactions = serviceMapGenerator(TIMESTAMP); + const traceDocs = transactions.map(getTraceDocsSubset); + for (let i = 0; i < traceDocs.length; i++) { + for (const doc of traceDocs[i]) { + const serviceName = doc['service.name']; + if (doc['processor.event'] === 'transaction') { + expect(doc).toHaveProperty(['transaction.name'], `GET /api/${serviceName}/${i}`); + } + if (doc['processor.event'] === 'span') { + if (doc['span.type'] === 'db') { + switch (doc['span.subtype']) { + case 'elasticsearch': + expect(doc).toHaveProperty(['span.name'], `GET ad-*/_search`); + break; + case 'redis': + expect(doc).toHaveProperty(['span.name'], `INCR item:i012345:count`); + break; + case 'sqlite': + expect(doc).toHaveProperty(['span.name'], `SELECT * FROM items`); + break; + } + } else { + expect(doc).toHaveProperty(['span.name'], `GET /api/${serviceName}/${i}`); + } + } + } + } + }); + + it('should create one parent transaction per trace', () => { + const serviceMapGenerator = serviceMap(BASIC_SERVICE_MAP_OPTS); + const transactions = serviceMapGenerator(TIMESTAMP); + const traces = transactions.map(getTraceDocsSubset); + for (const traceDocs of traces) { + const [transaction, ...spans] = traceDocs; + expect(transaction).toHaveProperty(['processor.event'], 'transaction'); + expect( + spans.every(({ 'processor.event': processorEvent }) => processorEvent === 'span') + ).toBe(true); + } + }); + }); + describe('Detailed definition', () => { + const DETAILED_SERVICE_MAP_OPTS: ServiceMapOpts = { + services: [ + { 'frontend-rum': 'rum-js' }, + { 'frontend-node': 'nodejs' }, + { advertService: 'java' }, + { checkoutService: 'go' }, + { cartService: 'dotnet' }, + { paymentService: 'nodejs' }, + { productCatalogService: 'go' }, + ], + definePaths([rum, node, adv, chk, cart, pay, prod]) { + return [ + [ + [rum, 'fetchAd'], + [node, 'GET /nodejs/adTag'], + [adv, 'APIRestController#getAd'], + ['elasticsearch', 'GET ad-*/_search'], + ], + [ + [rum, 'AddToCart'], + [node, 'POST /nodejs/addToCart'], + [cart, 'POST /dotnet/reserveProduct'], + ['redis', 'DECR inventory:i012345:stock'], + ], + { + path: [ + [rum, 'Checkout'], + [node, 'POST /nodejs/placeOrder'], + [chk, 'POST /go/placeOrder'], + [pay, 'POST /nodejs/processPayment'], + ], + transaction: (t) => t.defaults({ 'labels.name': 'transaction hook test' }), + }, + [ + [chk, 'POST /go/clearCart'], + [cart, 'PUT /dotnet/cart/c12345/reset'], + ['redis', 'INCR inventory:i012345:stock'], + ], + [ + [rum, 'ProductDashboard'], + [node, 'GET /nodejs/products'], + [prod, 'GET /go/product-catalog'], + ['elasticsearch', 'GET product-*/_search'], + ], + [ + [chk, 'PUT /go/update-inventory'], + [prod, 'PUT /go/product/i012345'], + ], + [pay], + ]; + }, + }; + + const SERVICE_AGENT_MAP: Record = { + 'frontend-rum': 'rum-js', + 'frontend-node': 'nodejs', + advertService: 'java', + checkoutService: 'go', + cartService: 'dotnet', + paymentService: 'nodejs', + productCatalogService: 'go', + }; + + it('should use the defined agent name for a given service', () => { + const serviceMapGenerator = serviceMap(DETAILED_SERVICE_MAP_OPTS); + const transactions = serviceMapGenerator(TIMESTAMP); + const traceDocs = transactions.flatMap(getTraceDocsSubset); + for (const doc of traceDocs) { + if (!(doc['service.name']! in SERVICE_AGENT_MAP)) { + throw new Error(`Unexpected service name '${doc['service.name']}' found`); + } + + expect(doc).toHaveProperty(['agent.name'], SERVICE_AGENT_MAP[doc['service.name']!]); + } + }); + + it('should use the defined transaction/span names for each trace document', () => { + const serviceMapGenerator = serviceMap(DETAILED_SERVICE_MAP_OPTS); + const transactions = serviceMapGenerator(TIMESTAMP); + const traceDocs = transactions.map((transaction) => { + return getTraceDocsSubset(transaction).map( + ({ 'span.name': spanName, 'transaction.name': transactionName }) => + transactionName || spanName + ); + }); + expect(traceDocs).toMatchInlineSnapshot(` + Array [ + Array [ + "fetchAd", + "fetchAd", + "GET /nodejs/adTag", + "APIRestController#getAd", + "GET ad-*/_search", + ], + Array [ + "AddToCart", + "AddToCart", + "POST /nodejs/addToCart", + "POST /dotnet/reserveProduct", + "DECR inventory:i012345:stock", + ], + Array [ + "Checkout", + "Checkout", + "POST /nodejs/placeOrder", + "POST /go/placeOrder", + "POST /nodejs/processPayment", + ], + Array [ + "POST /go/clearCart", + "POST /go/clearCart", + "PUT /dotnet/cart/c12345/reset", + "INCR inventory:i012345:stock", + ], + Array [ + "ProductDashboard", + "ProductDashboard", + "GET /nodejs/products", + "GET /go/product-catalog", + "GET product-*/_search", + ], + Array [ + "PUT /go/update-inventory", + "PUT /go/update-inventory", + "PUT /go/product/i012345", + ], + Array [ + "GET /api/paymentService/6", + "GET /api/paymentService/6", + ], + ] + `); + }); + + it('should apply the transaction hook function if defined', () => { + const serviceMapGenerator = serviceMap(DETAILED_SERVICE_MAP_OPTS); + const transactions = serviceMapGenerator(TIMESTAMP); + expect(transactions[2].fields['labels.name']).toBe('transaction hook test'); + }); + }); +}); + +function getTraceDocsSubset(transaction: BaseSpan): ApmFields[] { + const subsetFields = pick(transaction.fields, [ + 'processor.event', + 'service.name', + 'agent.name', + 'transaction.name', + 'span.name', + 'span.type', + 'span.subtype', + 'span.destination.service.resource', + ]); + + const children = transaction.getChildren(); + if (children) { + const childFields = children.flatMap((child) => getTraceDocsSubset(child)); + return [subsetFields, ...childFields]; + } + return [subsetFields]; +} + +function getTracePathLabel(transaction: BaseSpan) { + const traceDocs = getTraceDocsSubset(transaction); + const traceSpans = traceDocs.filter((doc) => doc['processor.event'] === 'span'); + const spanLabels = traceSpans.map((span) => + span['span.type'] === 'db' ? span['span.subtype'] : span['service.name'] + ); + return spanLabels.join(' → '); +} diff --git a/packages/kbn-apm-synthtrace-client/src/lib/dsl/service_map.ts b/packages/kbn-apm-synthtrace-client/src/lib/dsl/service_map.ts new file mode 100644 index 0000000000000..4c91352b11d02 --- /dev/null +++ b/packages/kbn-apm-synthtrace-client/src/lib/dsl/service_map.ts @@ -0,0 +1,156 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { AgentName } from '../../types/agent_names'; +import { apm } from '../apm'; +import { Instance } from '../apm/instance'; +import { elasticsearchSpan, redisSpan, sqliteSpan, Span } from '../apm/span'; +import { Transaction } from '../apm/transaction'; + +const ENVIRONMENT = 'Synthtrace: service_map'; + +function service(serviceName: string, agentName: AgentName, environment?: string) { + return apm + .service({ name: serviceName, environment: environment || ENVIRONMENT, agentName }) + .instance(serviceName); +} + +type DbSpan = 'elasticsearch' | 'redis' | 'sqlite'; +type ServiceMapNode = Instance | DbSpan; +type TransactionName = string; +type TraceItem = ServiceMapNode | [ServiceMapNode, TransactionName]; +type TracePath = TraceItem[]; + +function getTraceItem(traceItem: TraceItem) { + if (Array.isArray(traceItem)) { + const transactionName = traceItem[1]; + if (typeof traceItem[0] === 'string') { + const dbSpan = traceItem[0]; + return { dbSpan, transactionName, serviceInstance: undefined }; + } else { + const serviceInstance = traceItem[0]; + return { dbSpan: undefined, transactionName, serviceInstance }; + } + } else if (typeof traceItem === 'string') { + const dbSpan = traceItem; + return { dbSpan, transactionName: undefined, serviceInstance: undefined }; + } else { + const serviceInstance = traceItem; + return { dbSpan: undefined, transactionName: undefined, serviceInstance }; + } +} + +function getTransactionName( + transactionName: string | undefined, + serviceInstance: Instance, + index: number +) { + return transactionName || `GET /api/${serviceInstance.fields['service.name']}/${index}`; +} + +function getChildren( + childTraceItems: TracePath, + parentServiceInstance: Instance, + timestamp: number, + index: number +): Span[] { + if (childTraceItems.length === 0) { + return []; + } + const [first, ...rest] = childTraceItems; + const { dbSpan, serviceInstance, transactionName } = getTraceItem(first); + if (dbSpan) { + switch (dbSpan) { + case 'elasticsearch': + return [ + parentServiceInstance + .span(elasticsearchSpan(transactionName || 'GET ad-*/_search')) + .timestamp(timestamp) + .duration(1000), + ]; + case 'redis': + return [ + parentServiceInstance + .span(redisSpan(transactionName || 'INCR item:i012345:count')) + .timestamp(timestamp) + .duration(1000), + ]; + case 'sqlite': + return [ + parentServiceInstance + .span(sqliteSpan(transactionName || 'SELECT * FROM items')) + .timestamp(timestamp) + .duration(1000), + ]; + } + } + const childSpan = serviceInstance + .span({ + spanName: getTransactionName(transactionName, serviceInstance, index), + spanType: 'app', + }) + .timestamp(timestamp) + .duration(1000) + .children(...getChildren(rest, serviceInstance, timestamp, index)); + if (rest[0]) { + const next = getTraceItem(rest[0]); + if (next.serviceInstance) { + return [childSpan.destination(next.serviceInstance.fields['service.name']!)]; + } + } + return [childSpan]; +} + +interface TracePathOpts { + path: TracePath; + transaction?: (transaction: Transaction) => Transaction; +} +type PathDef = TracePath | TracePathOpts; +export interface ServiceMapOpts { + services: Array; + definePaths: (services: Instance[]) => PathDef[]; + environment?: string; +} + +export function serviceMap(options: ServiceMapOpts) { + const serviceInstances = options.services.map((s) => { + if (typeof s === 'string') { + return service(s, 'nodejs', options.environment); + } + return service(Object.keys(s)[0], Object.values(s)[0], options.environment); + }); + return (timestamp: number) => { + const tracePaths = options.definePaths(serviceInstances); + return tracePaths.map((traceDef, index) => { + const tracePath = 'path' in traceDef ? traceDef.path : traceDef; + const [first] = tracePath; + + const firstTraceItem = getTraceItem(first); + if (firstTraceItem.serviceInstance === undefined) { + throw new Error('First trace item must be a service instance'); + } + const transactionName = getTransactionName( + firstTraceItem.transactionName, + firstTraceItem.serviceInstance, + index + ); + + const transaction = firstTraceItem.serviceInstance + .transaction({ transactionName, transactionType: 'request' }) + .timestamp(timestamp) + .duration(1000) + .children(...getChildren(tracePath, firstTraceItem.serviceInstance, timestamp, index)); + + if ('transaction' in traceDef && traceDef.transaction) { + return traceDef.transaction(transaction); + } + + return transaction; + }); + }; +} diff --git a/packages/kbn-apm-synthtrace-client/src/types/agent_names.ts b/packages/kbn-apm-synthtrace-client/src/types/agent_names.ts new file mode 100644 index 0000000000000..d9e3a371e0e87 --- /dev/null +++ b/packages/kbn-apm-synthtrace-client/src/types/agent_names.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +type ElasticAgentName = + | 'go' + | 'java' + | 'js-base' + | 'iOS/swift' + | 'rum-js' + | 'nodejs' + | 'python' + | 'dotnet' + | 'ruby' + | 'php' + | 'android/java'; + +type OpenTelemetryAgentName = + | 'otlp' + | 'opentelemetry/cpp' + | 'opentelemetry/dotnet' + | 'opentelemetry/erlang' + | 'opentelemetry/go' + | 'opentelemetry/java' + | 'opentelemetry/nodejs' + | 'opentelemetry/php' + | 'opentelemetry/python' + | 'opentelemetry/ruby' + | 'opentelemetry/swift' + | 'opentelemetry/webjs'; + +// Unable to reference AgentName from '@kbn/apm-plugin/typings/es_schemas/ui/fields/agent' due to circular reference +export type AgentName = ElasticAgentName | OpenTelemetryAgentName; diff --git a/packages/kbn-apm-synthtrace/src/scenarios/service_map.ts b/packages/kbn-apm-synthtrace/src/scenarios/service_map.ts index 41e499320fd50..f39d4bdd1e221 100644 --- a/packages/kbn-apm-synthtrace/src/scenarios/service_map.ts +++ b/packages/kbn-apm-synthtrace/src/scenarios/service_map.ts @@ -6,116 +6,71 @@ * Side Public License, v 1. */ -import { apm, ApmFields, Instance } from '@kbn/apm-synthtrace-client'; -import { Transaction } from '@kbn/apm-synthtrace-client/src/lib/apm/transaction'; -import { AgentName } from '@kbn/apm-plugin/typings/es_schemas/ui/fields/agent'; +import { ApmFields, serviceMap } from '@kbn/apm-synthtrace-client'; import { Scenario } from '../cli/scenario'; import { RunOptions } from '../cli/utils/parse_run_cli_flags'; import { getSynthtraceEnvironment } from '../lib/utils/get_synthtrace_environment'; -const ENVIRONMENT = getSynthtraceEnvironment(__filename); - -function generateTrace( - timestamp: number, - transactionName: string, - order: Instance[], - db?: 'elasticsearch' | 'redis' -) { - return order - .concat() - .reverse() - .reduce((prev, instance, index) => { - const invertedIndex = order.length - index - 1; - - const duration = 50; - const time = timestamp + invertedIndex * 10; - - const transaction: Transaction = instance - .transaction({ transactionName }) - .timestamp(time) - .duration(duration); - - if (prev) { - const next = order[invertedIndex + 1].fields['service.name']!; - transaction.children( - instance - .span({ spanName: `GET ${next}/api`, spanType: 'external', spanSubtype: 'http' }) - .destination(next) - .duration(duration) - .timestamp(time + 1) - .children(prev) - ); - } else if (db) { - transaction.children( - instance - .span({ spanName: db, spanType: 'db', spanSubtype: db }) - .destination(db) - .duration(duration) - .timestamp(time + 1) - ); - } - - return transaction; - }, undefined)!; -} - -function service(serviceName: string, agentName: AgentName) { - return apm - .service({ name: serviceName, environment: ENVIRONMENT, agentName }) - .instance(serviceName); -} +const environment = getSynthtraceEnvironment(__filename); const scenario: Scenario = async (runOptions: RunOptions) => { return { generate: ({ range }) => { - const frontendRum = service('frontend-rum', 'rum-js'); - const frontendNode = service('frontend-node', 'nodejs'); - const advertService = service('advertService', 'java'); - const checkoutService = service('checkoutService', 'go'); - const cartService = service('cartService', 'dotnet'); - const paymentService = service('paymentService', 'nodejs'); - const productCatalogService = service('productCatalogService', 'go'); return range .interval('1s') .rate(3) - .generator((timestamp) => { - return [ - generateTrace( - timestamp, - 'GET /api/adTag', - [frontendRum, frontendNode, advertService], - 'elasticsearch' - ), - generateTrace( - timestamp, - 'POST /api/addToCart', - [frontendRum, frontendNode, cartService], - 'redis' - ), - generateTrace(timestamp, 'POST /api/checkout', [ - frontendRum, - frontendNode, - checkoutService, - paymentService, - ]), - generateTrace( - timestamp, - 'DELETE /api/clearCart', - [checkoutService, cartService], - 'redis' - ), - generateTrace( - timestamp, - 'GET /api/products', - [frontendRum, frontendNode, productCatalogService], - 'elasticsearch' - ), - generateTrace(timestamp, 'PUT /api/updateInventory', [ - checkoutService, - productCatalogService, - ]), - ]; - }); + .generator( + serviceMap({ + services: [ + { 'frontend-rum': 'rum-js' }, + { 'frontend-node': 'nodejs' }, + { advertService: 'java' }, + { checkoutService: 'go' }, + { cartService: 'dotnet' }, + { paymentService: 'nodejs' }, + { productCatalogService: 'go' }, + ], + environment, + definePaths([rum, node, adv, chk, cart, pay, prod]) { + return [ + [ + [rum, 'fetchAd'], + [node, 'GET /nodejs/adTag'], + [adv, 'APIRestController#getAd'], + ['elasticsearch', 'GET ad-*/_search'], + ], + [ + [rum, 'AddToCart'], + [node, 'POST /nodejs/addToCart'], + [cart, 'POST /dotnet/reserveProduct'], + ['redis', 'DECR inventory:i012345:stock'], + ], + [ + [rum, 'Checkout'], + [node, 'POST /nodejs/placeOrder'], + [chk, 'POST /go/placeOrder'], + [pay, 'POST /nodejs/processPayment'], + ], + [ + [chk, 'POST /go/clearCart'], + [cart, 'PUT /dotnet/cart/c12345/reset'], + ['redis', 'INCR inventory:i012345:stock'], + ], + [ + [rum, 'ProductDashboard'], + [node, 'GET /nodejs/products'], + [prod, 'GET /go/product-catalog'], + ['elasticsearch', 'GET product-*/_search'], + ], + [ + [chk, 'PUT /go/update-inventory'], + [prod, 'PUT /go/product/i012345'], + ], + [pay], + ]; + }, + }) + ); }, }; }; diff --git a/packages/kbn-apm-synthtrace/tsconfig.json b/packages/kbn-apm-synthtrace/tsconfig.json index 3db0ec03f6f4d..22ff0442879ab 100644 --- a/packages/kbn-apm-synthtrace/tsconfig.json +++ b/packages/kbn-apm-synthtrace/tsconfig.json @@ -8,7 +8,6 @@ "kbn_references": [ "@kbn/datemath", "@kbn/apm-synthtrace-client", - "@kbn/apm-plugin" ], "exclude": [ "target/**/*", diff --git a/packages/kbn-expandable-flyout/README.md b/packages/kbn-expandable-flyout/README.md new file mode 100644 index 0000000000000..8d21caba73a21 --- /dev/null +++ b/packages/kbn-expandable-flyout/README.md @@ -0,0 +1,66 @@ +# @kbn/expandable-flyout + +## Purpose + +This package offers an expandable flyout UI component and a way to manage the data displayed in it. The component leverages the [EuiFlyout](https://github.com/elastic/eui/tree/main/src/components/flyout) from the EUI library. + +The flyout is composed of 3 sections: +- a right section (primary section) that opens first +- a left wider section to show more details +- a preview section, that overlays the right section. This preview section can display multiple panels one after the other and displays a `Back` button + +At the moment, displaying more than one flyout within the same plugin might be complicated, unless there are in difference areas in the codebase and the contexts don't conflict with each other. + +## What the package offers + +The ExpandableFlyout [React component](https://github.com/elastic/kibana/tree/main/packages/kbn-expandable-flyout/src/components/index) that renders the UI. + +The ExpandableFlyout [React context](https://github.com/elastic/kibana/tree/main/packages/kbn-expandable-flyout/src/components/context) that exposes the following api: +- **openFlyout**: open the flyout with a set of panels +- **openFlyoutRightPanel**: open a right panel +- **openFlyoutLeftPanel**: open a left panel +- **openFlyoutPreviewPanel**: open a preview panel +- **closeFlyoutRightPanel**: close the right panel +- **closeFlyoutLeftPanel**: close the left panel +- **closeFlyoutPreviewPanel**: close the preview panels +- **previousFlyoutPreviewPanel**: navigate to the previous preview panel +- **closeFlyout**: close the flyout + +To retrieve the flyout's layout (left, right and preview panels), you can use the **panels** from the same [React context](https://github.com/elastic/kibana/tree/main/packages/kbn-expandable-flyout/src/components/context); + +- To have more details about how these above api work, see the code documentation [here](https://github.com/elastic/kibana/tree/main/packages/kbn-expandable-flyout/src/utils/helpers). + +## Usage + +To use the expandable flyout in your plugin, first you need wrap your code with the context provider at a high enough level as follows: +```typescript jsx + + + ... + + +``` + +Then use the React UI component where you need: + +```typescript jsx + +``` +where `myPanels` is a list of all the panels that can be rendered in the flyout (see interface [here](https://github.com/elastic/kibana/tree/main/packages/kbn-expandable-flyout/src/components/index)). + + +## Terminology + +### Section + +One of the 3 areas of the flyout (left, right or preview). + +### Panel + +A set of properties defining what's displayed in one of the flyout section. + +## Future work + +- currently the panels are aware of their width. This should be changed and the width of the left, right and preview sections should be handled by the flyout itself +- add the feature to save the flyout state (layout) to the url +- introduce the notion of scope to be able to handle more than one flyout per plugin?? \ No newline at end of file diff --git a/packages/kbn-expandable-flyout/index.ts b/packages/kbn-expandable-flyout/index.ts new file mode 100644 index 0000000000000..e2ce15d85a399 --- /dev/null +++ b/packages/kbn-expandable-flyout/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 { ExpandableFlyout } from './src'; +export { ExpandableFlyoutProvider, useExpandableFlyoutContext } from './src/context'; + +export type { ExpandableFlyoutProps } from './src'; +export type { FlyoutPanel } from './src/types'; diff --git a/packages/kbn-expandable-flyout/jest.config.js b/packages/kbn-expandable-flyout/jest.config.js new file mode 100644 index 0000000000000..f861f9b122fd5 --- /dev/null +++ b/packages/kbn-expandable-flyout/jest.config.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-expandable-flyout'], +}; diff --git a/packages/kbn-expandable-flyout/kibana.jsonc b/packages/kbn-expandable-flyout/kibana.jsonc new file mode 100644 index 0000000000000..b4f63cca6bf91 --- /dev/null +++ b/packages/kbn-expandable-flyout/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/expandable-flyout", + "owner": "@elastic/security-threat-hunting-investigations" +} diff --git a/packages/kbn-expandable-flyout/package.json b/packages/kbn-expandable-flyout/package.json new file mode 100644 index 0000000000000..a2e826c042842 --- /dev/null +++ b/packages/kbn-expandable-flyout/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/expandable-flyout", + "private": true, + "version": "1.0.0", + "license": "SSPL-1.0 OR Elastic License 2.0" +} \ No newline at end of file diff --git a/packages/kbn-expandable-flyout/src/actions.ts b/packages/kbn-expandable-flyout/src/actions.ts new file mode 100644 index 0000000000000..5709214394303 --- /dev/null +++ b/packages/kbn-expandable-flyout/src/actions.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 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 { FlyoutPanel } from './types'; + +export enum ActionType { + openFlyout = 'open_flyout', + openRightPanel = 'open_right_panel', + openLeftPanel = 'open_left_panel', + openPreviewPanel = 'open_preview_panel', + closeRightPanel = 'close_right_panel', + closeLeftPanel = 'close_left_panel', + closePreviewPanel = 'close_preview_panel', + previousPreviewPanel = 'previous_preview_panel', + closeFlyout = 'close_flyout', +} + +export type Action = + | { + type: ActionType.openFlyout; + payload: { + right?: FlyoutPanel; + left?: FlyoutPanel; + preview?: FlyoutPanel; + }; + } + | { + type: ActionType.openRightPanel; + payload: FlyoutPanel; + } + | { + type: ActionType.openLeftPanel; + payload: FlyoutPanel; + } + | { + type: ActionType.openPreviewPanel; + payload: FlyoutPanel; + } + | { + type: ActionType.closeRightPanel; + } + | { + type: ActionType.closeLeftPanel; + } + | { + type: ActionType.closePreviewPanel; + } + | { + type: ActionType.previousPreviewPanel; + } + | { + type: ActionType.closeFlyout; + }; diff --git a/packages/kbn-expandable-flyout/src/components/left_section.tsx b/packages/kbn-expandable-flyout/src/components/left_section.tsx new file mode 100644 index 0000000000000..ddf53efbad2b8 --- /dev/null +++ b/packages/kbn-expandable-flyout/src/components/left_section.tsx @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import React from 'react'; +import { LEFT_SECTION } from './test_ids'; + +interface LeftSectionProps { + /** + * Component to be rendered + */ + component: React.ReactElement; + /** + * Width used when rendering the panel + */ + width: number; +} + +/** + * Left section of the expanded flyout rendering a panel + */ +export const LeftSection: React.FC = ({ component, width }: LeftSectionProps) => { + return ( + + + {component} + + + ); +}; + +LeftSection.displayName = 'LeftSection'; diff --git a/packages/kbn-expandable-flyout/src/components/preview_section.test.tsx b/packages/kbn-expandable-flyout/src/components/preview_section.test.tsx new file mode 100644 index 0000000000000..41926400e11f5 --- /dev/null +++ b/packages/kbn-expandable-flyout/src/components/preview_section.test.tsx @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { render } from '@testing-library/react'; +import { PreviewSection } from './preview_section'; +import { PREVIEW_SECTION_BACK_BUTTON, PREVIEW_SECTION_CLOSE_BUTTON } from './test_ids'; +import { ExpandableFlyoutContext } from '../context'; + +describe('PreviewSection', () => { + const context: ExpandableFlyoutContext = { + panels: { + right: {}, + left: {}, + preview: [ + { + id: 'key', + }, + ], + }, + } as unknown as ExpandableFlyoutContext; + + it('should render close button in header', () => { + const component =

      {'component'}
      ; + const width = 500; + const showBackButton = false; + + const { getByTestId } = render( + + + + ); + + expect(getByTestId(PREVIEW_SECTION_CLOSE_BUTTON)).toBeInTheDocument(); + }); + + it('should render back button in header', () => { + const component =
      {'component'}
      ; + const width = 500; + const showBackButton = true; + + const { getByTestId } = render( + + + + ); + + expect(getByTestId(PREVIEW_SECTION_BACK_BUTTON)).toBeInTheDocument(); + }); +}); diff --git a/packages/kbn-expandable-flyout/src/components/preview_section.tsx b/packages/kbn-expandable-flyout/src/components/preview_section.tsx new file mode 100644 index 0000000000000..e474d1204bf03 --- /dev/null +++ b/packages/kbn-expandable-flyout/src/components/preview_section.tsx @@ -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 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 { + EuiButtonEmpty, + EuiButtonIcon, + EuiFlexGroup, + EuiFlexItem, + EuiPanel, + useEuiTheme, +} from '@elastic/eui'; +import React from 'react'; +import { css } from '@emotion/react'; +import { + PREVIEW_SECTION, + PREVIEW_SECTION_BACK_BUTTON, + PREVIEW_SECTION_CLOSE_BUTTON, +} from './test_ids'; +import { useExpandableFlyoutContext } from '../..'; +import { BACK_BUTTON, CLOSE_BUTTON } from './translations'; + +interface PreviewSectionProps { + /** + * Component to be rendered + */ + component: React.ReactElement; + /** + * Width used when rendering the panel + */ + width: number | undefined; + /** + * Display the back button in the header + */ + showBackButton: boolean; +} + +/** + * Preview section of the expanded flyout rendering one or multiple panels. + * Will display a back and close button in the header for the previous and close feature respectively. + */ +export const PreviewSection: React.FC = ({ + component, + showBackButton, + width, +}: PreviewSectionProps) => { + const { euiTheme } = useEuiTheme(); + const { closePreviewPanel, previousPreviewPanel } = useExpandableFlyoutContext(); + + const previewWith: string = width ? `${width}px` : '0px'; + + const closeButton = ( + + closePreviewPanel()} + data-test-subj={PREVIEW_SECTION_CLOSE_BUTTON} + aria-label={CLOSE_BUTTON} + /> + + ); + const header = showBackButton ? ( + + + previousPreviewPanel()} + data-test-subj={PREVIEW_SECTION_BACK_BUTTON} + aria-label={BACK_BUTTON} + > + {BACK_BUTTON} + + + {closeButton} + + ) : ( + {closeButton} + ); + + return ( + <> +
      +
      + + {header} + {component} + +
      + + ); +}; + +PreviewSection.displayName = 'PreviewSection'; diff --git a/packages/kbn-expandable-flyout/src/components/right_section.tsx b/packages/kbn-expandable-flyout/src/components/right_section.tsx new file mode 100644 index 0000000000000..d018dd8721ee7 --- /dev/null +++ b/packages/kbn-expandable-flyout/src/components/right_section.tsx @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import React from 'react'; +import { RIGHT_SECTION } from './test_ids'; + +interface RightSectionProps { + /** + * Component to be rendered + */ + component: React.ReactElement; + /** + * Width used when rendering the panel + */ + width: number; +} + +/** + * Right section of the expanded flyout rendering a panel + */ +export const RightSection: React.FC = ({ + component, + width, +}: RightSectionProps) => { + return ( + + + {component} + + + ); +}; + +RightSection.displayName = 'RightSection'; diff --git a/packages/kbn-expandable-flyout/src/components/test_ids.ts b/packages/kbn-expandable-flyout/src/components/test_ids.ts new file mode 100644 index 0000000000000..38dd9231712c4 --- /dev/null +++ b/packages/kbn-expandable-flyout/src/components/test_ids.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export const RIGHT_SECTION = 'rightSection'; + +export const LEFT_SECTION = 'leftSection'; + +export const PREVIEW_SECTION = 'previewSection'; + +export const PREVIEW_SECTION_CLOSE_BUTTON = 'previewSectionCloseButton'; + +export const PREVIEW_SECTION_BACK_BUTTON = 'previewSectionBackButton'; diff --git a/packages/kbn-expandable-flyout/src/components/translations.ts b/packages/kbn-expandable-flyout/src/components/translations.ts new file mode 100644 index 0000000000000..8b6646fa69df3 --- /dev/null +++ b/packages/kbn-expandable-flyout/src/components/translations.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { i18n } from '@kbn/i18n'; + +export const BACK_BUTTON = i18n.translate('expandableFlyout.previewSection.backButton', { + defaultMessage: 'Back', +}); + +export const CLOSE_BUTTON = i18n.translate('expandableFlyout.previewSection.closeButton', { + defaultMessage: 'Close', +}); diff --git a/packages/kbn-expandable-flyout/src/context.tsx b/packages/kbn-expandable-flyout/src/context.tsx new file mode 100644 index 0000000000000..89e8210e9578f --- /dev/null +++ b/packages/kbn-expandable-flyout/src/context.tsx @@ -0,0 +1,172 @@ +/* + * Copyright 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, { createContext, useCallback, useContext, useMemo, useReducer } from 'react'; +import { ActionType } from './actions'; +import { reducer, State } from './reducer'; +import type { FlyoutPanel } from './types'; +import { initialState } from './reducer'; + +export interface ExpandableFlyoutContext { + /** + * Right, left and preview panels + */ + panels: State; + /** + * Open the flyout with left, right and/or preview panels + */ + openFlyout: (panels: { left?: FlyoutPanel; right?: FlyoutPanel; preview?: FlyoutPanel }) => void; + /** + * Replaces the current right panel with a new one + */ + openRightPanel: (panel: FlyoutPanel) => void; + /** + * Replaces the current left panel with a new one + */ + openLeftPanel: (panel: FlyoutPanel) => void; + /** + * Add a new preview panel to the list of current preview panels + */ + openPreviewPanel: (panel: FlyoutPanel) => void; + /** + * Closes right panel + */ + closeRightPanel: () => void; + /** + * Closes left panel + */ + closeLeftPanel: () => void; + /** + * Closes all preview panels + */ + closePreviewPanel: () => void; + /** + * Go back to previous preview panel + */ + previousPreviewPanel: () => void; + /** + * Close all panels and closes flyout + */ + closeFlyout: () => void; +} + +export const ExpandableFlyoutContext = createContext( + undefined +); + +export interface ExpandableFlyoutProviderProps { + /** + * React children + */ + children: React.ReactNode; +} + +/** + * Wrap your plugin with this context for the ExpandableFlyout React component. + */ +export const ExpandableFlyoutProvider = ({ children }: ExpandableFlyoutProviderProps) => { + const [state, dispatch] = useReducer(reducer, initialState); + + const openPanels = useCallback( + ({ + right, + left, + preview, + }: { + right?: FlyoutPanel; + left?: FlyoutPanel; + preview?: FlyoutPanel; + }) => dispatch({ type: ActionType.openFlyout, payload: { left, right, preview } }), + [dispatch] + ); + + const openRightPanel = useCallback( + (panel: FlyoutPanel) => dispatch({ type: ActionType.openRightPanel, payload: panel }), + [dispatch] + ); + + const openLeftPanel = useCallback( + (panel: FlyoutPanel) => dispatch({ type: ActionType.openLeftPanel, payload: panel }), + [dispatch] + ); + + const openPreviewPanel = useCallback( + (panel: FlyoutPanel) => dispatch({ type: ActionType.openPreviewPanel, payload: panel }), + [dispatch] + ); + + const closeRightPanel = useCallback( + () => dispatch({ type: ActionType.closeRightPanel }), + [dispatch] + ); + + const closeLeftPanel = useCallback( + () => dispatch({ type: ActionType.closeLeftPanel }), + [dispatch] + ); + + const closePreviewPanel = useCallback( + () => dispatch({ type: ActionType.closePreviewPanel }), + [dispatch] + ); + + const previousPreviewPanel = useCallback( + () => dispatch({ type: ActionType.previousPreviewPanel }), + [dispatch] + ); + + const closePanels = useCallback(() => dispatch({ type: ActionType.closeFlyout }), [dispatch]); + + const contextValue = useMemo( + () => ({ + panels: state, + openFlyout: openPanels, + openRightPanel, + openLeftPanel, + openPreviewPanel, + closeRightPanel, + closeLeftPanel, + closePreviewPanel, + closeFlyout: closePanels, + previousPreviewPanel, + }), + [ + state, + openPanels, + openRightPanel, + openLeftPanel, + openPreviewPanel, + closeRightPanel, + closeLeftPanel, + closePreviewPanel, + closePanels, + previousPreviewPanel, + ] + ); + + return ( + + {children} + + ); +}; + +/** + * Retrieve context's properties + */ +export const useExpandableFlyoutContext = (): ExpandableFlyoutContext => { + const contextValue = useContext(ExpandableFlyoutContext); + + if (!contextValue) { + throw new Error( + 'ExpandableFlyoutContext can only be used within ExpandableFlyoutContext provider' + ); + } + + return contextValue; +}; diff --git a/packages/kbn-expandable-flyout/src/index.test.tsx b/packages/kbn-expandable-flyout/src/index.test.tsx new file mode 100644 index 0000000000000..fd1d1990ffbad --- /dev/null +++ b/packages/kbn-expandable-flyout/src/index.test.tsx @@ -0,0 +1,105 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { render } from '@testing-library/react'; +import { Panel } from './types'; +import { ExpandableFlyout } from '.'; +import { LEFT_SECTION, PREVIEW_SECTION, RIGHT_SECTION } from './components/test_ids'; +import { ExpandableFlyoutContext } from './context'; + +describe('ExpandableFlyout', () => { + const registeredPanels: Panel[] = [ + { + key: 'key', + width: 500, + component: () =>
      {'component'}
      , + }, + ]; + const onClose = () => window.alert('closed'); + + it(`shouldn't render flyout if no panels`, () => { + const context: ExpandableFlyoutContext = { + panels: { + right: undefined, + left: undefined, + preview: [], + }, + } as unknown as ExpandableFlyoutContext; + + const result = render( + + + + ); + + expect(result.asFragment()).toMatchInlineSnapshot(``); + }); + + it('should render right section', () => { + const context: ExpandableFlyoutContext = { + panels: { + right: { + id: 'key', + }, + left: {}, + preview: [], + }, + } as unknown as ExpandableFlyoutContext; + + const { getByTestId } = render( + + + + ); + + expect(getByTestId(RIGHT_SECTION)).toBeInTheDocument(); + }); + + it('should render left section', () => { + const context: ExpandableFlyoutContext = { + panels: { + right: {}, + left: { + id: 'key', + }, + preview: [], + }, + } as unknown as ExpandableFlyoutContext; + + const { getByTestId } = render( + + + + ); + + expect(getByTestId(LEFT_SECTION)).toBeInTheDocument(); + }); + + it('should render preview section', () => { + const context: ExpandableFlyoutContext = { + panels: { + right: {}, + left: {}, + preview: [ + { + id: 'key', + }, + ], + }, + } as unknown as ExpandableFlyoutContext; + + const { getByTestId } = render( + + + + ); + + expect(getByTestId(PREVIEW_SECTION)).toBeInTheDocument(); + }); +}); diff --git a/packages/kbn-expandable-flyout/src/index.tsx b/packages/kbn-expandable-flyout/src/index.tsx new file mode 100644 index 0000000000000..80dd1d425f2a2 --- /dev/null +++ b/packages/kbn-expandable-flyout/src/index.tsx @@ -0,0 +1,113 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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, { useCallback, useMemo } from 'react'; +import { css } from '@emotion/react'; +import type { EuiFlyoutProps } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlyout } from '@elastic/eui'; +import { useExpandableFlyoutContext } from './context'; +import { PreviewSection } from './components/preview_section'; +import { RightSection } from './components/right_section'; +import type { FlyoutPanel, Panel } from './types'; +import { LeftSection } from './components/left_section'; + +export interface ExpandableFlyoutProps extends EuiFlyoutProps { + /** + * List of all registered panels available for render + */ + registeredPanels: Panel[]; + /** + * Propagate out EuiFlyout onClose event + */ + handleOnFlyoutClosed?: () => void; +} + +/** + * Expandable flyout UI React component. + * Displays 3 sections (right, left, preview) depending on the panels in the context. + */ +export const ExpandableFlyout: React.FC = ({ + registeredPanels, + handleOnFlyoutClosed, + ...flyoutProps +}) => { + const { panels, closeFlyout } = useExpandableFlyoutContext(); + const { left, right, preview } = panels; + + const onClose = useCallback(() => { + if (handleOnFlyoutClosed) handleOnFlyoutClosed(); + closeFlyout(); + }, [closeFlyout, handleOnFlyoutClosed]); + + const leftSection = useMemo( + () => registeredPanels.find((panel) => panel.key === left?.id), + [left, registeredPanels] + ); + + const rightSection = useMemo( + () => registeredPanels.find((panel) => panel.key === right?.id), + [right, registeredPanels] + ); + + // retrieve the last preview panel (most recent) + const mostRecentPreview = preview ? preview[preview.length - 1] : undefined; + const showBackButton = preview && preview.length > 1; + const previewSection = useMemo( + () => registeredPanels.find((panel) => panel.key === mostRecentPreview?.id), + [mostRecentPreview, registeredPanels] + ); + + // do not add the flyout to the dom if there aren't any panels to display + if (!left && !right && !preview.length) { + return <>; + } + + const width: number = (leftSection?.width ?? 0) + (rightSection?.width ?? 0); + + return ( + + + {leftSection && left ? ( + + ) : null} + {rightSection && right ? ( + + ) : null} + + + {previewSection && preview ? ( + + ) : null} + + ); +}; + +ExpandableFlyout.displayName = 'ExpandableFlyout'; diff --git a/packages/kbn-expandable-flyout/src/reducer.test.ts b/packages/kbn-expandable-flyout/src/reducer.test.ts new file mode 100644 index 0000000000000..21128ded7b58e --- /dev/null +++ b/packages/kbn-expandable-flyout/src/reducer.test.ts @@ -0,0 +1,417 @@ +/* + * Copyright 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 { FlyoutPanel } from './types'; +import { initialState, reducer, State } from './reducer'; +import { Action, ActionType } from './actions'; + +const rightPanel1: FlyoutPanel = { + id: 'right1', + path: ['path'], +}; +const leftPanel1: FlyoutPanel = { + id: 'left1', + params: { id: 'id' }, +}; +const previewPanel1: FlyoutPanel = { + id: 'preview1', + state: { id: 'state' }, +}; + +const rightPanel2: FlyoutPanel = { + id: 'right2', + path: ['path'], +}; +const leftPanel2: FlyoutPanel = { + id: 'left2', + params: { id: 'id' }, +}; +const previewPanel2: FlyoutPanel = { + id: 'preview2', + state: { id: 'state' }, +}; +describe('reducer', () => { + describe('should handle openFlyout action', () => { + it('should add panels to empty state', () => { + const state: State = initialState; + const action: Action = { + type: ActionType.openFlyout, + payload: { + right: rightPanel1, + left: leftPanel1, + preview: previewPanel1, + }, + }; + const newState: State = reducer(state, action); + + expect(newState).toEqual({ + left: leftPanel1, + right: rightPanel1, + preview: [previewPanel1], + }); + }); + + it('should override all panels in the state', () => { + const state: State = { + left: leftPanel1, + right: rightPanel1, + preview: [previewPanel1, { id: 'preview' }], + }; + const action: Action = { + type: ActionType.openFlyout, + payload: { + right: rightPanel2, + left: leftPanel2, + preview: previewPanel2, + }, + }; + const newState: State = reducer(state, action); + + expect(newState).toEqual({ + left: leftPanel2, + right: rightPanel2, + preview: [previewPanel2], + }); + }); + + it('should remove all panels despite only passing a single section ', () => { + const state: State = { + left: leftPanel1, + right: rightPanel1, + preview: [previewPanel1], + }; + const action: Action = { + type: ActionType.openFlyout, + payload: { + right: rightPanel2, + }, + }; + const newState: State = reducer(state, action); + + expect(newState).toEqual({ + left: undefined, + right: rightPanel2, + preview: [], + }); + }); + }); + + describe('should handle openRightPanel action', () => { + it('should add right panel to empty state', () => { + const state: State = initialState; + const action: Action = { + type: ActionType.openRightPanel, + payload: rightPanel1, + }; + const newState: State = reducer(state, action); + + expect(newState).toEqual({ + left: undefined, + right: rightPanel1, + preview: [], + }); + }); + + it('should replace right panel', () => { + const state: State = { + left: leftPanel1, + right: rightPanel1, + preview: [previewPanel1], + }; + const action: Action = { + type: ActionType.openRightPanel, + payload: rightPanel2, + }; + const newState: State = reducer(state, action); + + expect(newState).toEqual({ + left: leftPanel1, + right: rightPanel2, + preview: [previewPanel1], + }); + }); + }); + + describe('should handle openLeftPanel action', () => { + it('should add left panel to empty state', () => { + const state: State = initialState; + const action: Action = { + type: ActionType.openLeftPanel, + payload: leftPanel1, + }; + const newState: State = reducer(state, action); + + expect(newState).toEqual({ + left: leftPanel1, + right: undefined, + preview: [], + }); + }); + + it('should replace only left panel', () => { + const state: State = { + left: leftPanel1, + right: rightPanel1, + preview: [previewPanel1], + }; + const action: Action = { + type: ActionType.openLeftPanel, + payload: leftPanel2, + }; + const newState: State = reducer(state, action); + + expect(newState).toEqual({ + left: leftPanel2, + right: rightPanel1, + preview: [previewPanel1], + }); + }); + }); + + describe('should handle openPreviewPanel action', () => { + it('should add preview panel to empty state', () => { + const state: State = initialState; + const action: Action = { + type: ActionType.openPreviewPanel, + payload: previewPanel1, + }; + const newState: State = reducer(state, action); + + expect(newState).toEqual({ + left: undefined, + right: undefined, + preview: [previewPanel1], + }); + }); + + it('should add preview panel to the list of preview panels', () => { + const state: State = { + left: leftPanel1, + right: rightPanel1, + preview: [previewPanel1], + }; + const action: Action = { + type: ActionType.openPreviewPanel, + payload: previewPanel2, + }; + const newState: State = reducer(state, action); + + expect(newState).toEqual({ + left: leftPanel1, + right: rightPanel1, + preview: [previewPanel1, previewPanel2], + }); + }); + }); + + describe('should handle closeRightPanel action', () => { + it('should return empty state when removing right panel from empty state', () => { + const state: State = initialState; + const action: Action = { + type: ActionType.closeRightPanel, + }; + const newState: State = reducer(state, action); + + expect(newState).toEqual(state); + }); + + it(`should return unmodified state when removing right panel when no right panel exist`, () => { + const state: State = { + left: leftPanel1, + right: undefined, + preview: [previewPanel1], + }; + const action: Action = { + type: ActionType.closeRightPanel, + }; + const newState: State = reducer(state, action); + + expect(newState).toEqual(state); + }); + + it('should remove right panel', () => { + const state: State = { + left: leftPanel1, + right: rightPanel1, + preview: [previewPanel1], + }; + const action: Action = { + type: ActionType.closeRightPanel, + }; + const newState: State = reducer(state, action); + + expect(newState).toEqual({ + left: leftPanel1, + right: undefined, + preview: [previewPanel1], + }); + }); + }); + + describe('should handle closeLeftPanel action', () => { + it('should return empty state when removing left panel on empty state', () => { + const state: State = initialState; + const action: Action = { + type: ActionType.closeLeftPanel, + }; + const newState: State = reducer(state, action); + + expect(newState).toEqual(state); + }); + + it(`should return unmodified state when removing left panel when no left panel exist`, () => { + const state: State = { + left: undefined, + right: rightPanel1, + preview: [], + }; + const action: Action = { + type: ActionType.closeLeftPanel, + }; + const newState: State = reducer(state, action); + + expect(newState).toEqual(state); + }); + + it('should remove left panel', () => { + const state: State = { + left: leftPanel1, + right: rightPanel1, + preview: [previewPanel1], + }; + const action: Action = { + type: ActionType.closeLeftPanel, + }; + const newState: State = reducer(state, action); + + expect(newState).toEqual({ + left: undefined, + right: rightPanel1, + preview: [previewPanel1], + }); + }); + }); + + describe('should handle closePreviewPanel action', () => { + it('should return empty state when removing preview panel on empty state', () => { + const state: State = initialState; + const action: Action = { + type: ActionType.closePreviewPanel, + }; + const newState: State = reducer(state, action); + + expect(newState).toEqual(state); + }); + + it(`should return unmodified state when removing preview panel when no preview panel exist`, () => { + const state: State = { + left: leftPanel1, + right: rightPanel1, + preview: [], + }; + const action: Action = { + type: ActionType.closePreviewPanel, + }; + const newState: State = reducer(state, action); + + expect(newState).toEqual(state); + }); + + it('should remove all preview panels', () => { + const state: State = { + left: rightPanel1, + right: leftPanel1, + preview: [previewPanel1, previewPanel2], + }; + const action: Action = { + type: ActionType.closePreviewPanel, + }; + const newState: State = reducer(state, action); + + expect(newState).toEqual({ + left: rightPanel1, + right: leftPanel1, + preview: [], + }); + }); + }); + + describe('should handle previousPreviewPanel action', () => { + it('should return empty state when previous preview panel on an empty state', () => { + const state: State = initialState; + const action: Action = { + type: ActionType.previousPreviewPanel, + }; + const newState: State = reducer(state, action); + + expect(newState).toEqual(state); + }); + + it(`should return unmodified state when previous preview panel when no preview panel exist`, () => { + const state: State = { + left: leftPanel1, + right: rightPanel1, + preview: [], + }; + const action: Action = { + type: ActionType.previousPreviewPanel, + }; + const newState: State = reducer(state, action); + + expect(newState).toEqual(state); + }); + + it('should remove only last preview panel', () => { + const state: State = { + left: leftPanel1, + right: rightPanel1, + preview: [previewPanel1, previewPanel2], + }; + const action: Action = { + type: ActionType.previousPreviewPanel, + }; + const newState: State = reducer(state, action); + + expect(newState).toEqual({ + left: leftPanel1, + right: rightPanel1, + preview: [previewPanel1], + }); + }); + }); + + describe('should handle closeFlyout action', () => { + it('should return empty state when closing flyout on an empty state', () => { + const state: State = initialState; + const action: Action = { + type: ActionType.closeFlyout, + }; + const newState: State = reducer(state, action); + + expect(newState).toEqual(initialState); + }); + + it('should remove all panels', () => { + const state: State = { + left: leftPanel1, + right: rightPanel1, + preview: [previewPanel1], + }; + const action: Action = { + type: ActionType.closeFlyout, + }; + const newState: State = reducer(state, action); + + expect(newState).toEqual({ + left: undefined, + right: undefined, + preview: [], + }); + }); + }); +}); diff --git a/packages/kbn-expandable-flyout/src/reducer.ts b/packages/kbn-expandable-flyout/src/reducer.ts new file mode 100644 index 0000000000000..4901eccfc6bb4 --- /dev/null +++ b/packages/kbn-expandable-flyout/src/reducer.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 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 { FlyoutPanel } from './types'; +import { Action, ActionType } from './actions'; + +export interface State { + /** + * Panel to render in the left section + */ + left: FlyoutPanel | undefined; + /** + * Panel to render in the right section + */ + right: FlyoutPanel | undefined; + /** + * Panels to render in the preview section + */ + preview: FlyoutPanel[]; +} + +export const initialState: State = { + left: undefined, + right: undefined, + preview: [], +}; + +export function reducer(state: State, action: Action) { + switch (action.type) { + /** + * Open the flyout by replacing the entire state with new panels. + */ + case ActionType.openFlyout: { + const { left, right, preview } = action.payload; + return { + left, + right, + preview: preview ? [preview] : [], + }; + } + + /** + * Opens a right section by replacing the previous right panel with the new one. + */ + case ActionType.openRightPanel: { + return { ...state, right: action.payload }; + } + + /** + * Opens a left section by replacing the previous left panel with the new one. + */ + case ActionType.openLeftPanel: { + return { ...state, left: action.payload }; + } + + /** + * Opens a preview section by adding to the array of preview panels. + */ + case ActionType.openPreviewPanel: { + return { ...state, preview: [...state.preview, action.payload] }; + } + + /** + * Closes the right section by removing the right panel. + */ + case ActionType.closeRightPanel: { + return { ...state, right: undefined }; + } + + /** + * Close the left section by removing the left panel. + */ + case ActionType.closeLeftPanel: { + return { ...state, left: undefined }; + } + + /** + * Closes the preview section by removing all the preview panels. + */ + case ActionType.closePreviewPanel: { + return { ...state, preview: [] }; + } + + /** + * Navigates to the previous preview panel by removing the last entry in the array of preview panels. + */ + case ActionType.previousPreviewPanel: { + const p: FlyoutPanel[] = [...state.preview]; + p.pop(); + return { ...state, preview: p }; + } + + /** + * Close the flyout by removing all the panels. + */ + case ActionType.closeFlyout: { + return { + left: undefined, + right: undefined, + preview: [], + }; + } + } +} diff --git a/packages/kbn-expandable-flyout/src/types.ts b/packages/kbn-expandable-flyout/src/types.ts new file mode 100644 index 0000000000000..f526832810900 --- /dev/null +++ b/packages/kbn-expandable-flyout/src/types.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 React from 'react'; + +export interface FlyoutPanel { + /** + * Unique key to identify the panel + */ + id: string; + /** + * Any parameters necessary for the initial requests within the flyout + */ + params?: Record; + /** + * Tracks the path for what to show in a panel. We may have multiple tabs or details..., so easiest to just use a stack + */ + path?: string[]; + /** + * Tracks visual state such as whether the panel is collapsed + */ + state?: Record; +} + +export interface Panel { + /** + * Unique key used to identify the panel + */ + key?: string; + /** + * Component to be rendered + */ + component: (props: FlyoutPanel) => React.ReactElement; + /** + * Width used when rendering the panel + */ + width: number; // TODO remove this, the width shouldn't be a property of a panel, but handled at the flyout level +} diff --git a/packages/kbn-expandable-flyout/tsconfig.json b/packages/kbn-expandable-flyout/tsconfig.json new file mode 100644 index 0000000000000..d1755389bcddc --- /dev/null +++ b/packages/kbn-expandable-flyout/tsconfig.json @@ -0,0 +1,24 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node", + "react", + "@emotion/react/types/css-prop", + "@testing-library/jest-dom", + "@testing-library/react" + ] + }, + "include": [ + "**/*.ts", + "**/*.tsx" + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [ + "@kbn/i18n" + ] +} diff --git a/packages/kbn-rule-data-utils/index.ts b/packages/kbn-rule-data-utils/index.ts index 145cc2c5220e9..ea0028b972ed9 100644 --- a/packages/kbn-rule-data-utils/index.ts +++ b/packages/kbn-rule-data-utils/index.ts @@ -7,6 +7,7 @@ */ export * from './src/default_alerts_as_data'; +export * from './src/legacy_alerts_as_data'; export * from './src/technical_field_names'; export * from './src/alerts_as_data_rbac'; export * from './src/alerts_as_data_severity'; diff --git a/packages/kbn-rule-data-utils/src/default_alerts_as_data.ts b/packages/kbn-rule-data-utils/src/default_alerts_as_data.ts index 3a982124b58e6..34b04116b9522 100644 --- a/packages/kbn-rule-data-utils/src/default_alerts_as_data.ts +++ b/packages/kbn-rule-data-utils/src/default_alerts_as_data.ts @@ -8,6 +8,9 @@ import { ValuesType } from 'utility-types'; +const TIMESTAMP = '@timestamp' as const; + +// namespaces const KIBANA_NAMESPACE = 'kibana' as const; const ALERT_NAMESPACE = `${KIBANA_NAMESPACE}.alert` as const; const ALERT_RULE_NAMESPACE = `${ALERT_NAMESPACE}.rule` as const; @@ -21,6 +24,9 @@ const VERSION = `${KIBANA_NAMESPACE}.version` as const; // kibana.alert.action_group - framework action group ID for this alert const ALERT_ACTION_GROUP = `${ALERT_NAMESPACE}.action_group` as const; +// kibana.alert.case_ids - array of cases associated with the alert +const ALERT_CASE_IDS = `${ALERT_NAMESPACE}.case_ids` as const; + // kibana.alert.duration.us - alert duration in nanoseconds - updated each execution // that the alert is active const ALERT_DURATION = `${ALERT_NAMESPACE}.duration.us` as const; @@ -31,8 +37,11 @@ const ALERT_END = `${ALERT_NAMESPACE}.end` as const; // kibana.alert.flapping - whether the alert is currently in a flapping state const ALERT_FLAPPING = `${ALERT_NAMESPACE}.flapping` as const; -// kibana.alert.id - alert ID, also known as alert instance ID -const ALERT_ID = `${ALERT_NAMESPACE}.id` as const; +// kibana.alert.flapping_history - whether the alert is currently in a flapping state +const ALERT_FLAPPING_HISTORY = `${ALERT_NAMESPACE}.flapping_history` as const; + +// kibana.alert.instance.id - alert ID, also known as alert instance ID +const ALERT_INSTANCE_ID = `${ALERT_NAMESPACE}.instance.id` as const; // kibana.alert.last_detected - timestamp when the alert was last seen const ALERT_LAST_DETECTED = `${ALERT_NAMESPACE}.last_detected` as const; @@ -90,10 +99,12 @@ const namespaces = { const fields = { ALERT_ACTION_GROUP, + ALERT_CASE_IDS, ALERT_DURATION, ALERT_END, ALERT_FLAPPING, - ALERT_ID, + ALERT_FLAPPING_HISTORY, + ALERT_INSTANCE_ID, ALERT_LAST_DETECTED, ALERT_REASON, ALERT_RULE_CATEGORY, @@ -111,15 +122,24 @@ const fields = { ALERT_UUID, ALERT_WORKFLOW_STATUS, SPACE_IDS, + TIMESTAMP, VERSION, }; export { + // namespaces + ALERT_NAMESPACE, + ALERT_RULE_NAMESPACE, + KIBANA_NAMESPACE, + + // fields ALERT_ACTION_GROUP, + ALERT_CASE_IDS, ALERT_DURATION, ALERT_END, ALERT_FLAPPING, - ALERT_ID, + ALERT_FLAPPING_HISTORY, + ALERT_INSTANCE_ID, ALERT_LAST_DETECTED, ALERT_REASON, ALERT_RULE_CATEGORY, @@ -137,10 +157,8 @@ export { ALERT_UUID, ALERT_WORKFLOW_STATUS, SPACE_IDS, + TIMESTAMP, VERSION, - ALERT_NAMESPACE, - ALERT_RULE_NAMESPACE, - KIBANA_NAMESPACE, }; export type DefaultAlertFieldName = ValuesType; diff --git a/packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts b/packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts new file mode 100644 index 0000000000000..4dd6c2be0c2a6 --- /dev/null +++ b/packages/kbn-rule-data-utils/src/legacy_alerts_as_data.ts @@ -0,0 +1,84 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { ALERT_NAMESPACE, ALERT_RULE_NAMESPACE } from './default_alerts_as_data'; + +const ECS_VERSION = 'ecs.version' as const; +const EVENT_ACTION = 'event.action' as const; +const EVENT_KIND = 'event.kind' as const; +const TAGS = 'tags' as const; + +// These are the fields that are in the rule registry technical component template +// that are NOT in the framework alerts as data common component template + +// We will maintain a legacy component template that can be used by legacy +// rule registry rules with these fields. +const ALERT_RISK_SCORE = `${ALERT_NAMESPACE}.risk_score` as const; +const ALERT_RULE_AUTHOR = `${ALERT_RULE_NAMESPACE}.author` as const; +const ALERT_RULE_CREATED_AT = `${ALERT_RULE_NAMESPACE}.created_at` as const; +const ALERT_RULE_CREATED_BY = `${ALERT_RULE_NAMESPACE}.created_by` as const; +const ALERT_RULE_DESCRIPTION = `${ALERT_RULE_NAMESPACE}.description` as const; +const ALERT_RULE_ENABLED = `${ALERT_RULE_NAMESPACE}.enabled` as const; +const ALERT_RULE_FROM = `${ALERT_RULE_NAMESPACE}.from` as const; +const ALERT_RULE_INTERVAL = `${ALERT_RULE_NAMESPACE}.interval` as const; +const ALERT_RULE_LICENSE = `${ALERT_RULE_NAMESPACE}.license` as const; +const ALERT_RULE_NOTE = `${ALERT_RULE_NAMESPACE}.note` as const; +const ALERT_RULE_REFERENCES = `${ALERT_RULE_NAMESPACE}.references` as const; +const ALERT_RULE_RULE_ID = `${ALERT_RULE_NAMESPACE}.rule_id` as const; +const ALERT_RULE_RULE_NAME_OVERRIDE = `${ALERT_RULE_NAMESPACE}.rule_name_override` as const; +const ALERT_RULE_TO = `${ALERT_RULE_NAMESPACE}.to` as const; +const ALERT_RULE_TYPE = `${ALERT_RULE_NAMESPACE}.type` as const; +const ALERT_RULE_UPDATED_AT = `${ALERT_RULE_NAMESPACE}.updated_at` as const; +const ALERT_RULE_UPDATED_BY = `${ALERT_RULE_NAMESPACE}.updated_by` as const; +const ALERT_RULE_VERSION = `${ALERT_RULE_NAMESPACE}.version` as const; +const ALERT_SEVERITY = `${ALERT_NAMESPACE}.severity` as const; +const ALERT_SUPPRESSION_META = `${ALERT_NAMESPACE}.suppression` as const; +const ALERT_SUPPRESSION_TERMS = `${ALERT_SUPPRESSION_META}.terms` as const; +const ALERT_SUPPRESSION_FIELD = `${ALERT_SUPPRESSION_TERMS}.field` as const; +const ALERT_SUPPRESSION_VALUE = `${ALERT_SUPPRESSION_TERMS}.value` as const; +const ALERT_SUPPRESSION_START = `${ALERT_SUPPRESSION_META}.start` as const; +const ALERT_SUPPRESSION_END = `${ALERT_SUPPRESSION_META}.end` as const; +const ALERT_SUPPRESSION_DOCS_COUNT = `${ALERT_SUPPRESSION_META}.docs_count` as const; +const ALERT_SYSTEM_STATUS = `${ALERT_NAMESPACE}.system_status` as const; +const ALERT_WORKFLOW_REASON = `${ALERT_NAMESPACE}.workflow_reason` as const; +const ALERT_WORKFLOW_USER = `${ALERT_NAMESPACE}.workflow_user` as const; + +export { + ALERT_RISK_SCORE, + ALERT_RULE_AUTHOR, + ALERT_RULE_CREATED_AT, + ALERT_RULE_CREATED_BY, + ALERT_RULE_DESCRIPTION, + ALERT_RULE_ENABLED, + ALERT_RULE_FROM, + ALERT_RULE_INTERVAL, + ALERT_RULE_LICENSE, + ALERT_RULE_NOTE, + ALERT_RULE_REFERENCES, + ALERT_RULE_RULE_ID, + ALERT_RULE_RULE_NAME_OVERRIDE, + ALERT_RULE_TO, + ALERT_RULE_TYPE, + ALERT_RULE_UPDATED_AT, + ALERT_RULE_UPDATED_BY, + ALERT_RULE_VERSION, + ALERT_SEVERITY, + ALERT_SUPPRESSION_DOCS_COUNT, + ALERT_SUPPRESSION_END, + ALERT_SUPPRESSION_FIELD, + ALERT_SUPPRESSION_START, + ALERT_SUPPRESSION_TERMS, + ALERT_SUPPRESSION_VALUE, + ALERT_SYSTEM_STATUS, + ALERT_WORKFLOW_REASON, + ALERT_WORKFLOW_USER, + ECS_VERSION, + EVENT_ACTION, + EVENT_KIND, + TAGS, +}; diff --git a/packages/kbn-rule-data-utils/src/technical_field_names.ts b/packages/kbn-rule-data-utils/src/technical_field_names.ts index 89eca0f923046..cf45162b20853 100644 --- a/packages/kbn-rule-data-utils/src/technical_field_names.ts +++ b/packages/kbn-rule-data-utils/src/technical_field_names.ts @@ -8,11 +8,15 @@ import { ValuesType } from 'utility-types'; import { + ALERT_NAMESPACE, + ALERT_RULE_NAMESPACE, KIBANA_NAMESPACE, ALERT_ACTION_GROUP, + ALERT_CASE_IDS, ALERT_DURATION, ALERT_END, ALERT_FLAPPING, + ALERT_INSTANCE_ID, ALERT_REASON, ALERT_RULE_CATEGORY, ALERT_RULE_CONSUMER, @@ -29,61 +33,61 @@ import { ALERT_UUID, ALERT_WORKFLOW_STATUS, SPACE_IDS, + TIMESTAMP, VERSION, - ALERT_NAMESPACE, - ALERT_RULE_NAMESPACE, } from './default_alerts_as_data'; +import { + ALERT_RISK_SCORE, + ALERT_RULE_AUTHOR, + ALERT_RULE_CREATED_AT, + ALERT_RULE_CREATED_BY, + ALERT_RULE_DESCRIPTION, + ALERT_RULE_ENABLED, + ALERT_RULE_FROM, + ALERT_RULE_INTERVAL, + ALERT_RULE_LICENSE, + ALERT_RULE_NOTE, + ALERT_RULE_REFERENCES, + ALERT_RULE_RULE_ID, + ALERT_RULE_RULE_NAME_OVERRIDE, + ALERT_RULE_TO, + ALERT_RULE_TYPE, + ALERT_RULE_UPDATED_AT, + ALERT_RULE_UPDATED_BY, + ALERT_RULE_VERSION, + ALERT_SEVERITY, + ALERT_SUPPRESSION_DOCS_COUNT, + ALERT_SUPPRESSION_END, + ALERT_SUPPRESSION_FIELD, + ALERT_SUPPRESSION_START, + ALERT_SUPPRESSION_TERMS, + ALERT_SUPPRESSION_VALUE, + ALERT_SYSTEM_STATUS, + ALERT_WORKFLOW_REASON, + ALERT_WORKFLOW_USER, + ECS_VERSION, + EVENT_ACTION, + EVENT_KIND, + TAGS, +} from './legacy_alerts_as_data'; + +// The following fields were identified as technical field names but were not defined in the +// rule registry technical component template. We will leave these here for backwards +// compatibility but these consts should be moved to the plugin that uses them + const ALERT_RULE_THREAT_NAMESPACE = `${ALERT_RULE_NAMESPACE}.threat` as const; -const ECS_VERSION = 'ecs.version' as const; -const EVENT_ACTION = 'event.action' as const; -const EVENT_KIND = 'event.kind' as const; const EVENT_MODULE = 'event.module' as const; -const TAGS = 'tags' as const; -const TIMESTAMP = '@timestamp' as const; // Fields pertaining to the alert const ALERT_BUILDING_BLOCK_TYPE = `${ALERT_NAMESPACE}.building_block_type` as const; const ALERT_EVALUATION_THRESHOLD = `${ALERT_NAMESPACE}.evaluation.threshold` as const; const ALERT_EVALUATION_VALUE = `${ALERT_NAMESPACE}.evaluation.value` as const; -const ALERT_INSTANCE_ID = `${ALERT_NAMESPACE}.instance.id` as const; -const ALERT_RISK_SCORE = `${ALERT_NAMESPACE}.risk_score` as const; -const ALERT_SEVERITY = `${ALERT_NAMESPACE}.severity` as const; -const ALERT_SYSTEM_STATUS = `${ALERT_NAMESPACE}.system_status` as const; -const ALERT_WORKFLOW_REASON = `${ALERT_NAMESPACE}.workflow_reason` as const; -const ALERT_WORKFLOW_USER = `${ALERT_NAMESPACE}.workflow_user` as const; -const ALERT_SUPPRESSION_META = `${ALERT_NAMESPACE}.suppression` as const; -const ALERT_SUPPRESSION_TERMS = `${ALERT_SUPPRESSION_META}.terms` as const; -const ALERT_SUPPRESSION_FIELD = `${ALERT_SUPPRESSION_TERMS}.field` as const; -const ALERT_SUPPRESSION_VALUE = `${ALERT_SUPPRESSION_TERMS}.value` as const; -const ALERT_SUPPRESSION_START = `${ALERT_SUPPRESSION_META}.start` as const; -const ALERT_SUPPRESSION_END = `${ALERT_SUPPRESSION_META}.end` as const; -const ALERT_SUPPRESSION_DOCS_COUNT = `${ALERT_SUPPRESSION_META}.docs_count` as const; - -// Fields pertaining to the cases associated with the alert -const ALERT_CASE_IDS = `${ALERT_NAMESPACE}.case_ids` as const; // Fields pertaining to the rule associated with the alert -const ALERT_RULE_AUTHOR = `${ALERT_RULE_NAMESPACE}.author` as const; -const ALERT_RULE_CREATED_AT = `${ALERT_RULE_NAMESPACE}.created_at` as const; -const ALERT_RULE_CREATED_BY = `${ALERT_RULE_NAMESPACE}.created_by` as const; -const ALERT_RULE_DESCRIPTION = `${ALERT_RULE_NAMESPACE}.description` as const; -const ALERT_RULE_ENABLED = `${ALERT_RULE_NAMESPACE}.enabled` as const; const ALERT_RULE_EXCEPTIONS_LIST = `${ALERT_RULE_NAMESPACE}.exceptions_list` as const; -const ALERT_RULE_FROM = `${ALERT_RULE_NAMESPACE}.from` as const; -const ALERT_RULE_INTERVAL = `${ALERT_RULE_NAMESPACE}.interval` as const; -const ALERT_RULE_LICENSE = `${ALERT_RULE_NAMESPACE}.license` as const; const ALERT_RULE_NAMESPACE_FIELD = `${ALERT_RULE_NAMESPACE}.namespace` as const; -const ALERT_RULE_NOTE = `${ALERT_RULE_NAMESPACE}.note` as const; -const ALERT_RULE_REFERENCES = `${ALERT_RULE_NAMESPACE}.references` as const; -const ALERT_RULE_RULE_ID = `${ALERT_RULE_NAMESPACE}.rule_id` as const; -const ALERT_RULE_RULE_NAME_OVERRIDE = `${ALERT_RULE_NAMESPACE}.rule_name_override` as const; -const ALERT_RULE_TO = `${ALERT_RULE_NAMESPACE}.to` as const; -const ALERT_RULE_TYPE = `${ALERT_RULE_NAMESPACE}.type` as const; -const ALERT_RULE_UPDATED_AT = `${ALERT_RULE_NAMESPACE}.updated_at` as const; -const ALERT_RULE_UPDATED_BY = `${ALERT_RULE_NAMESPACE}.updated_by` as const; -const ALERT_RULE_VERSION = `${ALERT_RULE_NAMESPACE}.version` as const; // Fields pertaining to the threat tactic associated with the rule const ALERT_THREAT_FRAMEWORK = `${ALERT_RULE_THREAT_NAMESPACE}.framework` as const; @@ -186,36 +190,8 @@ export { ALERT_BUILDING_BLOCK_TYPE, ALERT_EVALUATION_THRESHOLD, ALERT_EVALUATION_VALUE, - ALERT_INSTANCE_ID, - ALERT_RISK_SCORE, - ALERT_WORKFLOW_REASON, - ALERT_WORKFLOW_USER, - ALERT_CASE_IDS, - ALERT_RULE_AUTHOR, - ALERT_RULE_CREATED_AT, - ALERT_RULE_CREATED_BY, - ALERT_RULE_DESCRIPTION, - ALERT_RULE_ENABLED, ALERT_RULE_EXCEPTIONS_LIST, - ALERT_RULE_FROM, - ALERT_RULE_INTERVAL, - ALERT_RULE_LICENSE, ALERT_RULE_NAMESPACE_FIELD, - ALERT_RULE_NOTE, - ALERT_RULE_REFERENCES, - ALERT_RULE_RULE_ID, - ALERT_RULE_RULE_NAME_OVERRIDE, - ALERT_RULE_TO, - ALERT_RULE_TYPE, - ALERT_RULE_UPDATED_AT, - ALERT_RULE_UPDATED_BY, - ALERT_RULE_VERSION, - ALERT_SEVERITY, - ALERT_SYSTEM_STATUS, - ECS_VERSION, - EVENT_ACTION, - EVENT_KIND, - EVENT_MODULE, ALERT_THREAT_FRAMEWORK, ALERT_THREAT_TACTIC_ID, ALERT_THREAT_TACTIC_NAME, @@ -226,14 +202,7 @@ export { ALERT_THREAT_TECHNIQUE_SUBTECHNIQUE_ID, ALERT_THREAT_TECHNIQUE_SUBTECHNIQUE_NAME, ALERT_THREAT_TECHNIQUE_SUBTECHNIQUE_REFERENCE, - ALERT_SUPPRESSION_TERMS, - ALERT_SUPPRESSION_FIELD, - ALERT_SUPPRESSION_VALUE, - ALERT_SUPPRESSION_START, - ALERT_SUPPRESSION_END, - ALERT_SUPPRESSION_DOCS_COUNT, - TAGS, - TIMESTAMP, + EVENT_MODULE, }; export type TechnicalRuleDataFieldName = ValuesType; diff --git a/packages/kbn-rule-data-utils/tsconfig.json b/packages/kbn-rule-data-utils/tsconfig.json index 5c94013fc2eaf..77352c4f44209 100644 --- a/packages/kbn-rule-data-utils/tsconfig.json +++ b/packages/kbn-rule-data-utils/tsconfig.json @@ -11,7 +11,7 @@ "**/*.ts" ], "kbn_references": [ - "@kbn/es-query" + "@kbn/es-query", ], "exclude": [ "target/**/*", diff --git a/src/core/server/integration_tests/saved_objects/migrations/archives/7.13.0_with_corrupted_so.zip b/src/core/server/integration_tests/saved_objects/migrations/archives/7.13.0_with_corrupted_so.zip deleted file mode 100644 index f4a89fbcb2514..0000000000000 Binary files a/src/core/server/integration_tests/saved_objects/migrations/archives/7.13.0_with_corrupted_so.zip and /dev/null differ diff --git a/src/core/server/integration_tests/saved_objects/migrations/group1/7_13_0_failed_action_tasks.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group1/7_13_0_failed_action_tasks.test.ts index 1538e9cfe8ef4..89478ea377f6c 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group1/7_13_0_failed_action_tasks.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group1/7_13_0_failed_action_tasks.test.ts @@ -24,90 +24,104 @@ async function removeLogFile() { } describe('migration from 7.13 to 7.14+ with many failed action_tasks', () => { - let esServer: TestElasticsearchUtils; - let root: Root; - let startES: () => Promise; + describe('if mappings are incompatible (reindex required)', () => { + let esServer: TestElasticsearchUtils; + let root: Root; + let startES: () => Promise; - beforeAll(async () => { - await removeLogFile(); - }); + beforeAll(async () => { + await removeLogFile(); + }); - beforeEach(() => { - ({ startES } = createTestServers({ - adjustTimeout: (t: number) => jest.setTimeout(t), - settings: { - es: { - license: 'basic', - dataArchive: Path.join(__dirname, '..', 'archives', '7.13_1.5k_failed_action_tasks.zip'), + beforeEach(() => { + ({ startES } = createTestServers({ + adjustTimeout: (t: number) => jest.setTimeout(t), + settings: { + es: { + license: 'basic', + dataArchive: Path.join( + __dirname, + '..', + 'archives', + '7.13_1.5k_failed_action_tasks.zip' + ), + }, }, - }, - })); - }); + })); + }); - afterEach(async () => { - if (root) { - await root.shutdown(); - } - if (esServer) { - await esServer.stop(); - } + afterEach(async () => { + if (root) { + await root.shutdown(); + } + if (esServer) { + await esServer.stop(); + } - await new Promise((resolve) => setTimeout(resolve, 10000)); - }); + await new Promise((resolve) => setTimeout(resolve, 10000)); + }); - const getCounts = async ( - kibanaIndexName = '.kibana', - taskManagerIndexName = '.kibana_task_manager' - ): Promise<{ tasksCount: number; actionTaskParamsCount: number }> => { - const esClient: ElasticsearchClient = esServer.es.getClient(); - - const actionTaskParamsResponse = await esClient.count({ - index: kibanaIndexName, - body: { - query: { - bool: { must: { term: { type: 'action_task_params' } } }, + const getCounts = async ( + kibanaIndexName = '.kibana', + taskManagerIndexName = '.kibana_task_manager' + ): Promise<{ tasksCount: number; actionTaskParamsCount: number }> => { + const esClient: ElasticsearchClient = esServer.es.getClient(); + + const actionTaskParamsResponse = await esClient.count({ + index: kibanaIndexName, + body: { + query: { + bool: { must: { term: { type: 'action_task_params' } } }, + }, }, - }, - }); - const tasksResponse = await esClient.count({ - index: taskManagerIndexName, - body: { - query: { - bool: { must: { term: { type: 'task' } } }, + }); + const tasksResponse = await esClient.count({ + index: taskManagerIndexName, + body: { + query: { + bool: { must: { term: { type: 'task' } } }, + }, }, - }, - }); + }); - return { - actionTaskParamsCount: actionTaskParamsResponse.count, - tasksCount: tasksResponse.count, + return { + actionTaskParamsCount: actionTaskParamsResponse.count, + tasksCount: tasksResponse.count, + }; }; - }; - - it('filters out all outdated action_task_params and action tasks', async () => { - esServer = await startES(); - - // Verify counts in current index before migration starts - expect(await getCounts()).toEqual({ - actionTaskParamsCount: 2010, - tasksCount: 2020, - }); - root = createRoot(); - await root.preboot(); - await root.setup(); - await root.start(); - - // Bulk of tasks should have been filtered out of current index - const { actionTaskParamsCount, tasksCount } = await getCounts(); - // Use toBeLessThan to avoid flakiness in the case that TM starts manipulating docs before the counts are taken - expect(actionTaskParamsCount).toBeLessThan(1000); - expect(tasksCount).toBeLessThan(1000); - - // Verify that docs were not deleted from old index - expect(await getCounts('.kibana_7.13.5_001', '.kibana_task_manager_7.13.5_001')).toEqual({ - actionTaskParamsCount: 2010, - tasksCount: 2020, + it('filters out all outdated action_task_params and action tasks', async () => { + esServer = await startES(); + + // Verify counts in current index before migration starts + expect(await getCounts()).toEqual({ + actionTaskParamsCount: 2010, + tasksCount: 2020, + }); + + root = createRoot(); + await root.preboot(); + await root.setup(); + await root.start(); + + // Bulk of tasks should have been filtered out of current index + const { actionTaskParamsCount, tasksCount } = await getCounts(); + // Use toBeLessThan to avoid flakiness in the case that TM starts manipulating docs before the counts are taken + expect(actionTaskParamsCount).toBeLessThan(1000); + expect(tasksCount).toBeLessThan(1000); + + const { + actionTaskParamsCount: oldIndexActionTaskParamsCount, + tasksCount: oldIndexTasksCount, + } = await getCounts('.kibana_7.13.5_001', '.kibana_task_manager_7.13.5_001'); + + // .kibana mappings changes are NOT compatible, we reindex and preserve old index's documents + expect(oldIndexActionTaskParamsCount).toEqual(2010); + + // ATM .kibana_task_manager mappings changes are compatible, we skip reindex and actively delete unwanted documents + // if the mappings become incompatible in the future, the we will reindex and the old index must still contain all 2020 docs + // if the mappings remain compatible, we reuse the existing index and actively delete unwanted documents from it + expect(oldIndexTasksCount === 2020 || oldIndexTasksCount < 1000).toEqual(true); }); }); }); diff --git a/src/core/server/integration_tests/saved_objects/migrations/group1/7_13_0_transform_failures.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group1/7_13_0_transform_failures.test.ts index 065a4a4241d07..8a798508ce18f 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group1/7_13_0_transform_failures.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group1/7_13_0_transform_failures.test.ts @@ -122,30 +122,30 @@ describe('migration v2', () => { // 23 saved objects + 14 corrupt (discarded) = 37 total in the old index expect((docs.hits.total as SearchTotalHits).value).toEqual(23); - expect(docs.hits.hits.map(({ _id }) => _id)).toEqual([ + expect(docs.hits.hits.map(({ _id }) => _id).sort()).toEqual([ 'config:7.13.0', 'index-pattern:logs-*', 'index-pattern:metrics-*', + 'ui-metric:console:DELETE_delete', + 'ui-metric:console:GET_get', + 'ui-metric:console:GET_search', + 'ui-metric:console:POST_delete_by_query', + 'ui-metric:console:POST_index', + 'ui-metric:console:PUT_indices.put_mapping', 'usage-counters:uiCounter:21052021:click:global_search_bar:user_navigated_to_application', + 'usage-counters:uiCounter:21052021:click:global_search_bar:user_navigated_to_application_unknown', + 'usage-counters:uiCounter:21052021:count:console:DELETE_delete', 'usage-counters:uiCounter:21052021:count:console:GET_cat.aliases', - 'usage-counters:uiCounter:21052021:loaded:console:opened_app', 'usage-counters:uiCounter:21052021:count:console:GET_cat.indices', + 'usage-counters:uiCounter:21052021:count:console:GET_get', + 'usage-counters:uiCounter:21052021:count:console:GET_search', + 'usage-counters:uiCounter:21052021:count:console:POST_delete_by_query', + 'usage-counters:uiCounter:21052021:count:console:POST_index', + 'usage-counters:uiCounter:21052021:count:console:PUT_indices.put_mapping', 'usage-counters:uiCounter:21052021:count:global_search_bar:search_focus', - 'usage-counters:uiCounter:21052021:click:global_search_bar:user_navigated_to_application_unknown', 'usage-counters:uiCounter:21052021:count:global_search_bar:search_request', 'usage-counters:uiCounter:21052021:count:global_search_bar:shortcut_used', - 'ui-metric:console:POST_delete_by_query', - 'usage-counters:uiCounter:21052021:count:console:PUT_indices.put_mapping', - 'usage-counters:uiCounter:21052021:count:console:POST_delete_by_query', - 'usage-counters:uiCounter:21052021:count:console:GET_search', - 'ui-metric:console:PUT_indices.put_mapping', - 'ui-metric:console:GET_search', - 'usage-counters:uiCounter:21052021:count:console:DELETE_delete', - 'ui-metric:console:DELETE_delete', - 'usage-counters:uiCounter:21052021:count:console:GET_get', - 'ui-metric:console:GET_get', - 'usage-counters:uiCounter:21052021:count:console:POST_index', - 'ui-metric:console:POST_index', + 'usage-counters:uiCounter:21052021:loaded:console:opened_app', ]); }); }); diff --git a/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts index 626b405b165ed..8aa1eb22fccbe 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts @@ -69,12 +69,12 @@ describe('checking migration metadata changes on all registered SO types', () => "canvas-element": "ec334dd45d14291db4d74197e0e42dfe06526868", "canvas-workpad": "ab0525bd5aa4dbad2d6fdb30e6a51bb475254751", "canvas-workpad-template": "c54f2a188a1d0bf18a6cebd9d6f28a7337d41bbf", - "cases": "74c00dfb25f4b109894971bd1090fce4a7c99490", - "cases-comments": "371662a8464e623f1f4f55a981cec78bec4a12f5", - "cases-configure": "25099c9e4bbb91e01e334848c605b4a5de5c9fce", - "cases-connector-mappings": "8de3b77dc6ae8ee62cce2b58a222471dfc3dbdad", + "cases": "1e86563e8364c69f86b77cb6f2933408dd5b827a", + "cases-comments": "69257ec55e8380fdb2ecbddc83e7c26d2ce2a351", + "cases-configure": "66d4c64d83b464f5166005b8ffa03b721fcaaf8b", + "cases-connector-mappings": "877bb4d52e9821e330622bd75fba799490ec6952", "cases-telemetry": "fdeddcef28c75d8c66422475a829e75d37f0b668", - "cases-user-actions": "cfd388d2ca27b3abfd3955dc41428fb229989921", + "cases-user-actions": "8ad74294b71edffa58fad7a40eea2388209477c9", "config": "97e16b8f5dc10c404fd3b201ef36bc6c3c63dc80", "config-global": "d9791e8f73edee884630e1cb6e4954ae513ce75e", "connector_token": "fb05ff5afdcb6e2f20c9c6513ff7a1ab12b66f36", diff --git a/src/core/server/integration_tests/saved_objects/migrations/group2/check_target_mappings.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group2/check_target_mappings.test.ts index 19326c15e0f25..54ab116d2c596 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group2/check_target_mappings.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group2/check_target_mappings.test.ts @@ -8,12 +8,10 @@ import Path from 'path'; import fs from 'fs/promises'; -import JSON5 from 'json5'; import { Env } from '@kbn/config'; import { REPO_ROOT } from '@kbn/repo-info'; import { getEnvOptions } from '@kbn/config-mocks'; import { Root } from '@kbn/core-root-server-internal'; -import { LogRecord } from '@kbn/logging'; import { createRootWithCorePlugins, createTestServers, @@ -23,30 +21,14 @@ import { delay } from '../test_utils'; const logFilePath = Path.join(__dirname, 'check_target_mappings.log'); -async function removeLogFile() { - // ignore errors if it doesn't exist - await fs.unlink(logFilePath).catch(() => void 0); -} - -async function parseLogFile() { - const logFileContent = await fs.readFile(logFilePath, 'utf-8'); - - return logFileContent - .split('\n') - .filter(Boolean) - .map((str) => JSON5.parse(str)) as LogRecord[]; -} - -function logIncludes(logs: LogRecord[], message: string): boolean { - return Boolean(logs?.find((rec) => rec.message.includes(message))); -} - describe('migration v2 - CHECK_TARGET_MAPPINGS', () => { let esServer: TestElasticsearchUtils; let root: Root; - let logs: LogRecord[]; + let logs: string; - beforeEach(async () => await removeLogFile()); + beforeEach(async () => { + await fs.unlink(logFilePath).catch(() => {}); + }); afterEach(async () => { await root?.shutdown(); @@ -71,9 +53,10 @@ describe('migration v2 - CHECK_TARGET_MAPPINGS', () => { await root.start(); // Check for migration steps present in the logs - logs = await parseLogFile(); - expect(logIncludes(logs, 'CREATE_NEW_TARGET')).toEqual(true); - expect(logIncludes(logs, 'CHECK_TARGET_MAPPINGS')).toEqual(false); + logs = await fs.readFile(logFilePath, 'utf-8'); + + expect(logs).toMatch('CREATE_NEW_TARGET'); + expect(logs).not.toMatch('CHECK_TARGET_MAPPINGS'); }); describe('when the indices are aligned with the stack version', () => { @@ -98,7 +81,7 @@ describe('migration v2 - CHECK_TARGET_MAPPINGS', () => { // stop Kibana and remove logs await root.shutdown(); await delay(10); - await removeLogFile(); + await fs.unlink(logFilePath).catch(() => {}); root = createRoot(); await root.preboot(); @@ -106,14 +89,12 @@ describe('migration v2 - CHECK_TARGET_MAPPINGS', () => { await root.start(); // Check for migration steps present in the logs - logs = await parseLogFile(); - expect(logIncludes(logs, 'CREATE_NEW_TARGET')).toEqual(false); - expect( - logIncludes(logs, 'CHECK_TARGET_MAPPINGS -> CHECK_VERSION_INDEX_READY_ACTIONS') - ).toEqual(true); - expect(logIncludes(logs, 'UPDATE_TARGET_MAPPINGS')).toEqual(false); - expect(logIncludes(logs, 'UPDATE_TARGET_MAPPINGS_WAIT_FOR_TASK')).toEqual(false); - expect(logIncludes(logs, 'UPDATE_TARGET_MAPPINGS_META')).toEqual(false); + logs = await fs.readFile(logFilePath, 'utf-8'); + expect(logs).not.toMatch('CREATE_NEW_TARGET'); + expect(logs).toMatch('CHECK_TARGET_MAPPINGS -> CHECK_VERSION_INDEX_READY_ACTIONS'); + expect(logs).not.toMatch('UPDATE_TARGET_MAPPINGS'); + expect(logs).not.toMatch('UPDATE_TARGET_MAPPINGS_WAIT_FOR_TASK'); + expect(logs).not.toMatch('UPDATE_TARGET_MAPPINGS_META'); }); }); @@ -140,23 +121,13 @@ describe('migration v2 - CHECK_TARGET_MAPPINGS', () => { await root.start(); // Check for migration steps present in the logs - logs = await parseLogFile(); - expect(logIncludes(logs, 'CREATE_NEW_TARGET')).toEqual(false); - expect(logIncludes(logs, 'CHECK_TARGET_MAPPINGS -> UPDATE_TARGET_MAPPINGS')).toEqual(true); - expect( - logIncludes(logs, 'UPDATE_TARGET_MAPPINGS -> UPDATE_TARGET_MAPPINGS_WAIT_FOR_TASK') - ).toEqual(true); - expect( - logIncludes(logs, 'UPDATE_TARGET_MAPPINGS_WAIT_FOR_TASK -> UPDATE_TARGET_MAPPINGS_META') - ).toEqual(true); - expect( - logIncludes(logs, 'UPDATE_TARGET_MAPPINGS_META -> CHECK_VERSION_INDEX_READY_ACTIONS') - ).toEqual(true); - expect( - logIncludes(logs, 'CHECK_VERSION_INDEX_READY_ACTIONS -> MARK_VERSION_INDEX_READY') - ).toEqual(true); - expect(logIncludes(logs, 'MARK_VERSION_INDEX_READY -> DONE')).toEqual(true); - expect(logIncludes(logs, 'Migration completed')).toEqual(true); + logs = await fs.readFile(logFilePath, 'utf-8'); + expect(logs).not.toMatch('CREATE_NEW_TARGET'); + expect(logs).toMatch('CHECK_TARGET_MAPPINGS -> UPDATE_TARGET_MAPPINGS'); + expect(logs).toMatch('UPDATE_TARGET_MAPPINGS -> UPDATE_TARGET_MAPPINGS_WAIT_FOR_TASK'); + expect(logs).toMatch('UPDATE_TARGET_MAPPINGS_WAIT_FOR_TASK -> UPDATE_TARGET_MAPPINGS_META'); + expect(logs).toMatch('UPDATE_TARGET_MAPPINGS_META -> CHECK_VERSION_INDEX_READY_ACTIONS'); + expect(logs).toMatch('Migration completed'); }); }); }); diff --git a/src/core/server/integration_tests/saved_objects/migrations/group2/cleanup.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group2/cleanup.test.ts index 8df491c36f4e3..e1030fe9805e9 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group2/cleanup.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group2/cleanup.test.ts @@ -10,122 +10,48 @@ import Path from 'path'; import Fs from 'fs'; import Util from 'util'; import JSON5 from 'json5'; +import { type TestElasticsearchUtils } from '@kbn/core-test-helpers-kbn-server'; +import { SavedObjectsType } from '@kbn/core-saved-objects-server'; +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { getMigrationDocLink, delay } from '../test_utils'; import { - createTestServers, - createRootWithCorePlugins, - type TestElasticsearchUtils, -} from '@kbn/core-test-helpers-kbn-server'; -import { Root } from '@kbn/core-root-server-internal'; -import { getMigrationDocLink } from '../test_utils'; + clearLog, + currentVersion, + defaultKibanaIndex, + getKibanaMigratorTestKit, + nextMinor, + startElasticsearch, +} from '../kibana_migrator_test_kit'; const migrationDocLink = getMigrationDocLink().resolveMigrationFailures; const logFilePath = Path.join(__dirname, 'cleanup.log'); -const asyncUnlink = Util.promisify(Fs.unlink); const asyncReadFile = Util.promisify(Fs.readFile); -async function removeLogFile() { - // ignore errors if it doesn't exist - await asyncUnlink(logFilePath).catch(() => void 0); -} - -function createRoot() { - return createRootWithCorePlugins( - { - migrations: { - skip: false, - }, - logging: { - appenders: { - file: { - type: 'file', - fileName: logFilePath, - layout: { - type: 'json', - }, - }, - }, - loggers: [ - { - name: 'root', - appenders: ['file'], - level: 'debug', // DEBUG logs are required to retrieve the PIT _id from the action response logs - }, - ], - }, - }, - { - oss: true, - } - ); -} - describe('migration v2', () => { - let esServer: TestElasticsearchUtils; - let root: Root; + let esServer: TestElasticsearchUtils['es']; + let esClient: ElasticsearchClient; beforeAll(async () => { - await removeLogFile(); + esServer = await startElasticsearch(); }); - afterAll(async () => { - if (root) { - await root.shutdown(); - } - if (esServer) { - await esServer.stop(); - } - - await new Promise((resolve) => setTimeout(resolve, 10000)); + beforeEach(async () => { + esClient = await setupBaseline(); + await clearLog(logFilePath); }); it('clean ups if migration fails', async () => { - const { startES } = createTestServers({ - adjustTimeout: (t: number) => jest.setTimeout(t), - settings: { - es: { - license: 'basic', - // original SO: - // { - // _index: '.kibana_7.13.0_001', - // _type: '_doc', - // _id: 'index-pattern:test_index*', - // _version: 1, - // result: 'created', - // _shards: { total: 2, successful: 1, failed: 0 }, - // _seq_no: 0, - // _primary_term: 1 - // } - dataArchive: Path.join(__dirname, '..', 'archives', '7.13.0_with_corrupted_so.zip'), - }, - }, - }); - - root = createRoot(); - - esServer = await startES(); - await root.preboot(); - const coreSetup = await root.setup(); - - coreSetup.savedObjects.registerType({ - name: 'foo', - hidden: false, - mappings: { - properties: {}, - }, - namespaceType: 'agnostic', - migrations: { - '7.14.0': (doc) => doc, - }, - }); + const { migrator, client } = await setupNextMinor(); + migrator.prepareMigrations(); - await expect(root.start()).rejects.toThrowErrorMatchingInlineSnapshot(` - "Unable to complete saved object migrations for the [.kibana] index: Migrations failed. Reason: 1 corrupt saved object documents were found: index-pattern:test_index* + await expect(migrator.runMigrations()).rejects.toThrowErrorMatchingInlineSnapshot(` + "Unable to complete saved object migrations for the [${defaultKibanaIndex}] index: Migrations failed. Reason: 1 corrupt saved object documents were found: corrupt:2baf4de0-a6d4-11ed-ba5a-39196fc76e60 - To allow migrations to proceed, please delete or fix these documents. - Note that you can configure Kibana to automatically discard corrupt documents and transform errors for this migration. - Please refer to ${migrationDocLink} for more information." - `); + To allow migrations to proceed, please delete or fix these documents. + Note that you can configure Kibana to automatically discard corrupt documents and transform errors for this migration. + Please refer to ${migrationDocLink} for more information." + `); const logFileContent = await asyncReadFile(logFilePath, 'utf-8'); const records = logFileContent @@ -134,7 +60,7 @@ describe('migration v2', () => { .map((str) => JSON5.parse(str)); const logRecordWithPit = records.find( - (rec) => rec.message === '[.kibana] REINDEX_SOURCE_TO_TEMP_OPEN_PIT RESPONSE' + (rec) => rec.message === `[${defaultKibanaIndex}] REINDEX_SOURCE_TO_TEMP_OPEN_PIT RESPONSE` ); expect(logRecordWithPit).toBeTruthy(); @@ -142,7 +68,6 @@ describe('migration v2', () => { const pitId = logRecordWithPit.right.pitId; expect(pitId).toBeTruthy(); - const client = esServer.es.getClient(); await expect( client.search({ body: { @@ -152,4 +77,132 @@ describe('migration v2', () => { // throws an exception that cannot search with closed PIT ).rejects.toThrow(/search_phase_execution_exception/); }); + + afterEach(async () => { + await esClient?.indices.delete({ index: `${defaultKibanaIndex}_${currentVersion}_001` }); + }); + + afterAll(async () => { + await esServer?.stop(); + await delay(10); + }); }); + +const setupBaseline = async () => { + const typesCurrent: SavedObjectsType[] = [ + { + name: 'complex', + hidden: false, + namespaceType: 'agnostic', + mappings: { + properties: { + name: { type: 'text' }, + value: { type: 'integer' }, + }, + }, + migrations: {}, + }, + ]; + + const savedObjects = [ + { + id: 'complex:4baf4de0-a6d4-11ed-ba5a-39196fc76e60', + body: { + type: 'complex', + complex: { + name: 'foo', + value: 5, + }, + references: [], + coreMigrationVersion: currentVersion, + updated_at: '2023-02-07T11:04:44.914Z', + created_at: '2023-02-07T11:04:44.914Z', + }, + }, + { + id: 'corrupt:2baf4de0-a6d4-11ed-ba5a-39196fc76e60', // incorrect id => corrupt object + body: { + type: 'complex', + complex: { + name: 'bar', + value: 3, + }, + references: [], + coreMigrationVersion: currentVersion, + updated_at: '2023-02-07T11:04:44.914Z', + created_at: '2023-02-07T11:04:44.914Z', + }, + }, + ]; + + const { migrator: baselineMigrator, client } = await getKibanaMigratorTestKit({ + types: typesCurrent, + logFilePath, + }); + + baselineMigrator.prepareMigrations(); + await baselineMigrator.runMigrations(); + + // inject corrupt saved objects directly using esClient + await Promise.all( + savedObjects.map((savedObject) => { + client.create({ + index: defaultKibanaIndex, + refresh: 'wait_for', + ...savedObject, + }); + }) + ); + + return client; +}; + +const setupNextMinor = async () => { + const typesNextMinor: SavedObjectsType[] = [ + { + name: 'complex', + hidden: false, + namespaceType: 'agnostic', + mappings: { + properties: { + name: { type: 'keyword' }, + value: { type: 'long' }, + }, + }, + migrations: { + [nextMinor]: (doc) => doc, + }, + }, + ]; + + const { migrator, client } = await getKibanaMigratorTestKit({ + types: typesNextMinor, + kibanaVersion: nextMinor, + logFilePath, + settings: { + migrations: { + skip: false, + }, + logging: { + appenders: { + file: { + type: 'file', + fileName: logFilePath, + layout: { + type: 'json', + }, + }, + }, + loggers: [ + { + name: 'root', + appenders: ['file'], + level: 'debug', // DEBUG logs are required to retrieve the PIT _id from the action response logs + }, + ], + }, + }, + }); + + return { migrator, client }; +}; diff --git a/src/core/server/integration_tests/saved_objects/migrations/group2/multiple_kibana_nodes.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group2/multiple_kibana_nodes.test.ts index f4577c0379096..51e3c7ce53fe0 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group2/multiple_kibana_nodes.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group2/multiple_kibana_nodes.test.ts @@ -112,7 +112,7 @@ describe('migration v2', () => { let rootB: Root; let rootC: Root; - const migratedIndex = `.kibana_${pkg.version}_001`; + const migratedIndexAlias = `.kibana_${pkg.version}`; const fooType: SavedObjectsType = { name: 'foo', hidden: false, @@ -189,7 +189,7 @@ describe('migration v2', () => { await startWithDelay([rootA, rootB, rootC], 0); const esClient = esServer.es.getClient(); - const migratedDocs = await fetchDocs(esClient, migratedIndex); + const migratedDocs = await fetchDocs(esClient, migratedIndexAlias); expect(migratedDocs.length).toBe(5000); @@ -208,7 +208,7 @@ describe('migration v2', () => { await startWithDelay([rootA, rootB, rootC], 1); const esClient = esServer.es.getClient(); - const migratedDocs = await fetchDocs(esClient, migratedIndex); + const migratedDocs = await fetchDocs(esClient, migratedIndexAlias); expect(migratedDocs.length).toBe(5000); @@ -227,7 +227,7 @@ describe('migration v2', () => { await startWithDelay([rootA, rootB, rootC], 5); const esClient = esServer.es.getClient(); - const migratedDocs = await fetchDocs(esClient, migratedIndex); + const migratedDocs = await fetchDocs(esClient, migratedIndexAlias); expect(migratedDocs.length).toBe(5000); @@ -246,7 +246,7 @@ describe('migration v2', () => { await startWithDelay([rootA, rootB, rootC], 20); const esClient = esServer.es.getClient(); - const migratedDocs = await fetchDocs(esClient, migratedIndex); + const migratedDocs = await fetchDocs(esClient, migratedIndexAlias); expect(migratedDocs.length).toBe(5000); diff --git a/src/core/server/integration_tests/saved_objects/migrations/group2/outdated_docs.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group2/outdated_docs.test.ts index 119ee16e9cddc..5c9ee13f3f825 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group2/outdated_docs.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group2/outdated_docs.test.ts @@ -46,7 +46,7 @@ describe('migration v2', () => { }); it('migrates the documents to the highest version', async () => { - const migratedIndex = `.kibana_${pkg.version}_001`; + const migratedIndexAlias = `.kibana_${pkg.version}`; const { startES } = createTestServers({ adjustTimeout: (t: number) => jest.setTimeout(t), settings: { @@ -90,7 +90,7 @@ describe('migration v2', () => { const coreStart = await root.start(); const esClient = coreStart.elasticsearch.client.asInternalUser; - const migratedDocs = await fetchDocs(esClient, migratedIndex); + const migratedDocs = await fetchDocs(esClient, migratedIndexAlias); expect(migratedDocs.length).toBe(1); const [doc] = migratedDocs; diff --git a/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions.test.ts index 691800feee0e3..64592be985719 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions.test.ts @@ -34,7 +34,7 @@ import { type UpdateByQueryResponse, updateAndPickupMappings, type UpdateAndPickupMappingsResponse, - updateTargetMappingsMeta, + updateMappings, removeWriteBlock, transformDocs, waitForIndexStatus, @@ -71,7 +71,11 @@ describe('migration actions', () => { indexName: 'existing_index_with_docs', mappings: { dynamic: true, - properties: {}, + properties: { + someProperty: { + type: 'integer', + }, + }, _meta: { migrationMappingPropertyHashes: { references: '7997cf5a56cc02bdc9c93361bde732b0', @@ -1486,15 +1490,22 @@ describe('migration actions', () => { }); }); - describe('updateTargetMappingsMeta', () => { + describe('updateMappings', () => { it('rejects if ES throws an error', async () => { - const task = updateTargetMappingsMeta({ + const task = updateMappings({ client, index: 'no_such_index', - meta: { - migrationMappingPropertyHashes: { - references: 'updateda56cc02bdc9c93361bupdated', - newReferences: 'fooBarHashMd509387420934879300d9', + mappings: { + properties: { + created_at: { + type: 'date', + }, + }, + _meta: { + migrationMappingPropertyHashes: { + references: 'updateda56cc02bdc9c93361bupdated', + newReferences: 'fooBarHashMd509387420934879300d9', + }, }, }, })(); @@ -1502,13 +1513,51 @@ describe('migration actions', () => { await expect(task).rejects.toThrow('index_not_found_exception'); }); - it('resolves right when mappings._meta are correctly updated', async () => { - const res = await updateTargetMappingsMeta({ + it('resolves left when the mappings are incompatible', async () => { + const res = await updateMappings({ client, index: 'existing_index_with_docs', - meta: { - migrationMappingPropertyHashes: { - newReferences: 'fooBarHashMd509387420934879300d9', + mappings: { + properties: { + someProperty: { + type: 'date', // attempt to change an existing field's type in an incompatible fashion + }, + }, + _meta: { + migrationMappingPropertyHashes: { + references: 'updateda56cc02bdc9c93361bupdated', + newReferences: 'fooBarHashMd509387420934879300d9', + }, + }, + }, + })(); + + expect(Either.isLeft(res)).toBe(true); + expect(res).toMatchInlineSnapshot(` + Object { + "_tag": "Left", + "left": Object { + "type": "incompatible_mapping_exception", + }, + } + `); + }); + + it('resolves right when mappings are correctly updated', async () => { + const res = await updateMappings({ + client, + index: 'existing_index_with_docs', + mappings: { + properties: { + created_at: { + type: 'date', + }, + }, + _meta: { + migrationMappingPropertyHashes: { + references: 'updateda56cc02bdc9c93361bupdated', + newReferences: 'fooBarHashMd509387420934879300d9', + }, }, }, })(); @@ -1519,8 +1568,17 @@ describe('migration actions', () => { index: ['existing_index_with_docs'], }); + expect(indices.existing_index_with_docs.mappings?.properties).toEqual( + expect.objectContaining({ + created_at: { + type: 'date', + }, + }) + ); + expect(indices.existing_index_with_docs.mappings?._meta).toEqual({ migrationMappingPropertyHashes: { + references: 'updateda56cc02bdc9c93361bupdated', newReferences: 'fooBarHashMd509387420934879300d9', }, }); diff --git a/src/core/server/integration_tests/saved_objects/migrations/group3/active_delete.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/active_delete.test.ts index 80681ffd0a5af..793ed9d100685 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group3/active_delete.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group3/active_delete.test.ts @@ -6,56 +6,28 @@ * Side Public License, v 1. */ -import Path from 'path'; -import fs from 'fs/promises'; -import { SemVer } from 'semver'; -import { Env } from '@kbn/config'; -import type { AggregationsAggregate, SearchResponse } from '@elastic/elasticsearch/lib/api/types'; -import { getEnvOptions } from '@kbn/config-mocks'; -import { REPO_ROOT } from '@kbn/repo-info'; -import { createTestServers, type TestElasticsearchUtils } from '@kbn/core-test-helpers-kbn-server'; -import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import type { SavedObjectsType } from '@kbn/core-saved-objects-server'; -import { getKibanaMigratorTestKit } from '../kibana_migrator_test_kit'; -import { baselineDocuments, baselineTypes } from './active_delete.fixtures'; +import { AggregationsAggregate, SearchResponse } from '@elastic/elasticsearch/lib/api/types'; +import { TestElasticsearchUtils } from '@kbn/core-test-helpers-kbn-server'; +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { + readLog, + clearLog, + nextMinor, + createBaseline, + currentVersion, + defaultKibanaIndex, + startElasticsearch, + getCompatibleMappingsMigrator, + getIdenticalMappingsMigrator, + getIncompatibleMappingsMigrator, + getNonDeprecatedMappingsMigrator, +} from '../kibana_migrator_test_kit'; import { delay } from '../test_utils'; -const kibanaIndex = '.kibana_migrator_tests'; -export const logFilePath = Path.join(__dirname, 'active_delete.test.log'); -const currentVersion = Env.createDefault(REPO_ROOT, getEnvOptions()).packageInfo.version; -const nextMinor = new SemVer(currentVersion).inc('minor').format(); - describe('when upgrading to a new stack version', () => { let esServer: TestElasticsearchUtils['es']; let esClient: ElasticsearchClient; - const startElasticsearch = async () => { - const { startES } = createTestServers({ - adjustTimeout: (t: number) => jest.setTimeout(t), - settings: { - es: { - license: 'basic', - }, - }, - }); - return await startES(); - }; - - const createBaseline = async () => { - const { client, runMigrations, savedObjectsRepository } = await getKibanaMigratorTestKit({ - kibanaIndex, - types: baselineTypes, - }); - - await runMigrations(); - - await savedObjectsRepository.bulkCreate(baselineDocuments, { - refresh: 'wait_for', - }); - - return client; - }; - beforeAll(async () => { esServer = await startElasticsearch(); }); @@ -65,92 +37,66 @@ describe('when upgrading to a new stack version', () => { await delay(10); }); - describe('and the mappings match (diffMappings() === false)', () => { + describe('if the mappings match (diffMappings() === false)', () => { describe('and discardUnknownObjects = true', () => { let indexContents: SearchResponse<{ type: string }, Record>; beforeAll(async () => { esClient = await createBaseline(); - await fs.unlink(logFilePath).catch(() => {}); + await clearLog(); // remove the 'deprecated' type from the mappings, so that it is considered unknown - const types = baselineTypes.filter((type) => type.name !== 'deprecated'); - const { client, runMigrations } = await getKibanaMigratorTestKit({ + const { client, runMigrations } = await getNonDeprecatedMappingsMigrator({ settings: { migrations: { discardUnknownObjects: nextMinor, }, }, - kibanaIndex, - types, - kibanaVersion: nextMinor, - logFilePath, }); await runMigrations(); - indexContents = await client.search({ index: kibanaIndex, size: 100 }); + indexContents = await client.search({ index: defaultKibanaIndex, size: 100 }); }); afterAll(async () => { - await esClient?.indices.delete({ index: `${kibanaIndex}_${currentVersion}_001` }); + await esClient?.indices.delete({ index: `${defaultKibanaIndex}_${currentVersion}_001` }); }); it('the migrator is skipping reindex operation and executing CLEANUP_UNKNOWN_AND_EXCLUDED step', async () => { - const logs = await fs.readFile(logFilePath, 'utf-8'); - expect(logs).toMatch('[.kibana_migrator_tests] INIT -> WAIT_FOR_YELLOW_SOURCE'); - expect(logs).toMatch( - '[.kibana_migrator_tests] WAIT_FOR_YELLOW_SOURCE -> CLEANUP_UNKNOWN_AND_EXCLUDED' - ); + const logs = await readLog(); + expect(logs).toMatch('INIT -> WAIT_FOR_YELLOW_SOURCE'); + expect(logs).toMatch('WAIT_FOR_YELLOW_SOURCE -> CLEANUP_UNKNOWN_AND_EXCLUDED'); // we gotta inform that we are deleting unknown documents too (discardUnknownObjects: true) expect(logs).toMatch( - '[.kibana_migrator_tests] Kibana has been configured to discard unknown documents for this migration.' + 'Kibana has been configured to discard unknown documents for this migration.' ); - expect(logs).toMatch( 'Therefore, the following documents with unknown types will not be taken into account and they will not be available after the migration:' ); expect(logs).toMatch( - '[.kibana_migrator_tests] CLEANUP_UNKNOWN_AND_EXCLUDED -> CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK' - ); - expect(logs).toMatch( - '[.kibana_migrator_tests] CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK -> PREPARE_COMPATIBLE_MIGRATION' - ); - expect(logs).toMatch( - '[.kibana_migrator_tests] PREPARE_COMPATIBLE_MIGRATION -> REFRESH_TARGET' - ); - expect(logs).toMatch( - '[.kibana_migrator_tests] REFRESH_TARGET -> OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT' + 'CLEANUP_UNKNOWN_AND_EXCLUDED -> CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK' ); expect(logs).toMatch( - '[.kibana_migrator_tests] CHECK_TARGET_MAPPINGS -> CHECK_VERSION_INDEX_READY_ACTIONS' + 'CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK -> PREPARE_COMPATIBLE_MIGRATION' ); - expect(logs).toMatch('[.kibana_migrator_tests] CHECK_VERSION_INDEX_READY_ACTIONS -> DONE'); + expect(logs).toMatch('PREPARE_COMPATIBLE_MIGRATION -> REFRESH_TARGET'); + expect(logs).toMatch('REFRESH_TARGET -> OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT'); + expect(logs).toMatch('CHECK_TARGET_MAPPINGS -> CHECK_VERSION_INDEX_READY_ACTIONS'); + expect(logs).toMatch('CHECK_VERSION_INDEX_READY_ACTIONS -> DONE'); }); describe('CLEANUP_UNKNOWN_AND_EXCLUDED', () => { it('preserves documents with known types', async () => { - const basicDocumentCount = indexContents.hits.hits.filter( - (result) => result._source?.type === 'basic' - ).length; - - expect(basicDocumentCount).toEqual(3); + expect(countResultsByType(indexContents, 'basic')).toEqual(3); }); it('deletes documents with unknown types', async () => { - const deprecatedDocumentCount = indexContents.hits.hits.filter( - (result) => result._source?.type === 'deprecated' - ).length; - - expect(deprecatedDocumentCount).toEqual(0); + expect(countResultsByType(indexContents, 'deprecated')).toEqual(0); }); it('deletes documents that belong to REMOVED_TYPES', async () => { - const serverDocumentCount = indexContents.hits.hits.filter( - (result) => result._source?.type === 'server' - ).length; - - expect(serverDocumentCount).toEqual(0); + expect(countResultsByType(indexContents, 'server')).toEqual(0); }); it("deletes documents that have been excludeOnUpgrade'd via plugin hook", async () => { @@ -186,21 +132,15 @@ describe('when upgrading to a new stack version', () => { esClient = await createBaseline(); }); afterAll(async () => { - await esClient?.indices.delete({ index: `${kibanaIndex}_${currentVersion}_001` }); + await esClient?.indices.delete({ index: `${defaultKibanaIndex}_${currentVersion}_001` }); }); beforeEach(async () => { - await fs.unlink(logFilePath).catch(() => {}); + await clearLog(); }); it('fails if unknown documents exist', async () => { - // remove the 'deprecated' type from the mappings, so that SO of this type are considered unknown - const types = baselineTypes.filter((type) => type.name !== 'deprecated'); - const { runMigrations } = await getKibanaMigratorTestKit({ - kibanaIndex, - types, - kibanaVersion: nextMinor, - logFilePath, - }); + // remove the 'deprecated' type from the mappings, so that it is considered unknown + const { runMigrations } = await getNonDeprecatedMappingsMigrator(); try { await runMigrations(); @@ -215,121 +155,237 @@ describe('when upgrading to a new stack version', () => { expect(errorMessage).toMatch(/deprecated:.*\(type: "deprecated"\)/); } - const logs = await fs.readFile(logFilePath, 'utf-8'); - expect(logs).toMatch('[.kibana_migrator_tests] INIT -> WAIT_FOR_YELLOW_SOURCE'); + const logs = await readLog(); + expect(logs).toMatch('INIT -> WAIT_FOR_YELLOW_SOURCE.'); + expect(logs).toMatch('WAIT_FOR_YELLOW_SOURCE -> CLEANUP_UNKNOWN_AND_EXCLUDED.'); + expect(logs).toMatch('CLEANUP_UNKNOWN_AND_EXCLUDED -> FATAL.'); + }); + + it('proceeds if there are no unknown documents', async () => { + const { client, runMigrations } = await getIdenticalMappingsMigrator(); + + await runMigrations(); + + const logs = await readLog(); + expect(logs).toMatch('INIT -> WAIT_FOR_YELLOW_SOURCE.'); + expect(logs).toMatch('WAIT_FOR_YELLOW_SOURCE -> CLEANUP_UNKNOWN_AND_EXCLUDED.'); + expect(logs).toMatch( + 'CLEANUP_UNKNOWN_AND_EXCLUDED -> CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK.' + ); expect(logs).toMatch( - '[.kibana_migrator_tests] WAIT_FOR_YELLOW_SOURCE -> CLEANUP_UNKNOWN_AND_EXCLUDED' + 'CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK -> PREPARE_COMPATIBLE_MIGRATION.' ); - expect(logs).toMatch('[.kibana_migrator_tests] CLEANUP_UNKNOWN_AND_EXCLUDED -> FATAL'); + expect(logs).toMatch('PREPARE_COMPATIBLE_MIGRATION -> REFRESH_TARGET.'); + expect(logs).toMatch('REFRESH_TARGET -> OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT.'); + expect(logs).toMatch('CHECK_TARGET_MAPPINGS -> CHECK_VERSION_INDEX_READY_ACTIONS.'); + expect(logs).toMatch('CHECK_VERSION_INDEX_READY_ACTIONS -> DONE.'); + + const indexContents = await client.search({ index: defaultKibanaIndex, size: 100 }); + expect(indexContents.hits.hits.length).toEqual(8); }); + }); + }); - it('proceeds if there are no unknown documents', async () => { - const { client, runMigrations } = await getKibanaMigratorTestKit({ - kibanaIndex, - types: baselineTypes, - kibanaVersion: nextMinor, - logFilePath, + describe('if the mappings are compatible', () => { + describe('and discardUnknownObjects = true', () => { + let indexContents: SearchResponse<{ type: string }, Record>; + + beforeAll(async () => { + esClient = await createBaseline(); + + await clearLog(); + const { client, runMigrations } = await getCompatibleMappingsMigrator({ + filterDeprecated: true, // remove the 'deprecated' type from the mappings, so that it is considered unknown + settings: { + migrations: { + discardUnknownObjects: nextMinor, + }, + }, }); await runMigrations(); - const logs = await fs.readFile(logFilePath, 'utf-8'); - expect(logs).toMatch('[.kibana_migrator_tests] INIT -> WAIT_FOR_YELLOW_SOURCE'); + indexContents = await client.search({ index: defaultKibanaIndex, size: 100 }); + }); + + afterAll(async () => { + await esClient?.indices.delete({ index: `${defaultKibanaIndex}_${currentVersion}_001` }); + }); + + it('the migrator is skipping reindex operation and executing CLEANUP_UNKNOWN_AND_EXCLUDED step', async () => { + const logs = await readLog(); + + expect(logs).toMatch('INIT -> WAIT_FOR_YELLOW_SOURCE.'); + expect(logs).toMatch('WAIT_FOR_YELLOW_SOURCE -> UPDATE_SOURCE_MAPPINGS.'); + // this step is run only if mappings are compatible but NOT equal + expect(logs).toMatch('UPDATE_SOURCE_MAPPINGS -> CLEANUP_UNKNOWN_AND_EXCLUDED.'); + // we gotta inform that we are deleting unknown documents too (discardUnknownObjects: true), expect(logs).toMatch( - '[.kibana_migrator_tests] WAIT_FOR_YELLOW_SOURCE -> CLEANUP_UNKNOWN_AND_EXCLUDED' + 'Kibana has been configured to discard unknown documents for this migration.' ); expect(logs).toMatch( - '[.kibana_migrator_tests] CLEANUP_UNKNOWN_AND_EXCLUDED -> CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK' + 'Therefore, the following documents with unknown types will not be taken into account and they will not be available after the migration:' ); expect(logs).toMatch( - '[.kibana_migrator_tests] CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK -> PREPARE_COMPATIBLE_MIGRATION' + 'CLEANUP_UNKNOWN_AND_EXCLUDED -> CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK.' ); expect(logs).toMatch( - '[.kibana_migrator_tests] PREPARE_COMPATIBLE_MIGRATION -> REFRESH_TARGET' + 'CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK -> PREPARE_COMPATIBLE_MIGRATION.' ); + expect(logs).toMatch('PREPARE_COMPATIBLE_MIGRATION -> REFRESH_TARGET.'); + expect(logs).toMatch('REFRESH_TARGET -> OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT.'); + expect(logs).toMatch('CHECK_TARGET_MAPPINGS -> UPDATE_TARGET_MAPPINGS.'); + expect(logs).toMatch('UPDATE_TARGET_MAPPINGS_META -> CHECK_VERSION_INDEX_READY_ACTIONS.'); + expect(logs).toMatch('CHECK_VERSION_INDEX_READY_ACTIONS -> DONE.'); + }); + + describe('CLEANUP_UNKNOWN_AND_EXCLUDED', () => { + it('preserves documents with known types', async () => { + expect(countResultsByType(indexContents, 'basic')).toEqual(3); + }); + + it('deletes documents with unknown types', async () => { + expect(countResultsByType(indexContents, 'deprecated')).toEqual(0); + }); + + it('deletes documents that belong to REMOVED_TYPES', async () => { + expect(countResultsByType(indexContents, 'server')).toEqual(0); + }); + + it("deletes documents that have been excludeOnUpgrade'd via plugin hook", async () => { + const complexDocuments = indexContents.hits.hits.filter( + (result) => result._source?.type === 'complex' + ); + + expect(complexDocuments.length).toEqual(2); + expect(complexDocuments[0]._source).toEqual( + expect.objectContaining({ + complex: { + name: 'complex-baz', + value: 2, + }, + type: 'complex', + }) + ); + expect(complexDocuments[1]._source).toEqual( + expect.objectContaining({ + complex: { + name: 'complex-lipsum', + value: 3, + }, + type: 'complex', + }) + ); + }); + }); + }); + + describe('and discardUnknownObjects = false', () => { + beforeAll(async () => { + esClient = await createBaseline(); + }); + afterAll(async () => { + await esClient?.indices.delete({ index: `${defaultKibanaIndex}_${currentVersion}_001` }); + }); + beforeEach(async () => { + await clearLog(); + }); + + it('fails if unknown documents exist', async () => { + const { runMigrations } = await getCompatibleMappingsMigrator({ + filterDeprecated: true, // remove the 'deprecated' type from the mappings, so that it is considered unknown + }); + + try { + await runMigrations(); + } catch (err) { + const errorMessage = err.message; + expect(errorMessage).toMatch( + 'Unable to complete saved object migrations for the [.kibana_migrator_tests] index: Migration failed because some documents were found which use unknown saved object types:' + ); + expect(errorMessage).toMatch( + 'To proceed with the migration you can configure Kibana to discard unknown saved objects for this migration.' + ); + expect(errorMessage).toMatch(/deprecated:.*\(type: "deprecated"\)/); + } + + const logs = await readLog(); + expect(logs).toMatch('INIT -> WAIT_FOR_YELLOW_SOURCE.'); + expect(logs).toMatch('WAIT_FOR_YELLOW_SOURCE -> UPDATE_SOURCE_MAPPINGS.'); // this step is run only if mappings are compatible but NOT equal + expect(logs).toMatch('UPDATE_SOURCE_MAPPINGS -> CLEANUP_UNKNOWN_AND_EXCLUDED.'); + expect(logs).toMatch('CLEANUP_UNKNOWN_AND_EXCLUDED -> FATAL.'); + }); + + it('proceeds if there are no unknown documents', async () => { + const { client, runMigrations } = await getCompatibleMappingsMigrator(); + + await runMigrations(); + + const logs = await readLog(); + expect(logs).toMatch('INIT -> WAIT_FOR_YELLOW_SOURCE.'); + expect(logs).toMatch('WAIT_FOR_YELLOW_SOURCE -> UPDATE_SOURCE_MAPPINGS.'); + expect(logs).toMatch('UPDATE_SOURCE_MAPPINGS -> CLEANUP_UNKNOWN_AND_EXCLUDED.'); expect(logs).toMatch( - '[.kibana_migrator_tests] REFRESH_TARGET -> OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT' + 'CLEANUP_UNKNOWN_AND_EXCLUDED -> CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK.' ); expect(logs).toMatch( - '[.kibana_migrator_tests] CHECK_TARGET_MAPPINGS -> CHECK_VERSION_INDEX_READY_ACTIONS' + 'CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK -> PREPARE_COMPATIBLE_MIGRATION.' ); - expect(logs).toMatch('[.kibana_migrator_tests] CHECK_VERSION_INDEX_READY_ACTIONS -> DONE'); + expect(logs).toMatch('PREPARE_COMPATIBLE_MIGRATION -> REFRESH_TARGET.'); + expect(logs).toMatch('REFRESH_TARGET -> OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT.'); + expect(logs).toMatch('CHECK_TARGET_MAPPINGS -> UPDATE_TARGET_MAPPINGS.'); + expect(logs).toMatch('UPDATE_TARGET_MAPPINGS_META -> CHECK_VERSION_INDEX_READY_ACTIONS.'); + expect(logs).toMatch('CHECK_VERSION_INDEX_READY_ACTIONS -> DONE.'); - const indexContents = await client.search({ index: kibanaIndex, size: 100 }); + const indexContents = await client.search({ index: defaultKibanaIndex, size: 100 }); expect(indexContents.hits.hits.length).toEqual(8); }); }); }); - describe('and the mappings do NOT match (diffMappings() === true)', () => { + describe('if the mappings do NOT match (diffMappings() === true) and they are NOT compatible', () => { beforeAll(async () => { esClient = await createBaseline(); }); afterAll(async () => { - await esClient?.indices.delete({ index: `${kibanaIndex}_${currentVersion}_001` }); + await esClient?.indices.delete({ index: `${defaultKibanaIndex}_${currentVersion}_001` }); }); beforeEach(async () => { - await fs.unlink(logFilePath).catch(() => {}); + await clearLog(); }); it('the migrator does not skip reindexing', async () => { - const incompatibleTypes: Array> = baselineTypes.map((type) => { - if (type.name === 'complex') { - return { - ...type, - mappings: { - properties: { - name: { type: 'keyword' }, // text => keyword - value: { type: 'long' }, // integer => long - }, - }, - }; - } else { - return type; - } - }); - - const { client, runMigrations } = await getKibanaMigratorTestKit({ - kibanaIndex, - types: incompatibleTypes, - kibanaVersion: nextMinor, - logFilePath, - }); + const { client, runMigrations } = await getIncompatibleMappingsMigrator(); await runMigrations(); - const logs = await fs.readFile(logFilePath, 'utf-8'); - expect(logs).toMatch('[.kibana_migrator_tests] INIT -> WAIT_FOR_YELLOW_SOURCE'); - expect(logs).toMatch( - '[.kibana_migrator_tests] WAIT_FOR_YELLOW_SOURCE -> CHECK_UNKNOWN_DOCUMENTS.' - ); - expect(logs).toMatch( - '[.kibana_migrator_tests] CHECK_UNKNOWN_DOCUMENTS -> SET_SOURCE_WRITE_BLOCK.' - ); - expect(logs).toMatch( - '[.kibana_migrator_tests] CHECK_TARGET_MAPPINGS -> UPDATE_TARGET_MAPPINGS.' - ); - expect(logs).toMatch( - '[.kibana_migrator_tests] UPDATE_TARGET_MAPPINGS_META -> CHECK_VERSION_INDEX_READY_ACTIONS.' - ); - expect(logs).toMatch( - '[.kibana_migrator_tests] CHECK_VERSION_INDEX_READY_ACTIONS -> MARK_VERSION_INDEX_READY.' - ); - expect(logs).toMatch('[.kibana_migrator_tests] MARK_VERSION_INDEX_READY -> DONE'); + const logs = await readLog(); + expect(logs).toMatch('INIT -> WAIT_FOR_YELLOW_SOURCE'); + expect(logs).toMatch('WAIT_FOR_YELLOW_SOURCE -> UPDATE_SOURCE_MAPPINGS.'); + expect(logs).toMatch('UPDATE_SOURCE_MAPPINGS -> CHECK_UNKNOWN_DOCUMENTS.'); + expect(logs).toMatch('CHECK_UNKNOWN_DOCUMENTS -> SET_SOURCE_WRITE_BLOCK.'); + expect(logs).toMatch('CHECK_TARGET_MAPPINGS -> UPDATE_TARGET_MAPPINGS.'); + expect(logs).toMatch('UPDATE_TARGET_MAPPINGS_META -> CHECK_VERSION_INDEX_READY_ACTIONS.'); + expect(logs).toMatch('CHECK_VERSION_INDEX_READY_ACTIONS -> MARK_VERSION_INDEX_READY.'); + expect(logs).toMatch('MARK_VERSION_INDEX_READY -> DONE'); const indexContents: SearchResponse< { type: string }, Record - > = await client.search({ index: kibanaIndex, size: 100 }); + > = await client.search({ index: defaultKibanaIndex, size: 100 }); expect(indexContents.hits.hits.length).toEqual(8); // we're removing a couple of 'complex' (value < = 1) // double-check that the deprecated documents have not been deleted - const deprecatedDocumentCount = indexContents.hits.hits.filter( - (result) => result._source?.type === 'deprecated' - ).length; - expect(deprecatedDocumentCount).toEqual(3); + expect(countResultsByType(indexContents, 'deprecated')).toEqual(3); }); }); }); + +const countResultsByType = ( + indexContents: SearchResponse<{ type: string }, Record>, + type: string +): number => { + return indexContents.hits.hits.filter((result) => result._source?.type === type).length; +}; diff --git a/src/core/server/integration_tests/saved_objects/migrations/group3/multiple_es_nodes.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/multiple_es_nodes.test.ts index 5cb4028eba2ca..1240f5873e3a0 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group3/multiple_es_nodes.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group3/multiple_es_nodes.test.ts @@ -97,7 +97,7 @@ function createRoot({ logFileName, hosts }: RootConfig) { describe('migration v2', () => { let esServer: TestElasticsearchUtils; let root: Root; - const migratedIndex = `.kibana_${pkg.version}_001`; + const migratedIndexAlias = `.kibana_${pkg.version}`; beforeAll(async () => { await removeLogFile(); @@ -186,7 +186,7 @@ describe('migration v2', () => { await root.start(); const esClient = esServer.es.getClient(); - const migratedFooDocs = await fetchDocs(esClient, migratedIndex, 'foo'); + const migratedFooDocs = await fetchDocs(esClient, migratedIndexAlias, 'foo'); expect(migratedFooDocs.length).toBe(2500); migratedFooDocs.forEach((doc, i) => { expect(doc.id).toBe(`foo:${i}`); @@ -194,7 +194,7 @@ describe('migration v2', () => { expect(doc.migrationVersion.foo).toBe('7.14.0'); }); - const migratedBarDocs = await fetchDocs(esClient, migratedIndex, 'bar'); + const migratedBarDocs = await fetchDocs(esClient, migratedIndexAlias, 'bar'); expect(migratedBarDocs.length).toBe(2500); migratedBarDocs.forEach((doc, i) => { expect(doc.id).toBe(`bar:${i}`); diff --git a/src/core/server/integration_tests/saved_objects/migrations/group3/rewriting_id.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/rewriting_id.test.ts index ae90b81482f4c..88193063d5526 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group3/rewriting_id.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group3/rewriting_id.test.ts @@ -113,7 +113,7 @@ describe('migration v2', () => { }); it('rewrites id deterministically for SO with namespaceType: "multiple" and "multiple-isolated"', async () => { - const migratedIndex = `.kibana_${pkg.version}_001`; + const migratedIndexAlias = `.kibana_${pkg.version}`; const { startES } = createTestServers({ adjustTimeout: (t: number) => jest.setTimeout(t), settings: { @@ -172,7 +172,7 @@ describe('migration v2', () => { const coreStart = await root.start(); const esClient = coreStart.elasticsearch.client.asInternalUser; - const migratedDocs = await fetchDocs(esClient, migratedIndex); + const migratedDocs = await fetchDocs(esClient, migratedIndexAlias); // each newly converted multi-namespace object in a non-default space has its ID deterministically regenerated, and a legacy-url-alias // object is created which links the old ID to the new ID diff --git a/src/core/server/integration_tests/saved_objects/migrations/group3/skip_reindex.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/skip_reindex.test.ts index aa5d1c0c06eb4..5354a958e8cb7 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group3/skip_reindex.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group3/skip_reindex.test.ts @@ -5,121 +5,137 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import Path from 'path'; -import fs from 'fs/promises'; -import { Env } from '@kbn/config'; -import { getEnvOptions } from '@kbn/config-mocks'; -import { REPO_ROOT } from '@kbn/repo-info'; -import type { Root } from '@kbn/core-root-server-internal'; + +import { type TestElasticsearchUtils } from '@kbn/core-test-helpers-kbn-server'; +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { IKibanaMigrator } from '@kbn/core-saved-objects-base-server-internal'; import { - createRootWithCorePlugins, - createTestServers, - type TestElasticsearchUtils, -} from '@kbn/core-test-helpers-kbn-server'; + readLog, + clearLog, + createBaseline, + currentVersion, + defaultKibanaIndex, + getCompatibleMappingsMigrator, + getIdenticalMappingsMigrator, + getIncompatibleMappingsMigrator, + startElasticsearch, +} from '../kibana_migrator_test_kit'; import { delay } from '../test_utils'; -import { SemVer } from 'semver'; - -const logFilePath = Path.join(__dirname, 'skip_reindex.log'); -describe('skip reindexing', () => { - const currentVersion = Env.createDefault(REPO_ROOT, getEnvOptions()).packageInfo.version; +describe('when migrating to a new version', () => { let esServer: TestElasticsearchUtils['es']; - let root: Root; + let esClient: ElasticsearchClient; + let migrator: IKibanaMigrator; - afterEach(async () => { - await root?.shutdown(); - await esServer?.stop(); - await delay(10); + beforeAll(async () => { + esServer = await startElasticsearch(); + }); + + beforeEach(async () => { + esClient = await createBaseline(); + await clearLog(); }); - it('when migrating to a new version, but mappings remain the same', async () => { - let logs: string; - const { startES } = createTestServers({ - adjustTimeout: (t: number) => jest.setTimeout(t), - settings: { - es: { - license: 'basic', - }, - }, + describe('and the mappings remain the same', () => { + it('the migrator skips reindexing', async () => { + // we run the migrator with the same identic baseline types + migrator = (await getIdenticalMappingsMigrator()).migrator; + migrator.prepareMigrations(); + await migrator.runMigrations(); + + const logs = await readLog(); + expect(logs).toMatch('INIT -> WAIT_FOR_YELLOW_SOURCE.'); + expect(logs).toMatch('WAIT_FOR_YELLOW_SOURCE -> CLEANUP_UNKNOWN_AND_EXCLUDED.'); + expect(logs).toMatch( + 'CLEANUP_UNKNOWN_AND_EXCLUDED -> CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK.' + ); + expect(logs).toMatch( + 'CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK -> PREPARE_COMPATIBLE_MIGRATION.' + ); + expect(logs).toMatch('PREPARE_COMPATIBLE_MIGRATION -> REFRESH_TARGET.'); + expect(logs).toMatch('REFRESH_TARGET -> OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT.'); + expect(logs).toMatch('CHECK_TARGET_MAPPINGS -> CHECK_VERSION_INDEX_READY_ACTIONS.'); + expect(logs).toMatch('CHECK_VERSION_INDEX_READY_ACTIONS -> DONE.'); + + expect(logs).not.toMatch('CREATE_NEW_TARGET'); + expect(logs).not.toMatch('CHECK_UNKNOWN_DOCUMENTS'); + expect(logs).not.toMatch('REINDEX'); + expect(logs).not.toMatch('UPDATE_TARGET_MAPPINGS'); }); - esServer = await startES(); - root = createRoot(); + }); - // Run initial migrations - await root.preboot(); - await root.setup(); - await root.start(); + describe("and the mappings' changes are still compatible", () => { + it('the migrator skips reindexing', async () => { + // we run the migrator with altered, compatible mappings + migrator = (await getCompatibleMappingsMigrator()).migrator; + migrator.prepareMigrations(); + await migrator.runMigrations(); - // stop Kibana and remove logs - await root.shutdown(); - await delay(10); - await fs.unlink(logFilePath).catch(() => {}); - - const nextPatch = new SemVer(currentVersion).inc('patch').format(); - root = createRoot(nextPatch); - await root.preboot(); - await root.setup(); - await root.start(); - - logs = await fs.readFile(logFilePath, 'utf-8'); - - expect(logs).toMatch('INIT -> WAIT_FOR_YELLOW_SOURCE'); - expect(logs).toMatch('WAIT_FOR_YELLOW_SOURCE -> CLEANUP_UNKNOWN_AND_EXCLUDED'); - expect(logs).toMatch( - 'CLEANUP_UNKNOWN_AND_EXCLUDED -> CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK' - ); - expect(logs).toMatch( - 'CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK -> PREPARE_COMPATIBLE_MIGRATION' - ); - expect(logs).toMatch('PREPARE_COMPATIBLE_MIGRATION -> OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT'); - expect(logs).toMatch('CHECK_TARGET_MAPPINGS -> CHECK_VERSION_INDEX_READY_ACTIONS'); - expect(logs).toMatch('CHECK_VERSION_INDEX_READY_ACTIONS -> DONE'); + const logs = await readLog(); + expect(logs).toMatch('INIT -> WAIT_FOR_YELLOW_SOURCE.'); + expect(logs).toMatch('WAIT_FOR_YELLOW_SOURCE -> UPDATE_SOURCE_MAPPINGS.'); + expect(logs).toMatch('UPDATE_SOURCE_MAPPINGS -> CLEANUP_UNKNOWN_AND_EXCLUDED.'); + expect(logs).toMatch( + 'CLEANUP_UNKNOWN_AND_EXCLUDED -> CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK.' + ); + expect(logs).toMatch( + 'CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK -> PREPARE_COMPATIBLE_MIGRATION.' + ); + expect(logs).toMatch('PREPARE_COMPATIBLE_MIGRATION -> REFRESH_TARGET.'); + expect(logs).toMatch('REFRESH_TARGET -> OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT.'); + expect(logs).toMatch('CHECK_TARGET_MAPPINGS -> UPDATE_TARGET_MAPPINGS.'); + expect(logs).toMatch('UPDATE_TARGET_MAPPINGS_META -> CHECK_VERSION_INDEX_READY_ACTIONS.'); + expect(logs).toMatch('CHECK_VERSION_INDEX_READY_ACTIONS -> DONE.'); - expect(logs).not.toMatch('CREATE_NEW_TARGET'); - expect(logs).not.toMatch('CHECK_TARGET_MAPPINGS -> UPDATE_TARGET_MAPPINGS'); + expect(logs).not.toMatch('CREATE_NEW_TARGET'); + expect(logs).not.toMatch('CHECK_UNKNOWN_DOCUMENTS'); + expect(logs).not.toMatch('REINDEX'); + }); + }); - // We restart Kibana again after doing a "compatible migration" to ensure that - // the next time state is loaded everything still works as expected. - // For instance, we might see something like: - // Unable to complete saved object migrations for the [.kibana] index. Please check the health of your Elasticsearch cluster and try again. Unexpected Elasticsearch ResponseError: statusCode: 404, method: POST, url: /.kibana_8.7.1_001/_pit?keep_alive=10m error: [index_not_found_exception]: no such index [.kibana_8.7.1_001] - await root.shutdown(); - await delay(10); - await fs.unlink(logFilePath).catch(() => {}); + describe("and the mappings' changes are NOT compatible", () => { + it('the migrator reindexes documents to a new index', async () => { + // we run the migrator with altered, compatible mappings + migrator = (await getIncompatibleMappingsMigrator()).migrator; + migrator.prepareMigrations(); + await migrator.runMigrations(); - root = createRoot(nextPatch); - await root.preboot(); - await root.setup(); - await root.start(); + const logs = await readLog(); + expect(logs).toMatch('INIT -> WAIT_FOR_YELLOW_SOURCE.'); + expect(logs).toMatch('WAIT_FOR_YELLOW_SOURCE -> UPDATE_SOURCE_MAPPINGS.'); + expect(logs).toMatch('UPDATE_SOURCE_MAPPINGS -> CHECK_UNKNOWN_DOCUMENTS.'); + expect(logs).toMatch('CHECK_TARGET_MAPPINGS -> UPDATE_TARGET_MAPPINGS.'); + expect(logs).toMatch('UPDATE_TARGET_MAPPINGS_META -> CHECK_VERSION_INDEX_READY_ACTIONS.'); + expect(logs).toMatch('CHECK_VERSION_INDEX_READY_ACTIONS -> MARK_VERSION_INDEX_READY.'); + expect(logs).toMatch('MARK_VERSION_INDEX_READY -> DONE.'); - logs = await fs.readFile(logFilePath, 'utf-8'); - expect(logs).toMatch('INIT -> OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT'); - expect(logs).not.toMatch('INIT -> WAIT_FOR_YELLOW_SOURCE'); + expect(logs).not.toMatch('CREATE_NEW_TARGET'); + expect(logs).not.toMatch('CLEANUP_UNKNOWN_AND_EXCLUDED'); + expect(logs).not.toMatch('PREPARE_COMPATIBLE_MIGRATION'); + }); + }); + + afterEach(async () => { + // we run the migrator again to ensure that the next time state is loaded everything still works as expected + await clearLog(); + await migrator.runMigrations({ rerun: true }); + + const logs = await readLog(); + expect(logs).toMatch('INIT -> OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT.'); + expect(logs).toMatch('CHECK_VERSION_INDEX_READY_ACTIONS -> DONE.'); + + expect(logs).not.toMatch('WAIT_FOR_YELLOW_SOURCE'); + expect(logs).not.toMatch('CLEANUP_UNKNOWN_AND_EXCLUCED'); + expect(logs).not.toMatch('CREATE_NEW_TARGET'); + expect(logs).not.toMatch('PREPARE_COMPATIBLE_MIGRATION'); + expect(logs).not.toMatch('UPDATE_TARGET_MAPPINGS'); + + // clear the system index for next test + await esClient?.indices.delete({ index: `${defaultKibanaIndex}_${currentVersion}_001` }); }); -}); -function createRoot(kibanaVersion?: string): Root { - return createRootWithCorePlugins( - { - logging: { - appenders: { - file: { - type: 'file', - fileName: logFilePath, - layout: { - type: 'json', - }, - }, - }, - loggers: [ - { - name: 'root', - level: 'info', - appenders: ['file'], - }, - ], - }, - }, - { oss: true }, - kibanaVersion - ); -} + afterAll(async () => { + await esServer?.stop(); + await delay(10); + }); +}); diff --git a/src/core/server/integration_tests/saved_objects/migrations/kibana_migrator_test_kit.fixtures.ts b/src/core/server/integration_tests/saved_objects/migrations/kibana_migrator_test_kit.fixtures.ts new file mode 100644 index 0000000000000..7d605cf116341 --- /dev/null +++ b/src/core/server/integration_tests/saved_objects/migrations/kibana_migrator_test_kit.fixtures.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 type { SavedObjectsBulkCreateObject } from '@kbn/core-saved-objects-api-server'; +import type { SavedObjectsType } from '@kbn/core-saved-objects-server'; + +const defaultType: SavedObjectsType = { + name: 'defaultType', + hidden: false, + namespaceType: 'agnostic', + mappings: { + properties: { + name: { type: 'keyword' }, + }, + }, + migrations: {}, +}; + +export const baselineTypes: Array> = [ + { + ...defaultType, + name: 'server', + }, + { + ...defaultType, + name: 'basic', + }, + { + ...defaultType, + name: 'deprecated', + }, + { + ...defaultType, + name: 'complex', + mappings: { + properties: { + name: { type: 'text' }, + value: { type: 'integer' }, + }, + }, + excludeOnUpgrade: () => { + return { + bool: { + must: [{ term: { type: 'complex' } }, { range: { 'complex.value': { lte: 1 } } }], + }, + }; + }, + }, +]; + +export const baselineDocuments: SavedObjectsBulkCreateObject[] = [ + ...['server-foo', 'server-bar', 'server-baz'].map((name) => ({ + type: 'server', + attributes: { + name, + }, + })), + ...['basic-foo', 'basic-bar', 'basic-baz'].map((name) => ({ + type: 'basic', + attributes: { + name, + }, + })), + ...['deprecated-foo', 'deprecated-bar', 'deprecated-baz'].map((name) => ({ + type: 'deprecated', + attributes: { + name, + }, + })), + ...['complex-foo', 'complex-bar', 'complex-baz', 'complex-lipsum'].map((name, index) => ({ + type: 'complex', + attributes: { + name, + value: index, + }, + })), +]; diff --git a/src/core/server/integration_tests/saved_objects/migrations/kibana_migrator_test_kit.ts b/src/core/server/integration_tests/saved_objects/migrations/kibana_migrator_test_kit.ts index df3cce7dbdca6..f6760aa3264fc 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/kibana_migrator_test_kit.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/kibana_migrator_test_kit.ts @@ -7,6 +7,9 @@ */ import Path from 'path'; +import fs from 'fs/promises'; +import { SemVer } from 'semver'; + import { defaultsDeep } from 'lodash'; import { BehaviorSubject, firstValueFrom, map } from 'rxjs'; import { ConfigService, Env } from '@kbn/config'; @@ -19,8 +22,8 @@ import { type SavedObjectsConfigType, type SavedObjectsMigrationConfigType, SavedObjectTypeRegistry, - IKibanaMigrator, - MigrationResult, + type IKibanaMigrator, + type MigrationResult, } from '@kbn/core-saved-objects-base-server-internal'; import { SavedObjectsRepository } from '@kbn/core-saved-objects-api-server-internal'; import { @@ -32,20 +35,23 @@ import { type LoggingConfigType, LoggingSystem } from '@kbn/core-logging-server- import type { ISavedObjectTypeRegistry, SavedObjectsType } from '@kbn/core-saved-objects-server'; import { esTestConfig, kibanaServerTestUser } from '@kbn/test'; -import { LoggerFactory } from '@kbn/logging'; +import type { LoggerFactory } from '@kbn/logging'; +import { createTestServers } from '@kbn/core-test-helpers-kbn-server'; import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { registerServiceConfig } from '@kbn/core-root-server-internal'; -import { ISavedObjectsRepository } from '@kbn/core-saved-objects-api-server'; +import type { ISavedObjectsRepository } from '@kbn/core-saved-objects-api-server'; import { getDocLinks, getDocLinksMeta } from '@kbn/doc-links'; -import { DocLinksServiceStart } from '@kbn/core-doc-links-server'; -import { createTestServers } from '@kbn/core-test-helpers-kbn-server'; +import type { DocLinksServiceStart } from '@kbn/core-doc-links-server'; +import { baselineDocuments, baselineTypes } from './kibana_migrator_test_kit.fixtures'; export const defaultLogFilePath = Path.join(__dirname, 'kibana_migrator_test_kit.log'); const env = Env.createDefault(REPO_ROOT, getEnvOptions()); // Extract current stack version from Env, to use as a default -const currentVersion = env.packageInfo.version; -const currentBranch = env.packageInfo.branch; +export const currentVersion = env.packageInfo.version; +export const nextMinor = new SemVer(currentVersion).inc('minor').format(); +export const currentBranch = env.packageInfo.branch; +export const defaultKibanaIndex = '.kibana_migrator_tests'; export interface GetEsClientParams { settings?: Record; @@ -76,7 +82,7 @@ export const startElasticsearch = async ({ }: { basePath?: string; dataArchive?: string; -}) => { +} = {}) => { const { startES } = createTestServers({ adjustTimeout: (t: number) => jest.setTimeout(t), settings: { @@ -109,7 +115,7 @@ export const getEsClient = async ({ export const getKibanaMigratorTestKit = async ({ settings = {}, - kibanaIndex = '.kibana', + kibanaIndex = defaultKibanaIndex, kibanaVersion = currentVersion, kibanaBranch = currentBranch, types = [], @@ -272,3 +278,113 @@ const registerTypes = ( ) => { (types || []).forEach((type) => typeRegistry.registerType(type)); }; + +export const createBaseline = async () => { + const { client, migrator, savedObjectsRepository } = await getKibanaMigratorTestKit({ + kibanaIndex: defaultKibanaIndex, + types: baselineTypes, + }); + + migrator.prepareMigrations(); + await migrator.runMigrations(); + + await savedObjectsRepository.bulkCreate(baselineDocuments, { + refresh: 'wait_for', + }); + + return client; +}; + +interface GetMutatedMigratorParams { + kibanaVersion?: string; + settings?: Record; +} + +export const getIdenticalMappingsMigrator = async ({ + kibanaVersion = nextMinor, + settings = {}, +}: GetMutatedMigratorParams = {}) => { + return await getKibanaMigratorTestKit({ + types: baselineTypes, + kibanaVersion, + settings, + }); +}; + +export const getNonDeprecatedMappingsMigrator = async ({ + kibanaVersion = nextMinor, + settings = {}, +}: GetMutatedMigratorParams = {}) => { + return await getKibanaMigratorTestKit({ + types: baselineTypes.filter((type) => type.name !== 'deprecated'), + kibanaVersion, + settings, + }); +}; + +export const getCompatibleMappingsMigrator = async ({ + filterDeprecated = false, + kibanaVersion = nextMinor, + settings = {}, +}: GetMutatedMigratorParams & { filterDeprecated?: boolean } = {}) => { + const types = baselineTypes + .filter((type) => !filterDeprecated || type.name !== 'deprecated') + .map((type) => { + if (type.name === 'complex') { + return { + ...type, + mappings: { + properties: { + name: { type: 'text' }, + value: { type: 'integer' }, + createdAt: { type: 'date' }, + }, + }, + }; + } else { + return type; + } + }); + + return await getKibanaMigratorTestKit({ + types, + kibanaVersion, + settings, + }); +}; + +export const getIncompatibleMappingsMigrator = async ({ + kibanaVersion = nextMinor, + settings = {}, +}: GetMutatedMigratorParams = {}) => { + const types = baselineTypes.map((type) => { + if (type.name === 'complex') { + return { + ...type, + mappings: { + properties: { + name: { type: 'keyword' }, + value: { type: 'long' }, + createdAt: { type: 'date' }, + }, + }, + }; + } else { + return type; + } + }); + + return await getKibanaMigratorTestKit({ + types, + kibanaVersion, + settings, + }); +}; + +export const readLog = async (logFilePath: string = defaultLogFilePath): Promise => { + return await fs.readFile(logFilePath, 'utf-8'); +}; + +export const clearLog = async (logFilePath: string = defaultLogFilePath): Promise => { + await fs.truncate(logFilePath).catch(() => {}); +}; diff --git a/src/dev/license_checker/config.ts b/src/dev/license_checker/config.ts index dd0600b71d2b2..e45c89bf69b7a 100644 --- a/src/dev/license_checker/config.ts +++ b/src/dev/license_checker/config.ts @@ -84,6 +84,6 @@ export const LICENSE_OVERRIDES = { 'jsts@1.6.2': ['Eclipse Distribution License - v 1.0'], // cf. https://github.com/bjornharrtell/jsts '@mapbox/jsonlint-lines-primitives@2.0.2': ['MIT'], // license in readme https://github.com/tmcw/jsonlint '@elastic/ems-client@8.4.0': ['Elastic License 2.0'], - '@elastic/eui@75.1.0': ['SSPL-1.0 OR Elastic License 2.0'], + '@elastic/eui@75.1.2': ['SSPL-1.0 OR Elastic License 2.0'], 'language-subtag-registry@0.3.21': ['CC-BY-4.0'], // retired ODC‑By license https://github.com/mattcg/language-subtag-registry }; diff --git a/src/dev/performance/run_performance_cli.ts b/src/dev/performance/run_performance_cli.ts index daec946f8962b..3539ada07e405 100644 --- a/src/dev/performance/run_performance_cli.ts +++ b/src/dev/performance/run_performance_cli.ts @@ -103,7 +103,7 @@ run( process.stdout.write(`--- Starting ES\n`); await procRunner.run('es', { cmd: 'node', - args: ['scripts/es', 'snapshot'], + args: ['scripts/es', 'snapshot', '--license=trial'], cwd: REPO_ROOT, wait: /kbn\/es setup complete/, }); diff --git a/src/dev/storybook/aliases.ts b/src/dev/storybook/aliases.ts index fc80a35a2def8..47ebefcc9c8c7 100644 --- a/src/dev/storybook/aliases.ts +++ b/src/dev/storybook/aliases.ts @@ -18,7 +18,7 @@ export const storybookAliases = { language_documentation_popover: 'packages/kbn-language-documentation-popover/.storybook', chart_icons: 'packages/kbn-chart-icons/.storybook', content_management: 'packages/content-management/.storybook', - content_management_plugin: 'src/plugins/content_management/.storybook', + content_management_examples: 'examples/content_management_examples/.storybook', controls: 'src/plugins/controls/storybook', custom_integrations: 'src/plugins/custom_integrations/storybook', dashboard_enhanced: 'x-pack/plugins/dashboard_enhanced/.storybook', diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/components/__snapshots__/partition_vis_component.test.tsx.snap b/src/plugins/chart_expressions/expression_partition_vis/public/components/__snapshots__/partition_vis_component.test.tsx.snap index c91e491887a99..1a566571b4d6c 100644 --- a/src/plugins/chart_expressions/expression_partition_vis/public/components/__snapshots__/partition_vis_component.test.tsx.snap +++ b/src/plugins/chart_expressions/expression_partition_vis/public/components/__snapshots__/partition_vis_component.test.tsx.snap @@ -1267,7 +1267,7 @@ exports[`PartitionVisComponent should render correct structure for multi-metric "fillColor": [Function], }, "showAccessor": [Function], - "sortPredicate": undefined, + "sortPredicate": [Function], }, ] } diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_color.test.ts b/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_color.test.ts index 225f405bac2ca..06fa1d9aae83b 100644 --- a/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_color.test.ts +++ b/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_color.test.ts @@ -10,6 +10,15 @@ import type { PaletteOutput, PaletteDefinition } from '@kbn/coloring'; import { chartPluginMock } from '@kbn/charts-plugin/public/mocks'; import { Datatable } from '@kbn/expressions-plugin/common'; import { byDataColorPaletteMap } from './get_color'; +import { ShapeTreeNode } from '@elastic/charts'; +import type { SeriesLayer } from '@kbn/coloring'; +import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; +import { fieldFormatsMock } from '@kbn/field-formats-plugin/common/mocks'; +import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import { getColor } from './get_color'; +import { createMockVisData, createMockBucketColumns, createMockPieParams } from '../../mocks'; +import { generateFormatters } from '../formatters'; +import { ChartTypes } from '../../../common/types'; describe('#byDataColorPaletteMap', () => { let datatable: Datatable; @@ -88,3 +97,284 @@ describe('#byDataColorPaletteMap', () => { ); }); }); + +describe('getColor', () => { + const visData = createMockVisData(); + const buckets = createMockBucketColumns(); + const visParams = createMockPieParams(); + const colors = ['color1', 'color2', 'color3', 'color4']; + const dataMock = dataPluginMock.createStartContract(); + interface RangeProps { + gte: number; + lt: number; + } + const defaultFormatter = jest.fn((...args) => fieldFormatsMock.deserialize(...args)); + const formatters = generateFormatters(visData, defaultFormatter); + + dataMock.fieldFormats = { + deserialize: jest.fn(() => ({ + convert: jest.fn((s: RangeProps) => { + return `≥ ${s.gte} and < ${s.lt}`; + }), + })), + } as unknown as DataPublicPluginStart['fieldFormats']; + + const getPaletteRegistry = () => { + const mockPalette1: jest.Mocked = { + id: 'default', + title: 'My Palette', + getCategoricalColor: jest.fn((layer: SeriesLayer[]) => colors[layer[0].rankAtDepth]), + getCategoricalColors: jest.fn((num: number) => colors), + toExpression: jest.fn(() => ({ + type: 'expression', + chain: [ + { + type: 'function', + function: 'system_palette', + arguments: { + name: ['default'], + }, + }, + ], + })), + }; + + return { + get: () => mockPalette1, + getAll: () => [mockPalette1], + }; + }; + it('should return the correct color based on the parent sortIndex', () => { + const d = { + dataName: 'ES-Air', + depth: 1, + sortIndex: 0, + parent: { + children: [['ES-Air'], ['Kibana Airlines']], + depth: 0, + sortIndex: 0, + }, + } as unknown as ShapeTreeNode; + const color = getColor( + ChartTypes.PIE, + d, + 0, + false, + {}, + buckets, + visData.rows, + visParams, + getPaletteRegistry(), + { getColor: () => undefined }, + false, + false, + dataMock.fieldFormats, + visData.columns[0], + formatters + ); + expect(color).toEqual(colors[0]); + }); + + it('slices with the same label should have the same color for small multiples', () => { + const d = { + dataName: 'ES-Air', + depth: 1, + sortIndex: 0, + parent: { + children: [['ES-Air'], ['Kibana Airlines']], + depth: 0, + sortIndex: 0, + }, + } as unknown as ShapeTreeNode; + const color = getColor( + ChartTypes.PIE, + d, + 0, + true, + {}, + buckets, + visData.rows, + visParams, + getPaletteRegistry(), + { getColor: () => undefined }, + false, + false, + dataMock.fieldFormats, + visData.columns[0], + formatters + ); + expect(color).toEqual('color3'); + }); + it('returns the overwriteColor if exists', () => { + const d = { + dataName: 'ES-Air', + depth: 1, + sortIndex: 0, + parent: { + children: [['ES-Air'], ['Kibana Airlines']], + depth: 0, + sortIndex: 0, + }, + } as unknown as ShapeTreeNode; + const color = getColor( + ChartTypes.PIE, + d, + 0, + true, + { 'ES-Air': '#000028' }, + buckets, + visData.rows, + visParams, + getPaletteRegistry(), + { getColor: () => undefined }, + false, + false, + dataMock.fieldFormats, + visData.columns[0], + formatters + ); + expect(color).toEqual('#000028'); + }); + + it('returns the overwriteColor for older visualizations with formatted values', () => { + const d = { + dataName: { + gte: 1000, + lt: 2000, + }, + depth: 1, + sortIndex: 0, + parent: { + children: [ + [ + { + gte: 1000, + lt: 2000, + }, + ], + [ + { + gte: 2000, + lt: 3000, + }, + ], + ], + depth: 0, + sortIndex: 0, + }, + } as unknown as ShapeTreeNode; + const visParamsNew = { + ...visParams, + distinctColors: true, + }; + const column = { + ...visData.columns[0], + format: { + id: 'range', + params: { + id: 'number', + }, + }, + }; + const color = getColor( + ChartTypes.PIE, + d, + 0, + true, + { '≥ 1000 and < 2000': '#3F6833' }, + buckets, + visData.rows, + visParamsNew, + getPaletteRegistry(), + { getColor: () => undefined }, + false, + false, + dataMock.fieldFormats, + column, + formatters + ); + expect(color).toEqual('#3F6833'); + }); + + it('should only pass the second layer for mosaic', () => { + const d = { + dataName: 'Second level 1', + depth: 2, + sortIndex: 0, + parent: { + children: [['Second level 1'], ['Second level 2']], + depth: 1, + sortIndex: 0, + parent: { + children: [['First level']], + depth: 0, + sortIndex: 0, + }, + }, + } as unknown as ShapeTreeNode; + const registry = getPaletteRegistry(); + getColor( + ChartTypes.MOSAIC, + d, + 1, + true, + {}, + buckets, + visData.rows, + visParams, + registry, + undefined, + true, + false, + dataMock.fieldFormats, + visData.columns[0], + formatters + ); + expect(registry.get().getCategoricalColor).toHaveBeenCalledWith( + [expect.objectContaining({ name: 'Second level 1' })], + expect.anything(), + expect.anything() + ); + }); + + it('should only pass the first layer for treemap', () => { + const d = { + dataName: 'Second level 1', + depth: 2, + sortIndex: 0, + parent: { + children: [['Second level 1'], ['Second level 2']], + depth: 1, + sortIndex: 0, + parent: { + children: [['First level']], + depth: 0, + sortIndex: 0, + }, + }, + } as unknown as ShapeTreeNode; + const registry = getPaletteRegistry(); + getColor( + ChartTypes.TREEMAP, + d, + 1, + true, + {}, + buckets, + visData.rows, + visParams, + registry, + undefined, + true, + false, + dataMock.fieldFormats, + visData.columns[0], + formatters + ); + expect(registry.get().getCategoricalColor).toHaveBeenCalledWith( + [expect.objectContaining({ name: 'First level' })], + expect.anything(), + expect.anything() + ); + }); +}); diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.test.ts b/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.test.ts index d96ada51ba47a..7300aac06329f 100644 --- a/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.test.ts +++ b/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.test.ts @@ -5,294 +5,110 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import { ShapeTreeNode } from '@elastic/charts'; -import type { PaletteDefinition, SeriesLayer } from '@kbn/coloring'; -import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; -import { fieldFormatsMock } from '@kbn/field-formats-plugin/common/mocks'; -import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; -import { getColor } from './get_color'; -import { createMockVisData, createMockBucketColumns, createMockPieParams } from '../../mocks'; -import { generateFormatters } from '../formatters'; -import { ChartTypes } from '../../../common/types'; - -const visData = createMockVisData(); -const buckets = createMockBucketColumns(); -const visParams = createMockPieParams(); -const colors = ['color1', 'color2', 'color3', 'color4']; -const dataMock = dataPluginMock.createStartContract(); -interface RangeProps { - gte: number; - lt: number; -} -const defaultFormatter = jest.fn((...args) => fieldFormatsMock.deserialize(...args)); -const formatters = generateFormatters(visData, defaultFormatter); -dataMock.fieldFormats = { - deserialize: jest.fn(() => ({ - convert: jest.fn((s: RangeProps) => { - return `≥ ${s.gte} and < ${s.lt}`; - }), - })), -} as unknown as DataPublicPluginStart['fieldFormats']; +import { ArrayEntry, ArrayNode } from '@elastic/charts'; +import { fieldFormatsMock } from '@kbn/field-formats-plugin/common/mocks'; +import { BucketColumns, ChartTypes } from '../../../common/types'; +import { createMockPieParams, createMockVisData } from '../../mocks'; +import { getPaletteRegistry } from '../../__mocks__/palettes'; +import { getLayers } from './get_layers'; -export const getPaletteRegistry = () => { - const mockPalette1: jest.Mocked = { - id: 'default', - title: 'My Palette', - getCategoricalColor: jest.fn((layer: SeriesLayer[]) => colors[layer[0].rankAtDepth]), - getCategoricalColors: jest.fn((num: number) => colors), - toExpression: jest.fn(() => ({ - type: 'expression', - chain: [ - { - type: 'function', - function: 'system_palette', - arguments: { - name: ['default'], +describe('getLayers', () => { + it('preserves slice order for multi-metric layer', () => { + const visData = createMockVisData(); + const columns: BucketColumns[] = [ + { + id: 'col-0-0', + name: 'Normal column', + meta: { type: 'murmur3' }, + }, + { + id: 'col-0-0', + name: 'multi-metric column', + meta: { + type: 'number', + sourceParams: { + consolidatedMetricsColumn: true, }, }, - ], - })), - }; - - return { - get: () => mockPalette1, - getAll: () => [mockPalette1], - }; -}; - -describe('computeColor', () => { - it('should return the correct color based on the parent sortIndex', () => { - const d = { - dataName: 'ES-Air', - depth: 1, - sortIndex: 0, - parent: { - children: [['ES-Air'], ['Kibana Airlines']], - depth: 0, - sortIndex: 0, }, - } as unknown as ShapeTreeNode; - const color = getColor( + ]; + const visParams = createMockPieParams(); + const layers = getLayers( ChartTypes.PIE, - d, - 0, - false, - {}, - buckets, - visData.rows, + columns, visParams, - getPaletteRegistry(), - { getColor: () => undefined }, - false, - false, - dataMock.fieldFormats, - visData.columns[0], - formatters - ); - expect(color).toEqual(colors[0]); - }); - - it('slices with the same label should have the same color for small multiples', () => { - const d = { - dataName: 'ES-Air', - depth: 1, - sortIndex: 0, - parent: { - children: [['ES-Air'], ['Kibana Airlines']], - depth: 0, - sortIndex: 0, - }, - } as unknown as ShapeTreeNode; - const color = getColor( - ChartTypes.PIE, - d, - 0, - true, + visData, {}, - buckets, - visData.rows, - visParams, - getPaletteRegistry(), - { getColor: () => undefined }, - false, - false, - dataMock.fieldFormats, - visData.columns[0], - formatters - ); - expect(color).toEqual('color3'); - }); - it('returns the overwriteColor if exists', () => { - const d = { - dataName: 'ES-Air', - depth: 1, - sortIndex: 0, - parent: { - children: [['ES-Air'], ['Kibana Airlines']], - depth: 0, - sortIndex: 0, - }, - } as unknown as ShapeTreeNode; - const color = getColor( - ChartTypes.PIE, - d, - 0, - true, - { 'ES-Air': '#000028' }, - buckets, - visData.rows, - visParams, + [], getPaletteRegistry(), - { getColor: () => undefined }, - false, + {}, + fieldFormatsMock, false, - dataMock.fieldFormats, - visData.columns[0], - formatters + false ); - expect(color).toEqual('#000028'); - }); - it('returns the overwriteColor for older visualizations with formatted values', () => { - const d = { - dataName: { - gte: 1000, - lt: 2000, - }, - depth: 1, - sortIndex: 0, - parent: { - children: [ - [ - { - gte: 1000, - lt: 2000, - }, - ], - [ - { - gte: 2000, - lt: 3000, - }, - ], - ], - depth: 0, - sortIndex: 0, - }, - } as unknown as ShapeTreeNode; - const visParamsNew = { - ...visParams, - distinctColors: true, - }; - const column = { - ...visData.columns[0], - format: { - id: 'range', - params: { - id: 'number', - }, - }, - }; - const color = getColor( - ChartTypes.PIE, - d, - 0, - true, - { '≥ 1000 and < 2000': '#3F6833' }, - buckets, - visData.rows, - visParamsNew, - getPaletteRegistry(), - { getColor: () => undefined }, - false, - false, - dataMock.fieldFormats, - column, - formatters - ); - expect(color).toEqual('#3F6833'); - }); + expect(layers[0].sortPredicate).toBeUndefined(); + expect(layers[1].sortPredicate).toBeDefined(); - it('should only pass the second layer for mosaic', () => { - const d = { - dataName: 'Second level 1', - depth: 2, - sortIndex: 0, - parent: { - children: [['Second level 1'], ['Second level 2']], - depth: 1, - sortIndex: 0, - parent: { - children: [['First level']], - depth: 0, - sortIndex: 0, - }, - }, - } as unknown as ShapeTreeNode; - const registry = getPaletteRegistry(); - getColor( - ChartTypes.MOSAIC, - d, - 1, - true, - {}, - buckets, - visData.rows, - visParams, - registry, - undefined, - true, - false, - dataMock.fieldFormats, - visData.columns[0], - formatters - ); - expect(registry.get().getCategoricalColor).toHaveBeenCalledWith( - [expect.objectContaining({ name: 'Second level 1' })], - expect.anything(), - expect.anything() - ); - }); + const testNodes: ArrayEntry[] = [ + [ + '', + { + value: 2, + inputIndex: [3], + } as ArrayNode, + ], + [ + '', + { + value: 1, + inputIndex: [2], + } as ArrayNode, + ], - it('should only pass the first layer for treemap', () => { - const d = { - dataName: 'Second level 1', - depth: 2, - sortIndex: 0, - parent: { - children: [['Second level 1'], ['Second level 2']], - depth: 1, - sortIndex: 0, - parent: { - children: [['First level']], - depth: 0, - sortIndex: 0, - }, - }, - } as unknown as ShapeTreeNode; - const registry = getPaletteRegistry(); - getColor( - ChartTypes.TREEMAP, - d, - 1, - true, - {}, - buckets, - visData.rows, - visParams, - registry, - undefined, - true, - false, - dataMock.fieldFormats, - visData.columns[0], - formatters - ); - expect(registry.get().getCategoricalColor).toHaveBeenCalledWith( - [expect.objectContaining({ name: 'First level' })], - expect.anything(), - expect.anything() - ); + [ + '', + { + value: 3, + inputIndex: [1], + } as ArrayNode, + ], + ]; + + const predicate = layers[1].sortPredicate!; + testNodes.sort(predicate); + + expect(testNodes).toMatchInlineSnapshot(` + Array [ + Array [ + "", + Object { + "inputIndex": Array [ + 1, + ], + "value": 3, + }, + ], + Array [ + "", + Object { + "inputIndex": Array [ + 2, + ], + "value": 1, + }, + ], + Array [ + "", + Object { + "inputIndex": Array [ + 3, + ], + "value": 2, + }, + ], + ] + `); }); }); diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.ts b/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.ts index 646a0e4b45d2e..93a9cc2b6c7d6 100644 --- a/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.ts +++ b/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.ts @@ -12,7 +12,7 @@ import { FieldFormat } from '@kbn/field-formats-plugin/common'; import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; import type { Datatable, DatatableRow } from '@kbn/expressions-plugin/public'; import { BucketColumns, ChartTypes, PartitionVisParams } from '../../../common/types'; -import { sortPredicateByType } from './sort_predicate'; +import { sortPredicateByType, sortPredicateSaveSourceOrder } from './sort_predicate'; import { byDataColorPaletteMap, getColor } from './get_color'; import { getNodeLabel } from './get_node_labels'; @@ -52,7 +52,7 @@ export const getLayers = ( ); } - const sortPredicate = sortPredicateByType(chartType, visParams, visData, columns); + const sortPredicateForType = sortPredicateByType(chartType, visParams, visData, columns); return columns.map((col, layerIndex) => { return { groupByRollup: (d: Datum) => (col.id ? d[col.id] ?? EMPTY_SLICE : col.name), @@ -62,7 +62,9 @@ export const getLayers = ( layerIndex === 0 && chartType === ChartTypes.MOSAIC ? { ...fillLabel, minFontSize: 14, maxFontSize: 14, clipText: true } : fillLabel, - sortPredicate, + sortPredicate: col.meta?.sourceParams?.consolidatedMetricsColumn + ? sortPredicateSaveSourceOrder() + : sortPredicateForType, shape: { fillColor: (d) => getColor( diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/sort_predicate.ts b/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/sort_predicate.ts index 0e4564a9bcfe5..b5992c3b600bf 100644 --- a/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/sort_predicate.ts +++ b/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/sort_predicate.ts @@ -30,7 +30,7 @@ export const extractUniqTermsMap = (dataTable: Datatable, columnId: string) => {} ); -const sortPredicateSaveSourceOrder: SortPredicatePureFn = +export const sortPredicateSaveSourceOrder: SortPredicatePureFn = () => ([, node1], [, node2]) => { const [index1] = node1.inputIndex ?? []; diff --git a/src/plugins/content_management/common/constants.ts b/src/plugins/content_management/common/constants.ts index 5c35a41577b82..90298e1729d89 100644 --- a/src/plugins/content_management/common/constants.ts +++ b/src/plugins/content_management/common/constants.ts @@ -8,4 +8,4 @@ export const PLUGIN_ID = 'contentManagement'; -export const API_ENDPOINT = '/api/content_management'; +export const API_ENDPOINT = '/api/content_management/rpc'; diff --git a/src/plugins/content_management/common/index.ts b/src/plugins/content_management/common/index.ts index 0bc6549a1681b..ac944daf3571b 100644 --- a/src/plugins/content_management/common/index.ts +++ b/src/plugins/content_management/common/index.ts @@ -19,4 +19,7 @@ export type { SearchIn, } from './rpc'; -export { procedureNames, schemas as rpcSchemas } from './rpc'; +export { procedureNames } from './rpc/constants'; + +// intentionally not exporting schemas to not include @kbn/schema in the public bundle +// export { schemas as rpcSchemas } from './rpc'; diff --git a/src/plugins/content_management/common/schemas.ts b/src/plugins/content_management/common/schemas.ts new file mode 100644 index 0000000000000..172e0f4b28662 --- /dev/null +++ b/src/plugins/content_management/common/schemas.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +// exporting schemas separately from the index.ts file to not include @kbn/schema in the public bundle +// should be only used server-side or in jest tests +export { schemas as rpcSchemas } from './rpc'; diff --git a/src/plugins/content_management/public/index.ts b/src/plugins/content_management/public/index.ts index 0bf75e9d1087a..2687287a36c2f 100644 --- a/src/plugins/content_management/public/index.ts +++ b/src/plugins/content_management/public/index.ts @@ -7,6 +7,18 @@ */ import { ContentManagementPlugin } from './plugin'; +export type { CrudClient } from './crud_client'; +export { + ContentClientProvider, + ContentClient, + useCreateContentMutation, + useUpdateContentMutation, + useDeleteContentMutation, + useSearchContentQuery, + useGetContentQuery, + useContentClient, + type QueryOptions, +} from './content_client'; export function plugin() { return new ContentManagementPlugin(); diff --git a/src/plugins/content_management/public/plugin.ts b/src/plugins/content_management/public/plugin.ts index d82e2fadfb3e6..b2798e4224384 100644 --- a/src/plugins/content_management/public/plugin.ts +++ b/src/plugins/content_management/public/plugin.ts @@ -13,8 +13,9 @@ import { SetupDependencies, StartDependencies, } from './types'; -import type { ContentClient } from './content_client'; -import type { ContentTypeRegistry } from './registry'; +import { ContentClient } from './content_client'; +import { ContentTypeRegistry } from './registry'; +import { RpcClient } from './rpc_client'; export class ContentManagementPlugin implements @@ -26,20 +27,17 @@ export class ContentManagementPlugin > { public setup(core: CoreSetup, deps: SetupDependencies) { - // don't actually expose the client and the registry until it is used to avoid increasing bundle size return { registry: {} as ContentTypeRegistry, }; } public start(core: CoreStart, deps: StartDependencies) { - // don't actually expose the client and the registry until it is used to avoid increasing bundle size - // const rpcClient = new RpcClient(core.http); - // const contentTypeRegistry = new ContentTypeRegistry(); - // const contentClient = new ContentClient( - // (contentType) => contentTypeRegistry.get(contentType)?.crud() ?? rpcClient - // ); - // return { client: contentClient, registry: contentTypeRegistry }; - return { client: {} as ContentClient, registry: {} as ContentTypeRegistry }; + const rpcClient = new RpcClient(core.http); + const contentTypeRegistry = new ContentTypeRegistry(); + const contentClient = new ContentClient( + (contentType) => contentTypeRegistry.get(contentType)?.crud ?? rpcClient + ); + return { client: contentClient, registry: contentTypeRegistry }; } } diff --git a/src/plugins/content_management/public/rpc_client/rpc_client.test.ts b/src/plugins/content_management/public/rpc_client/rpc_client.test.ts index 3a76b355795ed..5e75b45be86ec 100644 --- a/src/plugins/content_management/public/rpc_client/rpc_client.test.ts +++ b/src/plugins/content_management/public/rpc_client/rpc_client.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { procedureNames, API_ENDPOINT } from '../../common'; +import { API_ENDPOINT, procedureNames } from '../../common'; import { RpcClient } from './rpc_client'; diff --git a/src/plugins/content_management/public/rpc_client/rpc_client.ts b/src/plugins/content_management/public/rpc_client/rpc_client.ts index 2d3e6d1ed1cce..9ec27342d45f2 100644 --- a/src/plugins/content_management/public/rpc_client/rpc_client.ts +++ b/src/plugins/content_management/public/rpc_client/rpc_client.ts @@ -18,36 +18,44 @@ import type { ProcedureName, } from '../../common'; import type { CrudClient } from '../crud_client/crud_client'; +import type { + GetResponse, + BulkGetResponse, + CreateItemResponse, + DeleteItemResponse, + UpdateItemResponse, + SearchResponse, +} from '../../server/core/crud'; export class RpcClient implements CrudClient { constructor(private http: { post: HttpSetup['post'] }) {} public get(input: I): Promise { - return this.sendMessage('get', input); + return this.sendMessage>('get', input).then((r) => r.item); } public bulkGet(input: I): Promise { - return this.sendMessage('bulkGet', input); + return this.sendMessage>('bulkGet', input).then((r) => r.items); } public create(input: I): Promise { - return this.sendMessage('create', input); + return this.sendMessage>('create', input).then((r) => r.result); } public update(input: I): Promise { - return this.sendMessage('update', input); + return this.sendMessage>('update', input).then((r) => r.result); } public delete(input: I): Promise { - return this.sendMessage('delete', input); + return this.sendMessage('delete', input).then((r) => r.result); } public search(input: I): Promise { - return this.sendMessage('search', input); + return this.sendMessage('search', input).then((r) => r.result); } - private sendMessage = async (name: ProcedureName, input: any): Promise => { - const { result } = await this.http.post<{ result: any }>(`${API_ENDPOINT}/${name}`, { + private sendMessage = async (name: ProcedureName, input: any): Promise => { + const { result } = await this.http.post<{ result: O }>(`${API_ENDPOINT}/${name}`, { body: JSON.stringify(input), }); return result; diff --git a/src/plugins/content_management/server/core/crud.ts b/src/plugins/content_management/server/core/crud.ts index cde3af9f2c2a2..1ac35769d0444 100644 --- a/src/plugins/content_management/server/core/crud.ts +++ b/src/plugins/content_management/server/core/crud.ts @@ -10,7 +10,7 @@ import type { ContentStorage, StorageContext } from './types'; export interface GetResponse { contentTypeId: string; - item?: T; + item: T; } export interface BulkGetResponse { @@ -33,6 +33,11 @@ export interface DeleteItemResponse { result: T; } +export interface SearchResponse { + contentTypeId: string; + result: T; +} + export class ContentCrud implements ContentStorage { private storage: ContentStorage; private eventBus: EventBus; @@ -245,7 +250,7 @@ export class ContentCrud implements ContentStorage { ctx: StorageContext, query: Query, options?: Options - ): Promise> { + ): Promise> { this.eventBus.emit({ type: 'searchItemStart', contentTypeId: this.contentTypeId, diff --git a/src/plugins/content_management/server/index.ts b/src/plugins/content_management/server/index.ts index e56a3b845fa93..659b59ed6880c 100644 --- a/src/plugins/content_management/server/index.ts +++ b/src/plugins/content_management/server/index.ts @@ -14,3 +14,4 @@ export function plugin(initializerContext: PluginInitializerContext) { } export type { ContentManagementServerSetup, ContentManagementServerStart } from './types'; +export type { ContentStorage, StorageContext } from './core'; diff --git a/src/plugins/content_management/server/rpc/procedures/bulk_get.ts b/src/plugins/content_management/server/rpc/procedures/bulk_get.ts index ee8c27271abfd..83de5d7fcedf4 100644 --- a/src/plugins/content_management/server/rpc/procedures/bulk_get.ts +++ b/src/plugins/content_management/server/rpc/procedures/bulk_get.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { rpcSchemas } from '../../../common'; +import { rpcSchemas } from '../../../common/schemas'; import type { BulkGetIn } from '../../../common'; import type { StorageContext, ContentCrud } from '../../core'; import type { ProcedureDefinition } from '../rpc_service'; diff --git a/src/plugins/content_management/server/rpc/procedures/create.ts b/src/plugins/content_management/server/rpc/procedures/create.ts index fe2f4e0ab7d16..9ff7a55fe560c 100644 --- a/src/plugins/content_management/server/rpc/procedures/create.ts +++ b/src/plugins/content_management/server/rpc/procedures/create.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { rpcSchemas } from '../../../common'; +import { rpcSchemas } from '../../../common/schemas'; import type { CreateIn } from '../../../common'; import type { StorageContext, ContentCrud } from '../../core'; import type { ProcedureDefinition } from '../rpc_service'; diff --git a/src/plugins/content_management/server/rpc/procedures/delete.ts b/src/plugins/content_management/server/rpc/procedures/delete.ts index c10cc02356beb..6260b54468039 100644 --- a/src/plugins/content_management/server/rpc/procedures/delete.ts +++ b/src/plugins/content_management/server/rpc/procedures/delete.ts @@ -5,7 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import { rpcSchemas } from '../../../common'; +import { rpcSchemas } from '../../../common/schemas'; import type { DeleteIn } from '../../../common'; import type { StorageContext, ContentCrud } from '../../core'; import type { ProcedureDefinition } from '../rpc_service'; diff --git a/src/plugins/content_management/server/rpc/procedures/get.ts b/src/plugins/content_management/server/rpc/procedures/get.ts index 73960ad8f3a2f..536263b0e9d15 100644 --- a/src/plugins/content_management/server/rpc/procedures/get.ts +++ b/src/plugins/content_management/server/rpc/procedures/get.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { rpcSchemas } from '../../../common'; +import { rpcSchemas } from '../../../common/schemas'; import type { GetIn } from '../../../common'; import type { ContentCrud, StorageContext } from '../../core'; import { validate } from '../../utils'; diff --git a/src/plugins/content_management/server/rpc/procedures/search.ts b/src/plugins/content_management/server/rpc/procedures/search.ts index 4c9d9a4a9b4c9..c0fa0b2c807dc 100644 --- a/src/plugins/content_management/server/rpc/procedures/search.ts +++ b/src/plugins/content_management/server/rpc/procedures/search.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { rpcSchemas } from '../../../common'; +import { rpcSchemas } from '../../../common/schemas'; import type { SearchIn } from '../../../common'; import type { StorageContext, ContentCrud } from '../../core'; import type { ProcedureDefinition } from '../rpc_service'; diff --git a/src/plugins/content_management/server/rpc/procedures/update.ts b/src/plugins/content_management/server/rpc/procedures/update.ts index 4a9a7951079e5..9ea3b0487d58b 100644 --- a/src/plugins/content_management/server/rpc/procedures/update.ts +++ b/src/plugins/content_management/server/rpc/procedures/update.ts @@ -5,7 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import { rpcSchemas } from '../../../common'; +import { rpcSchemas } from '../../../common/schemas'; import type { UpdateIn } from '../../../common'; import type { StorageContext, ContentCrud } from '../../core'; import type { ProcedureDefinition } from '../rpc_service'; diff --git a/src/plugins/content_management/server/types.ts b/src/plugins/content_management/server/types.ts index 0f4cc3138b5e9..3d11f487fbe7d 100644 --- a/src/plugins/content_management/server/types.ts +++ b/src/plugins/content_management/server/types.ts @@ -6,11 +6,13 @@ * Side Public License, v 1. */ +import { CoreApi } from './core'; + // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface SetupDependencies {} // eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface ContentManagementServerSetup {} +export interface ContentManagementServerSetup extends CoreApi {} // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface ContentManagementServerStart {} diff --git a/src/plugins/content_management/tsconfig.json b/src/plugins/content_management/tsconfig.json index dfdb4b0f93f2a..a5316fbfac1c1 100644 --- a/src/plugins/content_management/tsconfig.json +++ b/src/plugins/content_management/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "outDir": "target/types", }, - "include": ["common/**/*", "public/**/*", "server/**/*", "demo/**/*"], + "include": ["common/**/*", "public/**/*", "server/**/*"], "kbn_references": [ "@kbn/core", "@kbn/config-schema", diff --git a/src/plugins/dashboard/kibana.jsonc b/src/plugins/dashboard/kibana.jsonc index ae5194d662c2b..aba26ca66ed7d 100644 --- a/src/plugins/dashboard/kibana.jsonc +++ b/src/plugins/dashboard/kibana.jsonc @@ -16,6 +16,8 @@ "inspector", "navigation", "savedObjects", + "savedObjectsFinder", + "savedObjectsManagement", "share", "screenshotMode", "uiActions", diff --git a/src/plugins/dashboard/public/dashboard_actions/index.ts b/src/plugins/dashboard/public/dashboard_actions/index.ts index 652b66b2ad195..199854710f1f2 100644 --- a/src/plugins/dashboard/public/dashboard_actions/index.ts +++ b/src/plugins/dashboard/public/dashboard_actions/index.ts @@ -8,7 +8,7 @@ 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'; +import { getSavedObjectFinder } from '@kbn/saved-objects-finder-plugin/public'; import { ExportCSVAction } from './export_csv_action'; import { ClonePanelAction } from './clone_panel_action'; @@ -33,13 +33,13 @@ export const buildAllDashboardActions = async ({ allowByValueEmbeddables, }: BuildAllDashboardActionsProps) => { const { uiSettings } = core; - const { uiActions, share, presentationUtil } = plugins; + const { uiActions, share, presentationUtil, savedObjectsManagement } = plugins; const clonePanelAction = new ClonePanelAction(core.savedObjects); uiActions.registerAction(clonePanelAction); uiActions.attachAction(CONTEXT_MENU_TRIGGER, clonePanelAction.id); - const SavedObjectFinder = getSavedObjectFinder(uiSettings, core.http); + const SavedObjectFinder = getSavedObjectFinder(uiSettings, core.http, savedObjectsManagement); const changeViewAction = new ReplacePanelAction(SavedObjectFinder); uiActions.registerAction(changeViewAction); uiActions.attachAction(CONTEXT_MENU_TRIGGER, changeViewAction.id); diff --git a/src/plugins/dashboard/public/dashboard_app/url/sync_dashboard_url_state.ts b/src/plugins/dashboard/public/dashboard_app/url/sync_dashboard_url_state.ts index 6d90316098fb5..cbfc7271aef41 100644 --- a/src/plugins/dashboard/public/dashboard_app/url/sync_dashboard_url_state.ts +++ b/src/plugins/dashboard/public/dashboard_app/url/sync_dashboard_url_state.ts @@ -37,28 +37,35 @@ export const isPanelVersionTooOld = (panels: SavedDashboardPanel[]) => { return false; }; +function getPanelsMap(appStateInUrl: SharedDashboardState): DashboardPanelMap | undefined { + if (!appStateInUrl.panels) { + return undefined; + } + + if (appStateInUrl.panels.length === 0) { + return {}; + } + + if (isPanelVersionTooOld(appStateInUrl.panels)) { + pluginServices.getServices().notifications.toasts.addWarning(getPanelTooOldErrorString()); + return undefined; + } + + return convertSavedPanelsToPanelMap(appStateInUrl.panels); +} + /** * Loads any dashboard state from the URL, and removes the state from the URL. */ export const loadAndRemoveDashboardState = ( kbnUrlStateStorage: IKbnUrlStateStorage ): Partial => { - const { - notifications: { toasts }, - } = pluginServices.getServices(); const rawAppStateInUrl = kbnUrlStateStorage.get( DASHBOARD_STATE_STORAGE_KEY ); if (!rawAppStateInUrl) return {}; - let panelsMap: DashboardPanelMap | undefined; - if (rawAppStateInUrl.panels && rawAppStateInUrl.panels.length > 0) { - if (isPanelVersionTooOld(rawAppStateInUrl.panels)) { - toasts.addWarning(getPanelTooOldErrorString()); - } else { - panelsMap = convertSavedPanelsToPanelMap(rawAppStateInUrl.panels); - } - } + const panelsMap = getPanelsMap(rawAppStateInUrl); const nextUrl = replaceUrlHashQuery(window.location.href, (hashQuery) => { delete hashQuery[DASHBOARD_STATE_STORAGE_KEY]; diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/api/add_panel_from_library.ts b/src/plugins/dashboard/public/dashboard_container/embeddable/api/add_panel_from_library.ts index 79a3caeafba17..06389afc2a576 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/api/add_panel_from_library.ts +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/api/add_panel_from_library.ts @@ -8,7 +8,7 @@ import { HttpStart } from '@kbn/core/public'; import { isErrorEmbeddable, openAddPanelFlyout } from '@kbn/embeddable-plugin/public'; -import { getSavedObjectFinder } from '@kbn/saved-objects-plugin/public'; +import { getSavedObjectFinder } from '@kbn/saved-objects-finder-plugin/public'; import { pluginServices } from '../../../services/plugin_services'; import { DashboardContainer } from '../dashboard_container'; @@ -21,12 +21,17 @@ export function addFromLibrary(this: DashboardContainer) { settings: { uiSettings, theme }, embeddable: { getEmbeddableFactories, getEmbeddableFactory }, http, + savedObjectsManagement, } = pluginServices.getServices(); if (isErrorEmbeddable(this)) return; this.openOverlay( openAddPanelFlyout({ - SavedObjectFinder: getSavedObjectFinder(uiSettings, http as HttpStart), + SavedObjectFinder: getSavedObjectFinder( + uiSettings, + http as HttpStart, + savedObjectsManagement + ), reportUiCounter: usageCollection.reportUiCounter, getAllFactories: getEmbeddableFactories, getFactory: getEmbeddableFactory, diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/integrations/diff_state/dashboard_diffing_functions.ts b/src/plugins/dashboard/public/dashboard_container/embeddable/integrations/diff_state/dashboard_diffing_functions.ts index c4886c55d976c..74f852df484e4 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/integrations/diff_state/dashboard_diffing_functions.ts +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/integrations/diff_state/dashboard_diffing_functions.ts @@ -15,6 +15,7 @@ import { isFilterPinned, onlyDisabledFiltersChanged, } from '@kbn/es-query'; +import { shouldRefreshFilterCompareOptions } from '@kbn/embeddable-plugin/public'; import { DashboardContainer } from '../../dashboard_container'; import { DashboardContainerByValueInput } from '../../../../../common'; @@ -117,12 +118,6 @@ export const unsavedChangesDiffingFunctions: DashboardDiffFunctions = { viewMode: () => false, // When compared view mode is always considered unequal so that it gets backed up. }; -const shouldRefreshFilterCompareOptions = { - ...COMPARE_ALL_OPTIONS, - // do not compare $state to avoid refreshing when filter is pinned/unpinned (which does not impact results) - state: false, -}; - export const shouldRefreshDiffingFunctions: DashboardDiffFunctions = { ...unsavedChangesDiffingFunctions, filters: ({ currentValue, lastValue }) => diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index cb13afd4aabf7..3c4031072ca92 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -50,6 +50,7 @@ import type { UrlForwardingSetup, UrlForwardingStart } from '@kbn/url-forwarding import type { SavedObjectTaggingOssPluginStart } from '@kbn/saved-objects-tagging-oss-plugin/public'; import { CustomBrandingStart } from '@kbn/core-custom-branding-browser'; +import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public'; import { DashboardContainerFactoryDefinition } from './dashboard_container/embeddable/dashboard_container_factory'; import { type DashboardAppLocator, @@ -90,6 +91,7 @@ export interface DashboardStartDependencies { presentationUtil: PresentationUtilPluginStart; savedObjects: SavedObjectsStart; savedObjectsClient: SavedObjectsClientContract; + savedObjectsManagement: SavedObjectsManagementPluginStart; savedObjectsTaggingOss?: SavedObjectTaggingOssPluginStart; screenshotMode: ScreenshotModePluginStart; share?: SharePluginStart; diff --git a/src/plugins/dashboard/public/services/plugin_services.stub.ts b/src/plugins/dashboard/public/services/plugin_services.stub.ts index eabe85288687a..f0ec4a91f3fd6 100644 --- a/src/plugins/dashboard/public/services/plugin_services.stub.ts +++ b/src/plugins/dashboard/public/services/plugin_services.stub.ts @@ -39,6 +39,7 @@ import { urlForwardingServiceFactory } from './url_forwarding/url_fowarding.stub import { visualizationsServiceFactory } from './visualizations/visualizations.stub'; import { dashboardSavedObjectServiceFactory } from './dashboard_saved_object/dashboard_saved_object.stub'; import { customBrandingServiceFactory } from './custom_branding/custom_branding.stub'; +import { savedObjectsManagementServiceFactory } from './saved_objects_management/saved_objects_management_service.stub'; export const providers: PluginServiceProviders = { dashboardSavedObject: new PluginServiceProvider(dashboardSavedObjectServiceFactory), @@ -66,6 +67,7 @@ export const providers: PluginServiceProviders = { usageCollection: new PluginServiceProvider(usageCollectionServiceFactory), visualizations: new PluginServiceProvider(visualizationsServiceFactory), customBranding: new PluginServiceProvider(customBrandingServiceFactory), + savedObjectsManagement: new PluginServiceProvider(savedObjectsManagementServiceFactory), }; export const registry = new PluginServiceRegistry(providers); diff --git a/src/plugins/dashboard/public/services/plugin_services.ts b/src/plugins/dashboard/public/services/plugin_services.ts index 4382506a37948..599eb6a5b7249 100644 --- a/src/plugins/dashboard/public/services/plugin_services.ts +++ b/src/plugins/dashboard/public/services/plugin_services.ts @@ -40,6 +40,7 @@ import { usageCollectionServiceFactory } from './usage_collection/usage_collecti import { analyticsServiceFactory } from './analytics/analytics_service'; import { dashboardSavedObjectServiceFactory } from './dashboard_saved_object/dashboard_saved_object_service'; import { customBrandingServiceFactory } from './custom_branding/custom_branding_service'; +import { savedObjectsManagementServiceFactory } from './saved_objects_management/saved_objects_management_service'; const providers: PluginServiceProviders = { dashboardSavedObject: new PluginServiceProvider(dashboardSavedObjectServiceFactory, [ @@ -80,6 +81,7 @@ const providers: PluginServiceProviders(); diff --git a/src/plugins/dashboard/public/services/saved_objects_management/saved_objects_management_service.stub.ts b/src/plugins/dashboard/public/services/saved_objects_management/saved_objects_management_service.stub.ts new file mode 100644 index 0000000000000..b5ea444e702a8 --- /dev/null +++ b/src/plugins/dashboard/public/services/saved_objects_management/saved_objects_management_service.stub.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public'; +import { savedObjectsManagementPluginMock } from '@kbn/saved-objects-management-plugin/public/mocks'; + +type SavedObjectsManagementServiceFactory = PluginServiceFactory; + +export const savedObjectsManagementServiceFactory: SavedObjectsManagementServiceFactory = () => { + return savedObjectsManagementPluginMock.createStartContract(); +}; diff --git a/src/plugins/dashboard/public/services/saved_objects_management/saved_objects_management_service.ts b/src/plugins/dashboard/public/services/saved_objects_management/saved_objects_management_service.ts new file mode 100644 index 0000000000000..271a056948687 --- /dev/null +++ b/src/plugins/dashboard/public/services/saved_objects_management/saved_objects_management_service.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 { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public'; +import { DashboardStartDependencies } from '../../plugin'; + +export type SavedObjectsManagementServiceFactory = KibanaPluginServiceFactory< + SavedObjectsManagementPluginStart, + DashboardStartDependencies +>; + +export const savedObjectsManagementServiceFactory: SavedObjectsManagementServiceFactory = ({ + startPlugins, +}) => { + const { savedObjectsManagement } = startPlugins; + + return savedObjectsManagement; +}; diff --git a/src/plugins/dashboard/public/services/types.ts b/src/plugins/dashboard/public/services/types.ts index fc7e0acf1b5c4..1992cd83d4b6d 100644 --- a/src/plugins/dashboard/public/services/types.ts +++ b/src/plugins/dashboard/public/services/types.ts @@ -8,6 +8,7 @@ import { PluginInitializerContext } from '@kbn/core/public'; import { KibanaPluginServiceParams } from '@kbn/presentation-util-plugin/public'; +import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public'; import { DashboardStartDependencies } from '../plugin'; import { DashboardAnalyticsService } from './analytics/types'; @@ -66,4 +67,5 @@ export interface DashboardServices { usageCollection: DashboardUsageCollectionService; // TODO: make this optional in follow up visualizations: DashboardVisualizationsService; customBranding: DashboardCustomBrandingService; + savedObjectsManagement: SavedObjectsManagementPluginStart; } diff --git a/src/plugins/dashboard/tsconfig.json b/src/plugins/dashboard/tsconfig.json index 17ec86d25e6ef..4bfb899c5301d 100644 --- a/src/plugins/dashboard/tsconfig.json +++ b/src/plugins/dashboard/tsconfig.json @@ -53,6 +53,8 @@ "@kbn/core-execution-context-common", "@kbn/core-custom-branding-browser", "@kbn/shared-ux-router", + "@kbn/saved-objects-finder-plugin", + "@kbn/saved-objects-management-plugin", "@kbn/shared-ux-button-toolbar", ], "exclude": [ diff --git a/src/plugins/data/common/search/expressions/esaggs/request_handler.ts b/src/plugins/data/common/search/expressions/esaggs/request_handler.ts index 1497dd9aa1a37..196ebfa55810f 100644 --- a/src/plugins/data/common/search/expressions/esaggs/request_handler.ts +++ b/src/plugins/data/common/search/expressions/esaggs/request_handler.ts @@ -61,13 +61,6 @@ export const handleRequest = ({ searchSource.setField('index', indexPattern); searchSource.setField('size', 0); - // Create a new search source that inherits the original search source - // but has the appropriate timeRange applied via a filter. - // This is a temporary solution until we properly pass down all required - // information for the request to the request handler (https://github.com/elastic/kibana/issues/16641). - // Using callParentStartHandlers: true we make sure, that the parent searchSource - // onSearchRequestStart will be called properly even though we use an inherited - // search source. const timeFilterSearchSource = searchSource.createChild({ callParentStartHandlers: true }); const requestSearchSource = timeFilterSearchSource.createChild({ callParentStartHandlers: true, diff --git a/src/plugins/discover/public/application/main/components/sidebar/discover_field.test.tsx b/src/plugins/discover/public/application/main/components/sidebar/discover_field.test.tsx index 88a5660db3017..eb433bb6ee7c3 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/discover_field.test.tsx +++ b/src/plugins/discover/public/application/main/components/sidebar/discover_field.test.tsx @@ -136,7 +136,8 @@ async function getComponent({ return { comp, props }; } -describe('discover sidebar field', function () { +// FLAKY: https://github.com/elastic/kibana/issues/148349 +describe.skip('discover sidebar field', function () { beforeEach(() => { (DetailsUtil.getDetails as jest.Mock).mockClear(); }); diff --git a/src/plugins/embeddable/kibana.jsonc b/src/plugins/embeddable/kibana.jsonc index 89aa3e41026a6..b7a164b459d77 100644 --- a/src/plugins/embeddable/kibana.jsonc +++ b/src/plugins/embeddable/kibana.jsonc @@ -10,7 +10,9 @@ "requiredPlugins": [ "data", "inspector", - "uiActions" + "uiActions", + "savedObjectsFinder", + "savedObjectsManagement" ], "requiredBundles": [ "savedObjects", diff --git a/src/plugins/embeddable/public/index.ts b/src/plugins/embeddable/public/index.ts index ff41aa5503e33..18e5484b8fa4d 100644 --- a/src/plugins/embeddable/public/index.ts +++ b/src/plugins/embeddable/public/index.ts @@ -87,6 +87,8 @@ export { EmbeddableRenderer, useEmbeddableFactory, isFilterableEmbeddable, + shouldFetch$, + shouldRefreshFilterCompareOptions, } from './lib'; export { AttributeService, ATTRIBUTE_SERVICE_KEY } from './lib/attribute_service'; diff --git a/src/plugins/embeddable/public/lib/filterable_embeddable/index.ts b/src/plugins/embeddable/public/lib/filterable_embeddable/index.ts index ffa9470a34af4..0a01b6cab39df 100644 --- a/src/plugins/embeddable/public/lib/filterable_embeddable/index.ts +++ b/src/plugins/embeddable/public/lib/filterable_embeddable/index.ts @@ -8,3 +8,4 @@ export type { FilterableEmbeddable } from './types'; export { isFilterableEmbeddable } from './types'; +export { shouldFetch$, shouldRefreshFilterCompareOptions } from './should_fetch'; diff --git a/src/plugins/embeddable/public/lib/filterable_embeddable/should_fetch.tsx b/src/plugins/embeddable/public/lib/filterable_embeddable/should_fetch.tsx new file mode 100644 index 0000000000000..cf9cba103e011 --- /dev/null +++ b/src/plugins/embeddable/public/lib/filterable_embeddable/should_fetch.tsx @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 fastIsEqual from 'fast-deep-equal'; +import { Observable } from 'rxjs'; +import { map, distinctUntilChanged, skip, startWith } from 'rxjs/operators'; +import { COMPARE_ALL_OPTIONS, onlyDisabledFiltersChanged } from '@kbn/es-query'; +import type { FilterableEmbeddableInput } from './types'; + +export const shouldRefreshFilterCompareOptions = { + ...COMPARE_ALL_OPTIONS, + // do not compare $state to avoid refreshing when filter is pinned/unpinned (which does not impact results) + state: false, +}; + +export function shouldFetch$< + TFilterableEmbeddableInput extends FilterableEmbeddableInput = FilterableEmbeddableInput +>( + updated$: Observable, + getInput: () => TFilterableEmbeddableInput +): Observable { + return updated$.pipe(map(() => getInput())).pipe( + // wrapping distinctUntilChanged with startWith and skip to prime distinctUntilChanged with an initial input value. + startWith(getInput()), + distinctUntilChanged((a: TFilterableEmbeddableInput, b: TFilterableEmbeddableInput) => { + if ( + !fastIsEqual( + [a.searchSessionId, a.query, a.timeRange, a.timeslice], + [b.searchSessionId, b.query, b.timeRange, b.timeslice] + ) + ) { + return false; + } + + return onlyDisabledFiltersChanged(a.filters, b.filters, shouldRefreshFilterCompareOptions); + }), + skip(1) + ); +} diff --git a/src/plugins/embeddable/public/lib/filterable_embeddable/types.ts b/src/plugins/embeddable/public/lib/filterable_embeddable/types.ts index 8fe2b85e02ada..2b6182b1b95db 100644 --- a/src/plugins/embeddable/public/lib/filterable_embeddable/types.ts +++ b/src/plugins/embeddable/public/lib/filterable_embeddable/types.ts @@ -6,7 +6,15 @@ * Side Public License, v 1. */ -import { type AggregateQuery, type Filter, type Query } from '@kbn/es-query'; +import type { AggregateQuery, Filter, Query, TimeRange } from '@kbn/es-query'; +import { EmbeddableInput } from '../embeddables'; + +export type FilterableEmbeddableInput = EmbeddableInput & { + filters?: Filter[]; + query?: Query; + timeRange?: TimeRange; + timeslice?: [number, number]; +}; /** * All embeddables that implement this interface should support being filtered diff --git a/src/plugins/embeddable/public/mocks.tsx b/src/plugins/embeddable/public/mocks.tsx index 25ccb141cd356..478845cf66f6d 100644 --- a/src/plugins/embeddable/public/mocks.tsx +++ b/src/plugins/embeddable/public/mocks.tsx @@ -14,6 +14,11 @@ import { type AggregateQuery, type Filter, type Query } from '@kbn/es-query'; import { inspectorPluginMock } from '@kbn/inspector-plugin/public/mocks'; import { uiActionsPluginMock } from '@kbn/ui-actions-plugin/public/mocks'; +import { + SavedObjectManagementTypeInfo, + SavedObjectsManagementPluginStart, +} from '@kbn/saved-objects-management-plugin/public'; +import { SavedObjectsTaggingApi } from '@kbn/saved-objects-tagging-oss-plugin/public'; import { UiActionsService } from './lib/ui_actions'; import { EmbeddablePublicPlugin } from './plugin'; import { @@ -156,10 +161,28 @@ const createInstance = (setupPlugins: Partial = {}) const setup = plugin.setup(coreMock.createSetup(), { uiActions: setupPlugins.uiActions || uiActionsPluginMock.createSetupContract(), }); + const savedObjectsManagementMock = { + parseQuery: (query: Query, types: SavedObjectManagementTypeInfo[]) => { + return { + queryText: 'some search', + }; + }, + getTagFindReferences: ({ + selectedTags, + taggingApi, + }: { + selectedTags?: string[]; + taggingApi?: SavedObjectsTaggingApi; + }) => { + return undefined; + }, + }; const doStart = (startPlugins: Partial = {}) => plugin.start(coreMock.createStart(), { uiActions: startPlugins.uiActions || uiActionsPluginMock.createStartContract(), inspector: inspectorPluginMock.createStartContract(), + savedObjectsManagement: + savedObjectsManagementMock as unknown as SavedObjectsManagementPluginStart, }); return { plugin, diff --git a/src/plugins/embeddable/public/plugin.tsx b/src/plugins/embeddable/public/plugin.tsx index 1ea92fe129ccf..b064ae3f77592 100644 --- a/src/plugins/embeddable/public/plugin.tsx +++ b/src/plugins/embeddable/public/plugin.tsx @@ -11,7 +11,7 @@ import { Subscription } from 'rxjs'; import { identity } from 'lodash'; import { UI_SETTINGS } from '@kbn/data-plugin/public'; import type { SerializableRecord } from '@kbn/utility-types'; -import { getSavedObjectFinder } from '@kbn/saved-objects-plugin/public'; +import { getSavedObjectFinder } from '@kbn/saved-objects-finder-plugin/public'; import { UiActionsSetup, UiActionsStart } from '@kbn/ui-actions-plugin/public'; import { Start as InspectorStart } from '@kbn/inspector-plugin/public'; import { @@ -23,6 +23,7 @@ import { } from '@kbn/core/public'; import { Storage } from '@kbn/kibana-utils-plugin/public'; import { migrateToLatest, PersistableStateService } from '@kbn/kibana-utils-plugin/common'; +import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public'; import { EmbeddableFactoryRegistry, EmbeddableFactoryProvider, @@ -64,6 +65,7 @@ export interface EmbeddableSetupDependencies { export interface EmbeddableStartDependencies { uiActions: UiActionsStart; inspector: InspectorStart; + savedObjectsManagement: SavedObjectsManagementPluginStart; } export interface EmbeddableSetup { @@ -143,7 +145,7 @@ export class EmbeddablePublicPlugin implements Plugin { this.embeddableFactories.set( @@ -207,7 +209,11 @@ export class EmbeddablePublicPlugin implements Plugin diff --git a/src/plugins/embeddable/public/tests/test_plugin.ts b/src/plugins/embeddable/public/tests/test_plugin.ts index 6587d42dfda12..09412d973ec78 100644 --- a/src/plugins/embeddable/public/tests/test_plugin.ts +++ b/src/plugins/embeddable/public/tests/test_plugin.ts @@ -11,8 +11,13 @@ import { UiActionsStart } from '@kbn/ui-actions-plugin/public'; import { uiActionsPluginMock } from '@kbn/ui-actions-plugin/public/mocks'; import { inspectorPluginMock } from '@kbn/inspector-plugin/public/mocks'; import { coreMock } from '@kbn/core/public/mocks'; +import { + SavedObjectManagementTypeInfo, + SavedObjectsManagementPluginStart, +} from '@kbn/saved-objects-management-plugin/public'; +import { Query } from '@kbn/es-query'; +import { SavedObjectsTaggingApi } from '@kbn/saved-objects-tagging-oss-plugin/public'; import { EmbeddablePublicPlugin, EmbeddableSetup, EmbeddableStart } from '../plugin'; - export interface TestPluginReturn { plugin: EmbeddablePublicPlugin; coreSetup: CoreSetup; @@ -32,6 +37,22 @@ export const testPlugin = ( const setup = plugin.setup(coreSetup, { uiActions: uiActions.setup, }); + const savedObjectsManagementMock = { + parseQuery: (query: Query, types: SavedObjectManagementTypeInfo[]) => { + return { + queryText: 'some search', + }; + }, + getTagFindReferences: ({ + selectedTags, + taggingApi, + }: { + selectedTags?: string[]; + taggingApi?: SavedObjectsTaggingApi; + }) => { + return undefined; + }, + }; return { plugin, @@ -42,6 +63,8 @@ export const testPlugin = ( const start = plugin.start(anotherCoreStart, { inspector: inspectorPluginMock.createStartContract(), uiActions: uiActionsPluginMock.createStartContract(), + savedObjectsManagement: + savedObjectsManagementMock as unknown as SavedObjectsManagementPluginStart, }); return start; }, diff --git a/src/plugins/embeddable/tsconfig.json b/src/plugins/embeddable/tsconfig.json index 67102414915d9..6f8a146187b2a 100644 --- a/src/plugins/embeddable/tsconfig.json +++ b/src/plugins/embeddable/tsconfig.json @@ -30,6 +30,9 @@ "@kbn/data-plugin", "@kbn/core-overlays-browser-mocks", "@kbn/core-theme-browser-mocks", + "@kbn/saved-objects-management-plugin", + "@kbn/saved-objects-tagging-oss-plugin", + "@kbn/saved-objects-finder-plugin", ], "exclude": [ "target/**/*", diff --git a/src/plugins/files/server/usage/integration_tests/usage.test.ts b/src/plugins/files/server/usage/integration_tests/usage.test.ts index 0bccb869c822e..02cc7dfee55fa 100644 --- a/src/plugins/files/server/usage/integration_tests/usage.test.ts +++ b/src/plugins/files/server/usage/integration_tests/usage.test.ts @@ -44,9 +44,11 @@ describe('Files usage telemetry', () => { request.post(root, `/api/files/shares/${fileKind}/${file3.id}`).send({}).expect(200), ]); - const { body } = await request.get(root, `/api/stats?extended=true&legacy=true`); + const { body } = await request + .post(root, '/api/telemetry/v2/clusters/_stats') + .send({ unencrypted: true }); - expect(body.usage.files).toMatchInlineSnapshot(` + expect(body[0].stats.stack_stats.kibana.plugins.files).toMatchInlineSnapshot(` Object { "countByExtension": Array [ Object { diff --git a/src/plugins/guided_onboarding/README.md b/src/plugins/guided_onboarding/README.md index 8f9a428beb1fd..7dbf443f7f86e 100755 --- a/src/plugins/guided_onboarding/README.md +++ b/src/plugins/guided_onboarding/README.md @@ -2,9 +2,11 @@ This plugin contains the code for the Guided Onboarding project. Guided onboarding consists of guides for Solutions (Enterprise Search, Observability, Security) that can be completed as a checklist of steps. The guides help users to ingest their data and to navigate to the correct Solutions pages. -The guided onboarding plugin includes a client-side code for the UI and the server-side code for the internal API. The server-side code is not intended for external use. +The guided onboarding plugin includes a client-side code for the UI and the server-side code for the internal API. -The client-side code registers a button in the Kibana header that controls the guided onboarding panel (checklist) depending on the current state. There is also an API service exposed from the client-side start contract. The API service is intended for external use by other plugins. +The client-side code registers a button in the Kibana header that controls the guided onboarding panel (checklist) depending on the current state. There is also an API service exposed from the client-side start contract. The API service is intended for external use by other plugins that need to react to the guided onboarding state, for example hide or display UI elements if a guide step is active. + +Besides the internal API routes, the server-side code also exposes a function to register guide configs from the server-side setup start contract. This function is intended for external use by any plugin that need to add a new guide or modify an existing one. --- ## Current functionality @@ -106,3 +108,13 @@ The guided onboarding exposes a function `registerGuideConfig(guideId: GuideId, - observability: `x-pack/plugins/observability/server/plugin.ts` - security solution: `x-pack/plugins/security_solution/server/plugin.ts` + +## Adding a new guide +Follow these simple steps to add a new guide to the guided onboarding framework. For more detailed information about framework functionality and architecture and about API services exposed by the plugin, please read the full readme. + +1. Declare the `guidedOnboarding` plugin as a dependency in your plugin's `kibana.json` file. Add the guided onboarding plugin's client-side start contract to your plugin's client-side start dependencies and the guided onboarding plugin's server-side setup contract to your plugin's server-side dependencies. +2. Define the configuration for your guide. At a high level, this includes a title, description, and list of steps. See this [example config](https://github.com/elastic/kibana/blob/main/packages/kbn-guided-onboarding/src/common/test_guide_config.ts) or consult the `GuideConfig` interface. +3. Register your guide during your plugin's server-side setup by calling a function exposed by the guided onboarding plugin: `registerGuideConfig(guideId: GuideId, guideConfig: GuideConfig)`. For an example, see this [example plugin](https://github.com/elastic/kibana/blob/main/examples/guided_onboarding_example/server/plugin.ts). +4. Update the cards on the landing page to include your guide in the use case selection. Make sure that the card doesn't have the property `navigateTo` because that is only used for cards that redirect to Kibana pages and don't start a guide. Also add the same value to the property `guideId` as used in the guide config. Landing page cards are configured in this [kbn-guided-onboarding package](https://github.com/elastic/kibana/blob/main/packages/kbn-guided-onboarding/src/components/landing_page/guide_cards.constants.tsx). +5. Integrate the new guide into your Kibana pages by using the guided onboarding client-side API service. Make sure your Kibana pages correctly display UI elements depending on the active guide step and the UI flow is straight forward to complete the guide. See existing guides for an example and read more about the API service in this file, the section "Client-side: API service". +6. Optionally, update the example plugin's [form](https://github.com/elastic/kibana/blob/main/examples/guided_onboarding_example/public/components/main.tsx#L38) to be able to start your guide from that page and activate any step in your guide (useful to test your guide steps). diff --git a/src/plugins/saved_objects/public/finder/saved_object_finder.tsx b/src/plugins/saved_objects/public/finder/saved_object_finder.tsx index 4c39d02dd55f2..14fc712002abd 100644 --- a/src/plugins/saved_objects/public/finder/saved_object_finder.tsx +++ b/src/plugins/saved_objects/public/finder/saved_object_finder.tsx @@ -27,6 +27,9 @@ import { EuiSpacer, EuiTablePagination, IconType, + EuiFormRow, + EuiFieldSearchProps, + EuiFormRowProps, } from '@elastic/eui'; import { Direction } from '@elastic/eui/src/services/sort/sort_direction'; import { i18n } from '@kbn/i18n'; @@ -80,6 +83,8 @@ interface BaseSavedObjectFinder { noItemsMessage?: React.ReactNode; savedObjectMetaData: Array>; showFilter?: boolean; + euiFormRowProps?: Partial; + euiFieldSearchProps?: EuiFieldSearchProps; } interface SavedObjectFinderFixedPage extends BaseSavedObjectFinder { @@ -110,6 +115,13 @@ class SavedObjectFinderUi extends React.Component< initialPageSize: PropTypes.oneOf([5, 10, 15, 25]), fixedPageSize: PropTypes.number, showFilter: PropTypes.bool, + euiFormRowProps: PropTypes.object, + euiFieldSearchProps: PropTypes.object, + }; + + static defaultProps = { + euiFormRowProps: {}, + euiFieldSearchProps: {}, }; private isComponentMounted: boolean = false; @@ -336,109 +348,114 @@ class SavedObjectFinderUi extends React.Component< const availableSavedObjectMetaData = this.getAvailableSavedObjectMetaData(); return ( - - - { - this.setState( - { - query: e.target.value, - }, - this.fetchItems - ); - }} - data-test-subj="savedObjectFinderSearchInput" - isLoading={this.state.isFetchingItems} - /> - - - - this.setState({ sortOpen: false })} - button={ - - this.setState(({ sortOpen }) => ({ - sortOpen: !sortOpen, - })) - } - iconType="arrowDown" - isSelected={this.state.sortOpen} - data-test-subj="savedObjectFinderSortButton" - > - {i18n.translate('savedObjects.finder.sortButtonLabel', { - defaultMessage: 'Sort', - })} - - } - > - - - {this.props.showFilter && ( + + + + { + this.setState( + { + query: e.target.value, + }, + this.fetchItems + ); + }} + data-test-subj="savedObjectFinderSearchInput" + isLoading={this.state.isFetchingItems} + {...this.props.euiFieldSearchProps} + /> + + + this.setState({ filterOpen: false })} + isOpen={this.state.sortOpen} + closePopover={() => this.setState({ sortOpen: false })} button={ - this.setState(({ filterOpen }) => ({ - filterOpen: !filterOpen, + this.setState(({ sortOpen }) => ({ + sortOpen: !sortOpen, })) } iconType="arrowDown" - data-test-subj="savedObjectFinderFilterButton" - isSelected={this.state.filterOpen} - numFilters={this.props.savedObjectMetaData.length} - hasActiveFilters={this.state.filteredTypes.length > 0} - numActiveFilters={this.state.filteredTypes.length} + isSelected={this.state.sortOpen} + data-test-subj="savedObjectFinderSortButton" > - {i18n.translate('savedObjects.finder.filterButtonLabel', { - defaultMessage: 'Types', + {i18n.translate('savedObjects.finder.sortButtonLabel', { + defaultMessage: 'Sort', })} } > - ( - { - this.setState(({ filteredTypes }) => ({ - filteredTypes: filteredTypes.includes(metaData.type) - ? filteredTypes.filter((t) => t !== metaData.type) - : [...filteredTypes, metaData.type], - page: 0, - })); - }} - > - {metaData.name} - - ))} - /> + - )} - - - {this.props.children ? {this.props.children} : null} - + {this.props.showFilter && ( + this.setState({ filterOpen: false })} + button={ + + this.setState(({ filterOpen }) => ({ + filterOpen: !filterOpen, + })) + } + iconType="arrowDown" + data-test-subj="savedObjectFinderFilterButton" + isSelected={this.state.filterOpen} + numFilters={this.props.savedObjectMetaData.length} + hasActiveFilters={this.state.filteredTypes.length > 0} + numActiveFilters={this.state.filteredTypes.length} + > + {i18n.translate('savedObjects.finder.filterButtonLabel', { + defaultMessage: 'Types', + })} + + } + > + ( + { + this.setState(({ filteredTypes }) => ({ + filteredTypes: filteredTypes.includes(metaData.type) + ? filteredTypes.filter((t) => t !== metaData.type) + : [...filteredTypes, metaData.type], + page: 0, + })); + }} + > + {metaData.name} + + ))} + /> + + )} + + + {this.props.children ? ( + {this.props.children} + ) : null} + + ); } diff --git a/src/plugins/saved_objects_finder/kibana.jsonc b/src/plugins/saved_objects_finder/kibana.jsonc index bfe35d688b9cf..9373ef8d5f9de 100644 --- a/src/plugins/saved_objects_finder/kibana.jsonc +++ b/src/plugins/saved_objects_finder/kibana.jsonc @@ -6,8 +6,6 @@ "id": "savedObjectsFinder", "server": true, "browser": true, - "requiredBundles": [ - "savedObjects" - ] + "requiredBundles": ["savedObjects"] } } diff --git a/src/plugins/saved_objects_finder/public/finder/index.tsx b/src/plugins/saved_objects_finder/public/finder/index.tsx index 0819ebf8141b6..789d09ec520b1 100644 --- a/src/plugins/saved_objects_finder/public/finder/index.tsx +++ b/src/plugins/saved_objects_finder/public/finder/index.tsx @@ -8,6 +8,10 @@ import { EuiDelayRender, EuiLoadingContent } from '@elastic/eui'; import React from 'react'; +import { IUiSettingsClient } from '@kbn/core-ui-settings-browser'; +import { HttpStart } from '@kbn/core-http-browser'; +import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public'; +import { SavedObjectsTaggingApi } from '@kbn/saved-objects-tagging-oss-plugin/public'; import type { SavedObjectFinderProps } from './saved_object_finder'; const LazySavedObjectFinder = React.lazy(() => import('./saved_object_finder')); @@ -23,5 +27,19 @@ const SavedObjectFinder = (props: SavedObjectFinderProps) => ( ); +export const getSavedObjectFinder = ( + uiSettings: IUiSettingsClient, + http: HttpStart, + savedObjectsManagement: SavedObjectsManagementPluginStart, + savedObjectsTagging?: SavedObjectsTaggingApi +) => { + return (props: SavedObjectFinderProps) => ( + + ); +}; + export type { SavedObjectMetaData, SavedObjectFinderProps } from './saved_object_finder'; export { SavedObjectFinder }; diff --git a/src/plugins/saved_objects_finder/public/finder/saved_object_finder.tsx b/src/plugins/saved_objects_finder/public/finder/saved_object_finder.tsx index 570890e015409..2db81716876c9 100644 --- a/src/plugins/saved_objects_finder/public/finder/saved_object_finder.tsx +++ b/src/plugins/saved_objects_finder/public/finder/saved_object_finder.tsx @@ -28,7 +28,7 @@ import { i18n } from '@kbn/i18n'; import type { IUiSettingsClient, HttpStart } from '@kbn/core/public'; import type { SavedObjectsTaggingApi } from '@kbn/saved-objects-tagging-oss-plugin/public'; import { LISTING_LIMIT_SETTING } from '@kbn/saved-objects-plugin/public'; -import { SavedObjectCommon, FindQueryHTTP, FindResponseHTTP } from '../../common'; +import { SavedObjectCommon, FindQueryHTTP, FindResponseHTTP, FinderAttributes } from '../../common'; export interface SavedObjectMetaData { type: string; @@ -41,12 +41,6 @@ export interface SavedObjectMetaData { defaultSearchField?: string; } -interface FinderAttributes { - title?: string; - name?: string; - type: string; -} - interface SavedObjectFinderItem extends SavedObjectCommon { title: string | null; name: string | null; @@ -64,7 +58,7 @@ interface SavedObjectFinderServices { http: HttpStart; uiSettings: IUiSettingsClient; savedObjectsManagement: SavedObjectsManagementPluginStart; - savedObjectsTagging: SavedObjectsTaggingApi | undefined; + savedObjectsTagging?: SavedObjectsTaggingApi; } interface BaseSavedObjectFinder { @@ -109,16 +103,7 @@ export class SavedObjectFinderUi extends React.Component< private debouncedFetch = debounce(async (query: Query) => { const metaDataMap = this.getSavedObjectMetaDataMap(); - const { queryText, visibleTypes, selectedTags } = - this.props.services.savedObjectsManagement.parseQuery( - query, - Object.values(metaDataMap).map((metadata) => ({ - name: metadata.type, - namespaceType: 'single', - hidden: false, - displayName: metadata.name, - })) - ); + const { savedObjectsManagement, uiSettings, http } = this.props.services; const fields = Object.values(metaDataMap) .map((metaData) => metaData.includeFields || []) @@ -131,22 +116,32 @@ export class SavedObjectFinderUi extends React.Component< return col; }, []); - const perPage = this.props.services.uiSettings.get(LISTING_LIMIT_SETTING); - const hasReference = this.props.services.savedObjectsManagement.getTagFindReferences({ + const perPage = uiSettings.get(LISTING_LIMIT_SETTING); + const { queryText, visibleTypes, selectedTags } = savedObjectsManagement.parseQuery( + query, + Object.values(metaDataMap).map((metadata) => ({ + name: metadata.type, + namespaceType: 'single', + hidden: false, + displayName: metadata.name, + })) + ); + const hasReference = savedObjectsManagement.getTagFindReferences({ selectedTags, taggingApi: this.props.services.savedObjectsTagging, }); const params: FindQueryHTTP = { type: visibleTypes ?? Object.keys(metaDataMap), - fields: [...new Set(fields)], search: queryText ? `${queryText}*` : undefined, + fields: [...new Set(fields)], page: 1, perPage, searchFields: ['title^3', 'description', ...additionalSearchFields], defaultSearchOperator: 'AND', hasReference: hasReference ? JSON.stringify(hasReference) : undefined, }; - const response = (await this.props.services.http.get('/internal/saved-objects-finder/find', { + + const response = (await http.get('/internal/saved-objects-finder/find', { query: params as Record, })) as FindResponseHTTP; @@ -156,7 +151,7 @@ export class SavedObjectFinderUi extends React.Component< attributes: { name, title }, } = savedObject; const titleToUse = typeof title === 'string' ? title : ''; - const nameToUse = name && typeof name === 'string' ? name : titleToUse; + const nameToUse = name ? name : titleToUse; return { ...savedObject, version: savedObject.version, @@ -169,9 +164,8 @@ export class SavedObjectFinderUi extends React.Component< const metaData = metaDataMap[savedObject.type]; if (metaData.showSavedObject) { return metaData.showSavedObject(savedObject.simple); - } else { - return true; } + return true; }); if (!this.isComponentMounted) { @@ -289,7 +283,7 @@ export class SavedObjectFinderUi extends React.Component< name: i18n.translate('savedObjectsFinder.titleName', { defaultMessage: 'Title', }), - width: '55%', + width: tagColumn ? '55%' : '100%', description: i18n.translate('savedObjectsFinder.titleDescription', { defaultMessage: 'Title of the saved object', }), @@ -369,6 +363,7 @@ export class SavedObjectFinderUi extends React.Component< itemId="id" items={this.state.items} columns={columns} + data-test-subj="savedObjectsFinderTable" message={this.props.noItemsMessage} search={search} pagination={pagination} diff --git a/src/plugins/saved_objects_finder/public/index.ts b/src/plugins/saved_objects_finder/public/index.ts index b266860185365..ada561d5ccb0d 100644 --- a/src/plugins/saved_objects_finder/public/index.ts +++ b/src/plugins/saved_objects_finder/public/index.ts @@ -8,6 +8,6 @@ import { SavedObjectsFinderPublicPlugin } from './plugin'; export type { SavedObjectMetaData, SavedObjectFinderProps } from './finder'; -export { SavedObjectFinder } from './finder'; +export { SavedObjectFinder, getSavedObjectFinder } from './finder'; export const plugin = () => new SavedObjectsFinderPublicPlugin(); diff --git a/src/plugins/saved_objects_finder/tsconfig.json b/src/plugins/saved_objects_finder/tsconfig.json index 02aa4bf8208ea..8725d23a5ea14 100644 --- a/src/plugins/saved_objects_finder/tsconfig.json +++ b/src/plugins/saved_objects_finder/tsconfig.json @@ -13,6 +13,8 @@ "@kbn/saved-objects-plugin", "@kbn/core-saved-objects-server", "@kbn/config-schema", + "@kbn/core-ui-settings-browser", + "@kbn/core-http-browser", ], "exclude": [ "target/**/*", diff --git a/src/plugins/saved_objects_management/common/types/v1.ts b/src/plugins/saved_objects_management/common/types/v1.ts index 78bd4a50c036e..241188d035a6e 100644 --- a/src/plugins/saved_objects_management/common/types/v1.ts +++ b/src/plugins/saved_objects_management/common/types/v1.ts @@ -129,13 +129,6 @@ export interface FindQueryHTTP { sortOrder?: FindSortOrderHTTP; hasReference?: ReferenceHTTP | ReferenceHTTP[]; hasReferenceOperator?: FindSearchOperatorHTTP; - /** - * It is not clear who might be using this API option, the SOM UI only ever passes in "id" here. - * - * TODO: Determine use. If not in use we should remove this option. If in use we must deprecate and eventually - * remove. - */ - fields?: string | string[]; } export interface FindResponseHTTP { diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx index 264823dd8e991..48d428a9f087f 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx @@ -236,7 +236,6 @@ export class SavedObjectsTable extends Component typeRegistry.isImportableAndExportable(type) @@ -90,9 +86,6 @@ export const registerFindRoute = ( saved_objects: savedObjects.map((so) => { const obj = injectMetaAttributes(so, managementService); const result = { ...obj, attributes: {} as Record }; - for (const field of includedFields) { - result.attributes[field] = (obj.attributes as Record)[field]; - } return result; }), total: findResponse.total, diff --git a/src/plugins/telemetry/common/routes.ts b/src/plugins/telemetry/common/routes.ts index 2161cb7dd5651..06d6f746bf2c1 100644 --- a/src/plugins/telemetry/common/routes.ts +++ b/src/plugins/telemetry/common/routes.ts @@ -10,9 +10,3 @@ * Fetch Telemetry Config */ export const FetchTelemetryConfigRoute = '/api/telemetry/v2/config'; -export interface FetchTelemetryConfigResponse { - allowChangingOptInStatus: boolean; - optIn: boolean | null; - sendUsageFrom: 'server' | 'browser'; - telemetryNotifyUserAboutOptInDefault: boolean; -} diff --git a/src/plugins/telemetry/common/types/index.ts b/src/plugins/telemetry/common/types/index.ts new file mode 100644 index 0000000000000..14b2d3cbefcf4 --- /dev/null +++ b/src/plugins/telemetry/common/types/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './latest'; + +export * as v2 from './v2'; diff --git a/src/plugins/telemetry/common/types/latest.ts b/src/plugins/telemetry/common/types/latest.ts new file mode 100644 index 0000000000000..557f34eac9ee2 --- /dev/null +++ b/src/plugins/telemetry/common/types/latest.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 * from './v2'; diff --git a/src/plugins/telemetry/common/types/v2.ts b/src/plugins/telemetry/common/types/v2.ts new file mode 100644 index 0000000000000..dc90ad3d242a6 --- /dev/null +++ b/src/plugins/telemetry/common/types/v2.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 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 interface Telemetry { + /** Whether telemetry is enabled */ + enabled?: boolean | null; + lastVersionChecked?: string; + /** Whether to send usage from the server or browser. */ + sendUsageFrom?: 'browser' | 'server'; + lastReported?: number; + allowChangingOptInStatus?: boolean; + userHasSeenNotice?: boolean; + reportFailureCount?: number; + reportFailureVersion?: string; +} + +export interface FetchTelemetryConfigResponse { + allowChangingOptInStatus: boolean; + optIn: boolean | null; + sendUsageFrom: 'server' | 'browser'; + telemetryNotifyUserAboutOptInDefault: boolean; +} + +export interface FetchLastReportedResponse { + lastReported: undefined | number; +} + +export type UpdateLastReportedResponse = undefined; + +export interface OptInStatsBody { + enabled: boolean; + /** @default true */ + unencrypted?: boolean; +} + +export interface StatsPayload { + cluster_uuid: string; + opt_in_status: boolean; +} + +export type OptInStatsResponse = Array<{ + clusterUuid: string; + stats: StatsPayload; +}>; + +export interface OptInBody { + enabled: boolean; +} + +export type OptInResponse = Array<{ + clusterUuid: string; + stats: string; +}>; + +export interface UsageStatsBody { + /** @default false */ + unencrypted: boolean; + /** @default false */ + refreshCache: boolean; +} + +export type UseHasSeenNoticeResponse = Telemetry; + +export type EncryptedTelemetryPayload = Array<{ clusterUuid: string; stats: string }>; + +export type UnencryptedTelemetryPayload = Array<{ clusterUuid: string; stats: object }>; diff --git a/src/plugins/telemetry/public/plugin.ts b/src/plugins/telemetry/public/plugin.ts index d28868a6dd286..15b581c498366 100644 --- a/src/plugins/telemetry/public/plugin.ts +++ b/src/plugins/telemetry/public/plugin.ts @@ -21,7 +21,8 @@ import type { HomePublicPluginSetup } from '@kbn/home-plugin/public'; import { ElasticV3BrowserShipper } from '@kbn/analytics-shippers-elastic-v3-browser'; import { of } from 'rxjs'; -import { FetchTelemetryConfigResponse, FetchTelemetryConfigRoute } from '../common/routes'; +import { FetchTelemetryConfigRoute } from '../common/routes'; +import type { v2 } from '../common/types'; import { TelemetrySender, TelemetryService, TelemetryNotifications } from './services'; import { renderWelcomeTelemetryNotice } from './render_welcome_telemetry_notice'; @@ -322,7 +323,7 @@ export class TelemetryPlugin implements Plugin { const { allowChangingOptInStatus, optIn, sendUsageFrom, telemetryNotifyUserAboutOptInDefault } = - await http.get(FetchTelemetryConfigRoute); + await http.get(FetchTelemetryConfigRoute); return { ...this.config, diff --git a/src/plugins/telemetry/public/services/telemetry_sender.ts b/src/plugins/telemetry/public/services/telemetry_sender.ts index 6ffed583ec95c..3006a328fb620 100644 --- a/src/plugins/telemetry/public/services/telemetry_sender.ts +++ b/src/plugins/telemetry/public/services/telemetry_sender.ts @@ -12,7 +12,7 @@ import { exhaustMap } from 'rxjs/operators'; import { Storage } from '@kbn/kibana-utils-plugin/public'; import { LOCALSTORAGE_KEY, PAYLOAD_CONTENT_ENCODING } from '../../common/constants'; import { TelemetryService } from './telemetry_service'; -import type { EncryptedTelemetryPayload } from '../../common/types'; +import type { EncryptedTelemetryPayload } from '../../common/types/latest'; import { isReportIntervalExpired } from '../../common/is_report_interval_expired'; export class TelemetrySender { diff --git a/src/plugins/telemetry/public/services/telemetry_service.ts b/src/plugins/telemetry/public/services/telemetry_service.ts index 189df46b2328b..c1b96f4749067 100644 --- a/src/plugins/telemetry/public/services/telemetry_service.ts +++ b/src/plugins/telemetry/public/services/telemetry_service.ts @@ -10,7 +10,10 @@ import { i18n } from '@kbn/i18n'; import { CoreStart } from '@kbn/core/public'; import { TelemetryPluginConfig } from '../plugin'; import { getTelemetryChannelEndpoint } from '../../common/telemetry_config/get_telemetry_channel_endpoint'; -import type { UnencryptedTelemetryPayload, EncryptedTelemetryPayload } from '../../common/types'; +import type { + UnencryptedTelemetryPayload, + EncryptedTelemetryPayload, +} from '../../common/types/latest'; import { PAYLOAD_CONTENT_ENCODING } from '../../common/constants'; interface TelemetryServiceConstructor { diff --git a/src/plugins/telemetry/server/routes/telemetry_config.ts b/src/plugins/telemetry/server/routes/telemetry_config.ts index 4bcf1d1f4c811..60a34d80aad2e 100644 --- a/src/plugins/telemetry/server/routes/telemetry_config.ts +++ b/src/plugins/telemetry/server/routes/telemetry_config.ts @@ -9,7 +9,8 @@ import { type Observable, firstValueFrom } from 'rxjs'; import type { IRouter, SavedObjectsClient } from '@kbn/core/server'; import type { TelemetryConfigType } from '../config'; -import { FetchTelemetryConfigResponse, FetchTelemetryConfigRoute } from '../../common/routes'; +import { v2 } from '../../common/types'; +import { FetchTelemetryConfigRoute } from '../../common/routes'; import { getTelemetrySavedObject } from '../saved_objects'; import { getNotifyUserAboutOptInDefault, @@ -64,7 +65,7 @@ export function registerTelemetryConfigRoutes({ telemetryOptedIn: optIn, }); - const body: FetchTelemetryConfigResponse = { + const body: v2.FetchTelemetryConfigResponse = { allowChangingOptInStatus, optIn, sendUsageFrom, diff --git a/src/plugins/telemetry/server/routes/telemetry_last_reported.ts b/src/plugins/telemetry/server/routes/telemetry_last_reported.ts index 80037761895b2..2e21785b9296d 100644 --- a/src/plugins/telemetry/server/routes/telemetry_last_reported.ts +++ b/src/plugins/telemetry/server/routes/telemetry_last_reported.ts @@ -9,6 +9,7 @@ import type { IRouter, SavedObjectsClient } from '@kbn/core/server'; import type { Observable } from 'rxjs'; import { firstValueFrom } from 'rxjs'; +import { v2 } from '../../common/types'; import { getTelemetrySavedObject, updateTelemetrySavedObject } from '../saved_objects'; export function registerTelemetryLastReported( @@ -25,10 +26,12 @@ export function registerTelemetryLastReported( const savedObjectsInternalClient = await firstValueFrom(savedObjectsInternalClient$); const telemetrySavedObject = await getTelemetrySavedObject(savedObjectsInternalClient); + const body: v2.FetchLastReportedResponse = { + lastReported: telemetrySavedObject && telemetrySavedObject?.lastReported, + }; + return res.ok({ - body: { - lastReported: telemetrySavedObject && telemetrySavedObject?.lastReported, - }, + body, }); } ); diff --git a/src/plugins/telemetry/server/routes/telemetry_opt_in.ts b/src/plugins/telemetry/server/routes/telemetry_opt_in.ts index e26e8c596b53a..cc477c4f23198 100644 --- a/src/plugins/telemetry/server/routes/telemetry_opt_in.ts +++ b/src/plugins/telemetry/server/routes/telemetry_opt_in.ts @@ -14,6 +14,7 @@ import type { StatsGetterConfig, TelemetryCollectionManagerPluginSetup, } from '@kbn/telemetry-collection-manager-plugin/server'; +import { v2 } from '../../common/types'; import { sendTelemetryOptInStatus } from './telemetry_opt_in_stats'; import { getTelemetrySavedObject, @@ -109,7 +110,9 @@ export function registerTelemetryOptInRoutes({ return res.forbidden(); } } - return res.ok({ body: optInStatus }); + + const body: v2.OptInResponse = optInStatus; + return res.ok({ body }); } ); } diff --git a/src/plugins/telemetry/server/routes/telemetry_opt_in_stats.ts b/src/plugins/telemetry/server/routes/telemetry_opt_in_stats.ts index d8ec9d4922fc1..8c9c85172ced6 100644 --- a/src/plugins/telemetry/server/routes/telemetry_opt_in_stats.ts +++ b/src/plugins/telemetry/server/routes/telemetry_opt_in_stats.ts @@ -14,6 +14,7 @@ import type { TelemetryCollectionManagerPluginSetup, StatsGetterConfig, } from '@kbn/telemetry-collection-manager-plugin/server'; +import type { v2 } from '../../common/types'; import { EncryptedTelemetryPayload, UnencryptedTelemetryPayload } from '../../common/types'; import { getTelemetryChannelEndpoint } from '../../common/telemetry_config'; import { PAYLOAD_CONTENT_ENCODING } from '../../common/constants'; @@ -90,7 +91,8 @@ export function registerTelemetryOptInStatsRoutes( newOptInStatus, statsGetterConfig ); - return res.ok({ body: optInStatus }); + const body: v2.OptInStatsResponse = optInStatus; + return res.ok({ body }); } catch (err) { return res.ok({ body: [] }); } diff --git a/src/plugins/telemetry/server/routes/telemetry_usage_stats.ts b/src/plugins/telemetry/server/routes/telemetry_usage_stats.ts index fd613e1318966..53169367b965e 100644 --- a/src/plugins/telemetry/server/routes/telemetry_usage_stats.ts +++ b/src/plugins/telemetry/server/routes/telemetry_usage_stats.ts @@ -13,6 +13,7 @@ import type { StatsGetterConfig, } from '@kbn/telemetry-collection-manager-plugin/server'; import type { SecurityPluginStart } from '@kbn/security-plugin/server'; +import { v2 } from '../../common/types'; export type SecurityGetter = () => SecurityPluginStart | undefined; @@ -64,8 +65,10 @@ export function registerTelemetryUsageStatsRoutes( refreshCache: unencrypted || refreshCache, }; - const stats = await telemetryCollectionManager.getStats(statsConfig); - return res.ok({ body: stats }); + const body: v2.UnencryptedTelemetryPayload = await telemetryCollectionManager.getStats( + statsConfig + ); + return res.ok({ body }); } catch (err) { if (isDev) { // don't ignore errors when running in dev mode diff --git a/src/plugins/telemetry/server/routes/telemetry_user_has_seen_notice.ts b/src/plugins/telemetry/server/routes/telemetry_user_has_seen_notice.ts index 7686aa4100755..eeac24c0f5a07 100644 --- a/src/plugins/telemetry/server/routes/telemetry_user_has_seen_notice.ts +++ b/src/plugins/telemetry/server/routes/telemetry_user_has_seen_notice.ts @@ -8,6 +8,7 @@ import type { IRouter } from '@kbn/core/server'; import { TELEMETRY_SAVED_OBJECT_TYPE } from '../saved_objects'; +import { v2 } from '../../common/types'; import { type TelemetrySavedObjectAttributes, getTelemetrySavedObject, @@ -33,7 +34,17 @@ export function registerTelemetryUserHasSeenNotice(router: IRouter) { }; await updateTelemetrySavedObject(soClient, updatedAttributes); - return res.ok({ body: updatedAttributes }); + const body: v2.Telemetry = { + allowChangingOptInStatus: updatedAttributes.allowChangingOptInStatus, + enabled: updatedAttributes.enabled, + lastReported: updatedAttributes.lastReported, + lastVersionChecked: updatedAttributes.lastVersionChecked, + reportFailureCount: updatedAttributes.reportFailureCount, + reportFailureVersion: updatedAttributes.reportFailureVersion, + sendUsageFrom: updatedAttributes.sendUsageFrom, + userHasSeenNotice: updatedAttributes.userHasSeenNotice, + }; + return res.ok({ body }); } ); } diff --git a/src/plugins/unified_search/kibana.jsonc b/src/plugins/unified_search/kibana.jsonc index 008b9d9fe03d2..5f146723b7c2e 100644 --- a/src/plugins/unified_search/kibana.jsonc +++ b/src/plugins/unified_search/kibana.jsonc @@ -17,7 +17,8 @@ "dataViews", "data", "uiActions", - "screenshotMode" + "screenshotMode", + "savedObjectsManagement" ], "optionalPlugins": [ "usageCollection" diff --git a/src/plugins/unified_search/public/types.ts b/src/plugins/unified_search/public/types.ts index 557c31865a417..66abc195b5b19 100755 --- a/src/plugins/unified_search/public/types.ts +++ b/src/plugins/unified_search/public/types.ts @@ -16,6 +16,7 @@ import { UsageCollectionSetup, UsageCollectionStart } from '@kbn/usage-collectio import { Query, AggregateQuery } from '@kbn/es-query'; import { CoreStart, DocLinksStart } from '@kbn/core/public'; import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; +import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public'; import { AutocompleteSetup, AutocompleteStart } from './autocomplete'; import type { IndexPatternSelectProps, StatefulSearchBarProps } from '.'; import type { FiltersBuilderProps } from './filters_builder/filters_builder'; @@ -92,4 +93,5 @@ export interface IUnifiedSearchPluginServices extends Partial { dataViews: DataViewsPublicPluginStart; dataViewEditor: DataViewEditorStart; usageCollection?: UsageCollectionStart; + savedObjectsManagement: SavedObjectsManagementPluginStart; } diff --git a/src/plugins/unified_search/tsconfig.json b/src/plugins/unified_search/tsconfig.json index 0a4ab525d04b7..d11390cf0d18a 100644 --- a/src/plugins/unified_search/tsconfig.json +++ b/src/plugins/unified_search/tsconfig.json @@ -38,6 +38,7 @@ "@kbn/utility-types-jest", "@kbn/react-field", "@kbn/ui-theme", + "@kbn/saved-objects-management-plugin", ], "exclude": [ "target/**/*", diff --git a/src/plugins/usage_collection/server/routes/stats/README.md b/src/plugins/usage_collection/server/routes/stats/README.md index 09dabefbab44a..48ebda9cecb05 100644 --- a/src/plugins/usage_collection/server/routes/stats/README.md +++ b/src/plugins/usage_collection/server/routes/stats/README.md @@ -8,9 +8,9 @@ However, the information detailed above can be extended, with the combination of | Query Parameter | Default value | Description | |:----------------|:-------------:|:------------| -|`extended`|`false`|When `true`, it adds `clusterUuid` and `usage`. The latter contains the information reported by all the Usage Collectors registered in the Kibana server. It may throw `503 Stats not ready` if any of the collectors is not fully initialized yet.| -|`legacy`|`false`|By default, when `extended=true`, the key names of the data in `usage` are transformed into API-friendlier `snake_case` format (i.e.: `clusterUuid` is transformed to `cluster_uuid`). When this parameter is `true`, the data is returned as-is.| -|`exclude_usage`|`false`|When `true`, and `extended=true`, it will report `clusterUuid` but no `usage`.| +|`extended`|`false`|When `true`, it adds `clusterUuid`.| +|`legacy`|`false`|By default, when `extended=true`, the key names are transformed into API-friendlier `snake_case` format (i.e.: `clusterUuid` is transformed to `cluster_uuid`). When this parameter is `true`, the data is returned as-is.| +|`exclude_usage`|`true`| Deprecated. Only kept for backward-compatibility. Setting this to `false` has no effect. Usage is always excluded. | ## Known use cases diff --git a/src/plugins/usage_collection/server/routes/stats/stats.ts b/src/plugins/usage_collection/server/routes/stats/stats.ts index 9db7f5a8638af..242e93a7554e3 100644 --- a/src/plugins/usage_collection/server/routes/stats/stats.ts +++ b/src/plugins/usage_collection/server/routes/stats/stats.ts @@ -8,14 +8,12 @@ import { schema } from '@kbn/config-schema'; import { i18n } from '@kbn/i18n'; -import defaultsDeep from 'lodash/defaultsDeep'; import { firstValueFrom, Observable } from 'rxjs'; import { ElasticsearchClient, IRouter, type MetricsServiceSetup, - SavedObjectsClientContract, ServiceStatus, ServiceStatusLevels, } from '@kbn/core/server'; @@ -51,14 +49,6 @@ export function registerStatsRoute({ metrics: MetricsServiceSetup; overallStatus$: Observable; }) { - const getUsage = async ( - esClient: ElasticsearchClient, - savedObjectsClient: SavedObjectsClientContract - ): Promise => { - const usage = await collectorSet.bulkFetchUsage(esClient, savedObjectsClient); - return collectorSet.toObject(usage); - }; - const getClusterUuid = async (asCurrentUser: ElasticsearchClient): Promise => { const body = await asCurrentUser.info({ filter_path: 'cluster_uuid' }); const { cluster_uuid: uuid } = body; @@ -77,7 +67,7 @@ export function registerStatsRoute({ extended: schema.oneOf([schema.literal(''), schema.boolean()], { defaultValue: false }), legacy: schema.oneOf([schema.literal(''), schema.boolean()], { defaultValue: false }), exclude_usage: schema.oneOf([schema.literal(''), schema.boolean()], { - defaultValue: false, + defaultValue: true, }), }), }, @@ -85,61 +75,26 @@ export function registerStatsRoute({ async (context, req, res) => { const isExtended = req.query.extended === '' || req.query.extended; const isLegacy = req.query.legacy === '' || req.query.legacy; - const shouldGetUsage = req.query.exclude_usage === false; let extended; if (isExtended) { const core = await context.core; const { asCurrentUser } = core.elasticsearch.client; - const savedObjectsClient = core.savedObjects.client; - - const [usage, clusterUuid] = await Promise.all([ - shouldGetUsage - ? getUsage(asCurrentUser, savedObjectsClient) - : Promise.resolve({}), - getClusterUuid(asCurrentUser), - ]); - - let modifiedUsage = usage; - if (isLegacy) { - // In an effort to make telemetry more easily augmented, we need to ensure - // we can passthrough the data without every part of the process needing - // to know about the change; however, to support legacy use cases where this - // wasn't true, we need to be backwards compatible with how the legacy data - // looked and support those use cases here. - modifiedUsage = Object.keys(usage).reduce((accum, usageKey) => { - if (usageKey === 'kibana') { - accum = { - ...accum, - ...usage[usageKey], - }; - } else if (usageKey === 'reporting') { - accum = { - ...accum, - xpack: { - ...accum.xpack, - reporting: usage[usageKey], - }, - }; - } else { - // I don't think we need to it this for the above conditions, but do it for most as it will - // match the behavior done in monitoring/bulk_uploader - defaultsDeep(accum, { [usageKey]: usage[usageKey] }); - } - return accum; - }, {} as UsageObject); + const usage = {} as UsageObject; + const clusterUuid = await getClusterUuid(asCurrentUser); - extended = { - usage: modifiedUsage, - clusterUuid, - }; - } else { - extended = collectorSet.toApiFieldNames({ - usage: modifiedUsage, - clusterUuid, - }); - } + // In an effort to make telemetry more easily augmented, we need to ensure + // we can passthrough the data without every part of the process needing + // to know about the change; however, to support legacy use cases where this + // wasn't true, we need to be backwards compatible with how the legacy data + // looked and support those use cases here. + extended = isLegacy + ? { usage, clusterUuid } + : collectorSet.toApiFieldNames({ + usage, + clusterUuid, + }); } // Guaranteed to resolve immediately due to replay effect on getOpsMetrics$ diff --git a/src/plugins/vis_types/timeseries/server/lib/vis_data/request_processors/series/filter_ratios.js b/src/plugins/vis_types/timeseries/server/lib/vis_data/request_processors/series/filter_ratios.js index 0aaf011053718..34d933c2e9ae4 100644 --- a/src/plugins/vis_types/timeseries/server/lib/vis_data/request_processors/series/filter_ratios.js +++ b/src/plugins/vis_types/timeseries/server/lib/vis_data/request_processors/series/filter_ratios.js @@ -62,7 +62,7 @@ export function ratios(req, panel, series, esQueryConfig, seriesIndex) { denominator: denominatorPath, }, script: - 'params.numerator != null && params.denominator != null && params.denominator > 0 ? params.numerator / params.denominator : 0', + 'params.numerator != null && params.denominator != null && params.denominator != 0 ? params.numerator / params.denominator : 0', }, }); }); diff --git a/src/plugins/vis_types/timeseries/server/lib/vis_data/request_processors/series/filter_ratios.test.js b/src/plugins/vis_types/timeseries/server/lib/vis_data/request_processors/series/filter_ratios.test.js index a93827ba82cd6..e11414d88fd5c 100644 --- a/src/plugins/vis_types/timeseries/server/lib/vis_data/request_processors/series/filter_ratios.test.js +++ b/src/plugins/vis_types/timeseries/server/lib/vis_data/request_processors/series/filter_ratios.test.js @@ -73,7 +73,7 @@ describe('ratios(req, panel, series, esQueryConfig, seriesIndex)', () => { }, script: 'params.numerator != null && params.denominator != null &&' + - ' params.denominator > 0 ? params.numerator / params.denominator : 0', + ' params.denominator != 0 ? params.numerator / params.denominator : 0', }, }, 'metric-1-denominator': { @@ -150,7 +150,7 @@ describe('ratios(req, panel, series, esQueryConfig, seriesIndex)', () => { }, script: 'params.numerator != null && params.denominator != null &&' + - ' params.denominator > 0 ? params.numerator / params.denominator : 0', + ' params.denominator != 0 ? params.numerator / params.denominator : 0', }, }, 'metric-1-denominator': { diff --git a/src/plugins/vis_types/timeseries/server/lib/vis_data/request_processors/table/filter_ratios.ts b/src/plugins/vis_types/timeseries/server/lib/vis_data/request_processors/table/filter_ratios.ts index a897232b27554..3f69c7ec0b99b 100644 --- a/src/plugins/vis_types/timeseries/server/lib/vis_data/request_processors/table/filter_ratios.ts +++ b/src/plugins/vis_types/timeseries/server/lib/vis_data/request_processors/table/filter_ratios.ts @@ -63,7 +63,7 @@ export const filterRatios: TableRequestProcessorsFunction = ({ denominator: denominatorPath, }, script: - 'params.numerator != null && params.denominator != null && params.denominator > 0 ? params.numerator / params.denominator : 0', + 'params.numerator != null && params.denominator != null && params.denominator != 0 ? params.numerator / params.denominator : 0', }, }); }); diff --git a/test/api_integration/apis/saved_objects_management/find.ts b/test/api_integration/apis/saved_objects_management/find.ts index ca891986f609a..ca78702925d5c 100644 --- a/test/api_integration/apis/saved_objects_management/find.ts +++ b/test/api_integration/apis/saved_objects_management/find.ts @@ -37,7 +37,7 @@ export default function ({ getService }: FtrProviderContext) { it('should return 200 with individual responses', async () => await supertest - .get('/api/kibana/management/saved_objects/_find?type=visualization&fields=title') + .get('/api/kibana/management/saved_objects/_find?type=visualization') .expect(200) .then((resp: Response) => { expect(resp.body.saved_objects.map((so: { id: string }) => so.id)).to.eql([ diff --git a/test/api_integration/apis/stats/stats.js b/test/api_integration/apis/stats/stats.js index a95204b5fff4a..3d69a949a4db3 100644 --- a/test/api_integration/apis/stats/stats.js +++ b/test/api_integration/apis/stats/stats.js @@ -91,6 +91,7 @@ export default function ({ getService }) { .then(({ body }) => { expect(body.cluster_uuid).to.be.a('string'); expect(body.usage).to.be.an('object'); // no usage collectors have been registered so usage is an empty object + expect(body.usage).to.eql({}); assertStatsAndMetrics(body); }); }); @@ -103,6 +104,7 @@ export default function ({ getService }) { .then(({ body }) => { expect(body.cluster_uuid).to.be.a('string'); expect(body.usage).to.be.an('object'); + expect(body.usage).to.eql({}); assertStatsAndMetrics(body); }); }); @@ -116,6 +118,7 @@ export default function ({ getService }) { .then(({ body }) => { expect(body.clusterUuid).to.be.a('string'); expect(body.usage).to.be.an('object'); // no usage collectors have been registered so usage is an empty object + expect(body.usage).to.eql({}); assertStatsAndMetrics(body, true); }); }); diff --git a/test/examples/config.js b/test/examples/config.js index fc57b610941d2..2dce755e413b7 100644 --- a/test/examples/config.js +++ b/test/examples/config.js @@ -28,6 +28,7 @@ export default async function ({ readConfigFile }) { require.resolve('./field_formats'), require.resolve('./partial_results'), require.resolve('./search'), + require.resolve('./content_management'), ], services: { ...functionalConfig.get('services'), diff --git a/test/examples/content_management/index.ts b/test/examples/content_management/index.ts new file mode 100644 index 0000000000000..d5e3f14bfc0e6 --- /dev/null +++ b/test/examples/content_management/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { PluginFunctionalProviderContext } from '../../plugin_functional/services'; + +// eslint-disable-next-line import/no-default-export +export default function ({ loadTestFile }: PluginFunctionalProviderContext) { + describe('content management examples', function () { + loadTestFile(require.resolve('./todo_app')); + }); +} diff --git a/test/examples/content_management/todo_app.ts b/test/examples/content_management/todo_app.ts new file mode 100644 index 0000000000000..5c8228540a2de --- /dev/null +++ b/test/examples/content_management/todo_app.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import expect from '@kbn/expect'; +import { Key } from 'selenium-webdriver'; + +import { PluginFunctionalProviderContext } from '../../plugin_functional/services'; + +// eslint-disable-next-line import/no-default-export +export default function ({ getService, getPageObjects }: PluginFunctionalProviderContext) { + const testSubjects = getService('testSubjects'); + const find = getService('find'); + const retry = getService('retry'); + const PageObjects = getPageObjects(['common']); + + describe('Todo app', () => { + it('Todo app works', async () => { + const appId = 'contentManagementExamples'; + await PageObjects.common.navigateToApp(appId); + + // check that initial state is correct + let todos = await testSubjects.findAll(`~todoItem`); + expect(todos.length).to.be(2); + + // check that filters work + await (await find.byCssSelector('label[title="Completed"]')).click(); + todos = await testSubjects.findAll(`~todoItem`); + expect(todos.length).to.be(1); + + await (await find.byCssSelector('label[title="Todo"]')).click(); + todos = await testSubjects.findAll(`~todoItem`); + expect(todos.length).to.be(1); + + await (await find.byCssSelector('label[title="All"]')).click(); + todos = await testSubjects.findAll(`~todoItem`); + expect(todos.length).to.be(2); + + // check that adding new todo works + await testSubjects.setValue('newTodo', 'New todo'); + await (await testSubjects.find('newTodo')).pressKeys(Key.ENTER); + await retry.tryForTime(1000, async () => { + todos = await testSubjects.findAll(`~todoItem`); + expect(todos.length).to.be(3); + }); + + // check that updating todo works + let newTodo = todos[2]; + expect(await newTodo.getVisibleText()).to.be('New todo'); + let newTodoCheckbox = await newTodo.findByTestSubject('~todoCheckbox'); + expect(await newTodoCheckbox.isSelected()).to.be(false); + await (await newTodo.findByTagName('label')).click(); + + await (await find.byCssSelector('label[title="Completed"]')).click(); + todos = await testSubjects.findAll(`~todoItem`); + expect(todos.length).to.be(2); + newTodo = todos[1]; + expect(await newTodo.getVisibleText()).to.be('New todo'); + newTodoCheckbox = await newTodo.findByTestSubject('~todoCheckbox'); + expect(await newTodoCheckbox.isSelected()).to.be(true); + + // check that deleting todo works + await (await newTodo.findByCssSelector('[aria-label="Delete"]')).click(); + todos = await testSubjects.findAll(`~todoItem`); + expect(todos.length).to.be(1); + }); + }); +} diff --git a/test/examples/embeddables/adding_children.ts b/test/examples/embeddables/adding_children.ts index 1b99413b184f7..7b3e48151fd17 100644 --- a/test/examples/embeddables/adding_children.ts +++ b/test/examples/embeddables/adding_children.ts @@ -12,8 +12,29 @@ import { PluginFunctionalProviderContext } from '../../plugin_functional/service // eslint-disable-next-line import/no-default-export export default function ({ getService }: PluginFunctionalProviderContext) { const testSubjects = getService('testSubjects'); + const find = getService('find'); const flyout = getService('flyout'); + const toggleFilterPopover = async () => { + const filtersHolder = await find.byClassName('euiSearchBar__filtersHolder'); + const filtersButton = await filtersHolder.findByCssSelector('button'); + await filtersButton.click(); + }; + + const clickFilter = async (type: string) => { + const list = await testSubjects.find('euiSelectableList'); + const listItems = await list.findAllByCssSelector('li'); + for (let i = 0; i < listItems.length; i++) { + const listItem = await listItems[i].findByClassName('euiSelectableListItem__text'); + const text = await listItem.getVisibleText(); + if (text.includes(type)) { + await listItem.click(); + await toggleFilterPopover(); + break; + } + } + }; + describe('adding children', () => { before(async () => { await testSubjects.click('embeddablePanelExample'); @@ -23,8 +44,8 @@ export default function ({ getService }: PluginFunctionalProviderContext) { await testSubjects.click('embeddablePanelToggleMenuIcon'); await testSubjects.click('embeddablePanelAction-ACTION_ADD_PANEL'); await testSubjects.waitForDeleted('savedObjectFinderLoadingIndicator'); - await testSubjects.click('savedObjectFinderFilterButton'); - await testSubjects.click('savedObjectFinderFilter-todo'); + await toggleFilterPopover(); + await clickFilter('Todo'); await testSubjects.click('savedObjectTitleGarbage'); await testSubjects.moveMouseTo('euiFlyoutCloseButton'); await flyout.ensureClosed('dashboardAddPanel'); diff --git a/test/functional/apps/console/_xjson.ts b/test/functional/apps/console/_xjson.ts index 1535337a2a848..445bfa5d42f0a 100644 --- a/test/functional/apps/console/_xjson.ts +++ b/test/functional/apps/console/_xjson.ts @@ -15,7 +15,8 @@ export default ({ getService, getPageObjects }: FtrProviderContext) => { const log = getService('log'); const PageObjects = getPageObjects(['common', 'console', 'header']); - describe('XJSON', function testXjson() { + // FLAKY: https://github.com/elastic/kibana/issues/145477 + describe.skip('XJSON', function testXjson() { this.tags('includeFirefox'); before(async () => { await PageObjects.common.navigateToApp('console'); diff --git a/test/functional/apps/dashboard/group2/embeddable_library.ts b/test/functional/apps/dashboard/group2/embeddable_library.ts index ca52eaecaf46e..472a2a890c978 100644 --- a/test/functional/apps/dashboard/group2/embeddable_library.ts +++ b/test/functional/apps/dashboard/group2/embeddable_library.ts @@ -17,6 +17,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const dashboardAddPanel = getService('dashboardAddPanel'); const panelActions = getService('dashboardPanelActions'); + const savedObjectsFinder = getService('savedObjectsFinder'); describe('embeddable library', () => { before(async () => { @@ -35,7 +36,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('unlink visualize panel from embeddable library', async () => { // add heatmap panel from library await dashboardAddPanel.clickOpenAddPanel(); - await dashboardAddPanel.filterEmbeddableNames('Rendering Test: heatmap'); + await savedObjectsFinder.filterEmbeddableNames('Rendering Test: heatmap'); await find.clickByButtonText('Rendering Test: heatmap'); await dashboardAddPanel.closeAddPanel(); @@ -51,7 +52,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(libraryActionExists).to.be(false); await dashboardAddPanel.clickOpenAddPanel(); - await dashboardAddPanel.filterEmbeddableNames('Rendering Test: heatmap'); + await savedObjectsFinder.filterEmbeddableNames('Rendering Test: heatmap'); await find.existsByLinkText('Rendering Test: heatmap'); await dashboardAddPanel.closeAddPanel(); }); diff --git a/test/functional/apps/dashboard/group3/dashboard_state.ts b/test/functional/apps/dashboard/group3/dashboard_state.ts index c280de155be82..7839315cb2239 100644 --- a/test/functional/apps/dashboard/group3/dashboard_state.ts +++ b/test/functional/apps/dashboard/group3/dashboard_state.ts @@ -8,13 +8,15 @@ import expect from '@kbn/expect'; import chroma from 'chroma-js'; - +import rison from '@kbn/rison'; import { DEFAULT_PANEL_WIDTH } from '@kbn/dashboard-plugin/public/dashboard_constants'; +import type { SharedDashboardState } from '@kbn/dashboard-plugin/common'; import { PIE_CHART_VIS_NAME, AREA_CHART_VIS_NAME } from '../../../page_objects/dashboard_page'; import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects([ + 'common', 'dashboard', 'visualize', 'header', @@ -31,6 +33,36 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const elasticChart = getService('elasticChart'); const dashboardAddPanel = getService('dashboardAddPanel'); const xyChartSelector = 'xyVisChart'; + const log = getService('log'); + + const updateAppStateQueryParam = ( + url: string, + setAppState: (appState: Partial) => Partial + ) => { + log.debug(`updateAppStateQueryParam, before url: ${url}`); + + // Using lastIndexOf because URL may have 2 sets of query parameters. + // 1) server query parameters, '_t' + // 2) client query parameters, '_g' and '_a'. Anything after the '#' in a URL is used by the client + // Example shape of URL http://localhost:5620/app/dashboards?_t=12345#/create?_g=() + const clientQueryParamsStartIndex = url.lastIndexOf('?'); + if (clientQueryParamsStartIndex === -1) { + throw Error(`Unable to locate query parameters in URL: ${url}`); + } + const urlBeforeClientQueryParams = url.substring(0, clientQueryParamsStartIndex); + const urlParams = new URLSearchParams(url.substring(clientQueryParamsStartIndex + 1)); + const appState: Partial = urlParams.has('_a') + ? (rison.decode(urlParams.get('_a')!) as Partial) + : {}; + const newAppState = { + ...appState, + ...setAppState(appState), + }; + urlParams.set('_a', rison.encode(newAppState)); + const newUrl = urlBeforeClientQueryParams + '?' + urlParams.toString(); + log.debug(`updateAppStateQueryParam, after url: ${newUrl}`); + return newUrl; + }; const enableNewChartLibraryDebug = async (force = false) => { if ((await PageObjects.visChart.isNewChartsLibraryEnabled()) || force) { @@ -39,10 +71,7 @@ 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 - + describe('dashboard state', function () { before(async function () { await PageObjects.dashboard.initTests(); await PageObjects.dashboard.preserveCrossAppState(); @@ -140,8 +169,17 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('Saved search will update when the query is changed in the URL', async () => { const currentQuery = await queryBar.getQueryString(); expect(currentQuery).to.equal(''); - const currentUrl = await getUrlFromShare(); - const newUrl = currentUrl.replace(`query:''`, `query:'abc12345678910'`); + const newUrl = updateAppStateQueryParam( + await getUrlFromShare(), + (appState: Partial) => { + return { + query: { + language: 'kuery', + query: 'abc12345678910', + }, + }; + } + ); // We need to add a timestamp to the URL because URL changes now only work with a hard refresh. await browser.get(newUrl.toString()); @@ -153,9 +191,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); const getUrlFromShare = async () => { + log.debug(`getUrlFromShare`); await PageObjects.share.clickShareTopNavButton(); const sharedUrl = await PageObjects.share.getSharedUrl(); await PageObjects.share.clickShareTopNavButton(); + log.debug(`sharedUrl: ${sharedUrl}`); return sharedUrl; }; @@ -177,11 +217,21 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const changeQuery = async (useHardRefresh: boolean, newQuery: string) => { await queryBar.clickQuerySubmitButton(); - const oldQuery = await queryBar.getQueryString(); const currentUrl = await getUrlFromShare(); - const newUrl = currentUrl.replace(`query:'${oldQuery}'`, `query:'${newQuery}'`); + const newUrl = updateAppStateQueryParam( + currentUrl, + (appState: Partial) => { + return { + query: { + language: 'kuery', + query: newQuery, + }, + }; + } + ); await browser.get(newUrl.toString(), !useHardRefresh); + await PageObjects.dashboard.waitForRenderComplete(); const queryBarContentsAfterRefresh = await queryBar.getQueryString(); expect(queryBarContentsAfterRefresh).to.equal(newQuery); }; @@ -202,9 +252,25 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await dashboardAddPanel.addVisualization(PIE_CHART_VIS_NAME); const currentUrl = await getUrlFromShare(); const currentPanelDimensions = await PageObjects.dashboard.getPanelDimensions(); - const newUrl = currentUrl.replace( - `w:${DEFAULT_PANEL_WIDTH}`, - `w:${DEFAULT_PANEL_WIDTH * 2}` + const newUrl = updateAppStateQueryParam( + currentUrl, + (appState: Partial) => { + log.debug(JSON.stringify(appState, null, ' ')); + return { + panels: (appState.panels ?? []).map((panel) => { + return { + ...panel, + gridData: { + ...panel.gridData, + w: + panel.gridData.w === DEFAULT_PANEL_WIDTH + ? DEFAULT_PANEL_WIDTH * 2 + : panel.gridData.w, + }, + }; + }), + }; + } ); await hardRefresh(newUrl); @@ -229,7 +295,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('when removing a panel', async function () { await PageObjects.dashboard.waitForRenderComplete(); const currentUrl = await getUrlFromShare(); - const newUrl = currentUrl.replace(/panels:\!\(.*\),query/, 'panels:!(),query'); + const newUrl = updateAppStateQueryParam( + currentUrl, + (appState: Partial) => { + return { + panels: [], + }; + } + ); await hardRefresh(newUrl); await retry.try(async () => { @@ -255,7 +328,28 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ); await PageObjects.visChart.selectNewLegendColorChoice('#F9D9F9'); const currentUrl = await getUrlFromShare(); - const newUrl = currentUrl.replace('F9D9F9', 'FFFFFF'); + const newUrl = updateAppStateQueryParam( + currentUrl, + (appState: Partial) => { + return { + panels: (appState.panels ?? []).map((panel) => { + return { + ...panel, + embeddableConfig: { + ...(panel.embeddableConfig ?? {}), + vis: { + ...((panel.embeddableConfig?.vis as object) ?? {}), + colors: { + ...((panel.embeddableConfig?.vis as { colors: object })?.colors ?? {}), + ['80000']: 'FFFFFF', + }, + }, + }, + }; + }), + }; + } + ); await hardRefresh(newUrl); await PageObjects.header.waitUntilLoadingHasFinished(); @@ -280,7 +374,25 @@ 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 = currentUrl.replace(`'80000':%23FFFFFF`, ''); + const newUrl = updateAppStateQueryParam( + currentUrl, + (appState: Partial) => { + return { + panels: (appState.panels ?? []).map((panel) => { + return { + ...panel, + embeddableConfig: { + ...(panel.embeddableConfig ?? {}), + vis: { + ...((panel.embeddableConfig?.vis as object) ?? {}), + colors: {}, + }, + }, + }; + }), + }; + } + ); await hardRefresh(newUrl); await PageObjects.header.waitUntilLoadingHasFinished(); diff --git a/test/functional/apps/dashboard/group3/panel_replacing.ts b/test/functional/apps/dashboard/group3/panel_replacing.ts index e6ff8c4f940bb..1cb344748594f 100644 --- a/test/functional/apps/dashboard/group3/panel_replacing.ts +++ b/test/functional/apps/dashboard/group3/panel_replacing.ts @@ -80,7 +80,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.dashboard.switchToEditMode(); } await dashboardPanelActions.replacePanelByTitle(AREA_CHART_VIS_NAME); - await dashboardReplacePanel.replaceEmbeddable(replacedSearch, 'search'); + await dashboardReplacePanel.replaceEmbeddable(replacedSearch, 'Saved search'); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.dashboard.waitForRenderComplete(); const panelTitles = await PageObjects.dashboard.getPanelTitles(); diff --git a/test/functional/services/dashboard/add_panel.ts b/test/functional/services/dashboard/add_panel.ts index a29fd8046e7ec..7af97ef7ff32f 100644 --- a/test/functional/services/dashboard/add_panel.ts +++ b/test/functional/services/dashboard/add_panel.ts @@ -5,7 +5,6 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ - import { FtrService } from '../../ftr_provider_context'; export class DashboardAddPanelService extends FtrService { @@ -15,6 +14,7 @@ export class DashboardAddPanelService extends FtrService { private readonly flyout = this.ctx.getService('flyout'); private readonly common = this.ctx.getPageObject('common'); private readonly header = this.ctx.getPageObject('header'); + private readonly savedObjectsFinder = this.ctx.getService('savedObjectsFinder'); async clickOpenAddPanel() { this.log.debug('DashboardAddPanel.clickOpenAddPanel'); @@ -77,34 +77,20 @@ export class DashboardAddPanelService extends FtrService { await this.testSubjects.click(`createNew-${type}`); } - async toggleFilterPopover() { - this.log.debug('DashboardAddPanel.toggleFilter'); - await this.testSubjects.click('savedObjectFinderFilterButton'); - } - - async toggleFilter(type: string) { - this.log.debug(`DashboardAddPanel.addToFilter(${type})`); - await this.waitForListLoading(); - await this.toggleFilterPopover(); - await this.testSubjects.click(`savedObjectFinderFilter-${type}`); - await this.toggleFilterPopover(); - } - async addEveryEmbeddableOnCurrentPage() { this.log.debug('addEveryEmbeddableOnCurrentPage'); - const itemList = await this.testSubjects.find('savedObjectFinderItemList'); + const itemList = await this.testSubjects.find('savedObjectsFinderTable'); const embeddableList: string[] = []; await this.retry.try(async () => { - const embeddableRows = await itemList.findAllByCssSelector('li'); + const embeddableListBody = await itemList.findByTagName('tbody'); + const embeddableRows = await embeddableListBody.findAllByCssSelector('tr'); for (let i = 0; i < embeddableRows.length; i++) { - const name = await embeddableRows[i].getVisibleText(); - + const { name, button } = await this.savedObjectsFinder.getRowAtIndex(embeddableRows, i); if (embeddableList.includes(name)) { // already added this one continue; } - - await embeddableRows[i].click(); + await button.click(); await this.common.closeToast(); embeddableList.push(name); } @@ -159,21 +145,21 @@ export class DashboardAddPanelService extends FtrService { } } - async waitForListLoading() { - await this.testSubjects.waitForDeleted('savedObjectFinderLoadingIndicator'); - } - async closeAddPanel() { await this.flyout.ensureClosed('dashboardAddPanel'); } + async filterEmbeddableNames(name: string) { + await this.savedObjectsFinder.filterEmbeddableNames(name); + } + async addEveryVisualization(filter: string) { this.log.debug('DashboardAddPanel.addEveryVisualization'); await this.ensureAddPanelIsShowing(); - await this.toggleFilter('visualization'); if (filter) { await this.filterEmbeddableNames(filter.replace('-', ' ')); } + await this.savedObjectsFinder.waitForFilter('Visualization', 'search'); let morePages = true; const vizList: string[][] = []; while (morePages) { @@ -187,11 +173,11 @@ export class DashboardAddPanelService extends FtrService { async addEverySavedSearch(filter: string) { this.log.debug('DashboardAddPanel.addEverySavedSearch'); await this.ensureAddPanelIsShowing(); - await this.toggleFilter('search'); const searchList = []; if (filter) { await this.filterEmbeddableNames(filter.replace('-', ' ')); } + await this.savedObjectsFinder.waitForFilter('Saved search', 'visualization'); let morePages = true; while (morePages) { searchList.push(await this.addEveryEmbeddableOnCurrentPage()); @@ -222,7 +208,8 @@ export class DashboardAddPanelService extends FtrService { } async addVisualization(vizName: string) { - return this.addEmbeddable(vizName, 'visualization'); + this.log.debug(`DashboardAddPanel.addVisualization, ${vizName}`); + return this.addEmbeddable(vizName, 'Visualization'); } async addEmbeddable(embeddableName: string, embeddableType: string) { @@ -230,25 +217,18 @@ export class DashboardAddPanelService extends FtrService { `DashboardAddPanel.addEmbeddable, name: ${embeddableName}, type: ${embeddableType}` ); await this.ensureAddPanelIsShowing(); - await this.toggleFilter(embeddableType); - await this.filterEmbeddableNames(`"${embeddableName.replace('-', ' ')}"`); + await this.savedObjectsFinder.toggleFilter(embeddableType); + await this.savedObjectsFinder.filterEmbeddableNames(`"${embeddableName.replace('-', ' ')}"`); await this.testSubjects.click(`savedObjectTitle${embeddableName.split(' ').join('-')}`); await this.testSubjects.exists('addObjectToDashboardSuccess'); await this.closeAddPanel(); return embeddableName; } - async filterEmbeddableNames(name: string) { - // The search input field may be disabled while the table is loading so wait for it - await this.waitForListLoading(); - await this.testSubjects.setValue('savedObjectFinderSearchInput', name); - await this.waitForListLoading(); - } - async panelAddLinkExists(name: string) { this.log.debug(`DashboardAddPanel.panelAddLinkExists(${name})`); await this.ensureAddPanelIsShowing(); - await this.filterEmbeddableNames(`"${name}"`); + await this.savedObjectsFinder.filterEmbeddableNames(`"${name}"`); return await this.testSubjects.exists(`savedObjectTitle${name.split(' ').join('-')}`); } } diff --git a/test/functional/services/dashboard/replace_panel.ts b/test/functional/services/dashboard/replace_panel.ts index 8f8f680b839bf..915e65467a7a5 100644 --- a/test/functional/services/dashboard/replace_panel.ts +++ b/test/functional/services/dashboard/replace_panel.ts @@ -12,19 +12,7 @@ export class DashboardReplacePanelService extends FtrService { private readonly log = this.ctx.getService('log'); private readonly testSubjects = this.ctx.getService('testSubjects'); private readonly flyout = this.ctx.getService('flyout'); - - async toggleFilterPopover() { - this.log.debug('DashboardReplacePanel.toggleFilter'); - await this.testSubjects.click('savedObjectFinderFilterButton'); - } - - async toggleFilter(type: string) { - this.log.debug(`DashboardReplacePanel.replaceToFilter(${type})`); - await this.waitForListLoading(); - await this.toggleFilterPopover(); - await this.testSubjects.click(`savedObjectFinderFilter-${type}`); - await this.toggleFilterPopover(); - } + private readonly savedObjectsFinder = this.ctx.getService('savedObjectsFinder'); async isReplacePanelOpen() { this.log.debug('DashboardReplacePanel.isReplacePanelOpen'); @@ -66,10 +54,10 @@ export class DashboardReplacePanelService extends FtrService { `DashboardReplacePanel.replaceEmbeddable, name: ${embeddableName}, type: ${embeddableType}` ); await this.ensureReplacePanelIsShowing(); + await this.filterEmbeddableNames(`"${embeddableName.replace('-', ' ')}"`); if (embeddableType) { - await this.toggleFilter(embeddableType); + await this.savedObjectsFinder.toggleFilter(embeddableType); } - await this.filterEmbeddableNames(`"${embeddableName.replace('-', ' ')}"`); await this.testSubjects.click(`savedObjectTitle${embeddableName.split(' ').join('-')}`); await this.testSubjects.exists('addObjectToDashboardSuccess'); await this.closeReplacePanel(); diff --git a/test/functional/services/index.ts b/test/functional/services/index.ts index 80b80ada9a979..e13cac581ce52 100644 --- a/test/functional/services/index.ts +++ b/test/functional/services/index.ts @@ -55,6 +55,7 @@ import { KibanaSupertestProvider } from './supertest'; import { MenuToggleService } from './menu_toggle'; import { MonacoEditorService } from './monaco_editor'; import { UsageCollectionService } from './usage_collection'; +import { SavedObjectsFinderService } from './saved_objects_finder'; export const services = { ...commonServiceProviders, @@ -100,4 +101,5 @@ export const services = { menuToggle: MenuToggleService, retryOnStale: RetryOnStaleProvider, usageCollection: UsageCollectionService, + savedObjectsFinder: SavedObjectsFinderService, }; diff --git a/test/functional/services/saved_objects_finder.ts b/test/functional/services/saved_objects_finder.ts new file mode 100644 index 0000000000000..12e06fe8a1710 --- /dev/null +++ b/test/functional/services/saved_objects_finder.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import expect from '@kbn/expect'; +import { FtrService } from '../ftr_provider_context'; +import { WebElementWrapper } from './lib/web_element_wrapper'; + +export class SavedObjectsFinderService extends FtrService { + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly find = this.ctx.getService('find'); + private readonly log = this.ctx.getService('log'); + private readonly retry = this.ctx.getService('retry'); + + public async toggleFilterPopover() { + this.log.debug('SavedObjectsFinder.toggleFilter'); + const filtersHolder = await this.find.byClassName('euiSearchBar__filtersHolder'); + const filtersButton = await filtersHolder.findByCssSelector('button'); + await filtersButton.click(); + } + + public async toggleFilter(type: string) { + this.log.debug(`SavedObjectsFinder.addToFilter(${type})`); + await this.waitForListLoading(); + await this.toggleFilterPopover(); + const list = await this.testSubjects.find('euiSelectableList'); + const listItems = await list.findAllByCssSelector('li'); + for (let i = 0; i < listItems.length; i++) { + const listItem = await listItems[i].findByClassName('euiSelectableListItem__text'); + const text = await listItem.getVisibleText(); + if (text.includes(type)) { + await listItem.click(); + await this.toggleFilterPopover(); + break; + } + } + } + + public async waitForFilter(type: string, expectCondition: string) { + await this.toggleFilter(type); + const itemList = await this.testSubjects.find('savedObjectsFinderTable'); + await this.retry.try(async () => { + const embeddableListBody = await itemList.findByTagName('tbody'); + const embeddableRows = await embeddableListBody.findAllByCssSelector('tr'); + const { name } = await this.getRowAtIndex(embeddableRows, 0); + expect(name.includes(expectCondition)).to.be(false); + }); + } + + public async filterEmbeddableNames(name: string) { + // The search input field may be disabled while the table is loading so wait for it + await this.waitForListLoading(); + await this.testSubjects.setValue('savedObjectFinderSearchInput', name); + await this.waitForListLoading(); + } + + public async getRowAtIndex(rows: WebElementWrapper[], rowIndex: number) { + const cell = await rows[rowIndex].findByTestSubject('savedObjectFinderTitle'); + const button = await cell.findByTagName('button'); + const name = await button.getVisibleText(); + return { button, name }; + } + + private async waitForListLoading() { + await this.testSubjects.waitForDeleted('savedObjectFinderLoadingIndicator'); + } +} diff --git a/test/plugin_functional/test_suites/saved_objects_hidden_type/find.ts b/test/plugin_functional/test_suites/saved_objects_hidden_type/find.ts index 5a31da2b57a8e..c73edfc106668 100644 --- a/test/plugin_functional/test_suites/saved_objects_hidden_type/find.ts +++ b/test/plugin_functional/test_suites/saved_objects_hidden_type/find.ts @@ -27,7 +27,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) { it('returns empty response for importableAndExportable types', async () => await supertest - .get('/api/saved_objects/_find?type=test-hidden-importable-exportable&fields=title') + .get('/api/saved_objects/_find?type=test-hidden-importable-exportable') .set('kbn-xsrf', 'true') .expect(200) .then((resp) => { @@ -41,7 +41,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) { it('returns empty response for non importableAndExportable types', async () => await supertest - .get('/api/saved_objects/_find?type=test-hidden-non-importable-exportable&fields=title') + .get('/api/saved_objects/_find?type=test-hidden-non-importable-exportable') .set('kbn-xsrf', 'true') .expect(200) .then((resp) => { diff --git a/test/plugin_functional/test_suites/saved_objects_management/find.ts b/test/plugin_functional/test_suites/saved_objects_management/find.ts index fdeb2c6f8b124..6492f7439079b 100644 --- a/test/plugin_functional/test_suites/saved_objects_management/find.ts +++ b/test/plugin_functional/test_suites/saved_objects_management/find.ts @@ -27,9 +27,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) { ); it('returns saved objects with importableAndExportable types', async () => await supertest - .get( - '/api/kibana/management/saved_objects/_find?type=test-hidden-importable-exportable&fields=title' - ) + .get('/api/kibana/management/saved_objects/_find?type=test-hidden-importable-exportable') .set('kbn-xsrf', 'true') .expect(200) .then((resp) => { diff --git a/test/plugin_functional/test_suites/saved_objects_management/hidden_from_http_apis.ts b/test/plugin_functional/test_suites/saved_objects_management/hidden_from_http_apis.ts index ce441d9d1b353..d9516ee0331c4 100644 --- a/test/plugin_functional/test_suites/saved_objects_management/hidden_from_http_apis.ts +++ b/test/plugin_functional/test_suites/saved_objects_management/hidden_from_http_apis.ts @@ -107,9 +107,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) { describe('find', () => { it('returns saved objects registered as hidden from the http Apis', async () => { await supertest - .get( - `/api/kibana/management/saved_objects/_find?type=${hiddenFromHttpApisType.type}&fields=title` - ) + .get(`/api/kibana/management/saved_objects/_find?type=${hiddenFromHttpApisType.type}`) .set('kbn-xsrf', 'true') .expect(200) .then((resp) => { diff --git a/tsconfig.base.json b/tsconfig.base.json index cff7037533bfe..1e8bd1ef0ade0 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -32,6 +32,8 @@ "@kbn/alerting-plugin/*": ["x-pack/plugins/alerting/*"], "@kbn/alerts": ["packages/kbn-alerts"], "@kbn/alerts/*": ["packages/kbn-alerts/*"], + "@kbn/alerts-as-data-utils": ["packages/kbn-alerts-as-data-utils"], + "@kbn/alerts-as-data-utils/*": ["packages/kbn-alerts-as-data-utils/*"], "@kbn/alerts-restricted-fixtures-plugin": ["x-pack/test/alerting_api_integration/common/plugins/alerts_restricted"], "@kbn/alerts-restricted-fixtures-plugin/*": ["x-pack/test/alerting_api_integration/common/plugins/alerts_restricted/*"], "@kbn/alerts-ui-shared": ["packages/kbn-alerts-ui-shared"], @@ -158,6 +160,8 @@ "@kbn/console-plugin/*": ["src/plugins/console/*"], "@kbn/content-management-content-editor": ["packages/content-management/content_editor"], "@kbn/content-management-content-editor/*": ["packages/content-management/content_editor/*"], + "@kbn/content-management-examples-plugin": ["examples/content_management_examples"], + "@kbn/content-management-examples-plugin/*": ["examples/content_management_examples/*"], "@kbn/content-management-plugin": ["src/plugins/content_management"], "@kbn/content-management-plugin/*": ["src/plugins/content_management/*"], "@kbn/content-management-table-list": ["packages/content-management/table_list"], @@ -666,6 +670,8 @@ "@kbn/event-log-fixture-plugin/*": ["x-pack/test/plugin_api_integration/plugins/event_log/*"], "@kbn/event-log-plugin": ["x-pack/plugins/event_log"], "@kbn/event-log-plugin/*": ["x-pack/plugins/event_log/*"], + "@kbn/expandable-flyout": ["packages/kbn-expandable-flyout"], + "@kbn/expandable-flyout/*": ["packages/kbn-expandable-flyout/*"], "@kbn/expect": ["packages/kbn-expect"], "@kbn/expect/*": ["packages/kbn-expect/*"], "@kbn/exploratory-view-example-plugin": ["x-pack/examples/exploratory_view_example"], diff --git a/x-pack/packages/ml/date_picker/index.ts b/x-pack/packages/ml/date_picker/index.ts index f795d6a4d1f06..1a949a5d1e1d1 100644 --- a/x-pack/packages/ml/date_picker/index.ts +++ b/x-pack/packages/ml/date_picker/index.ts @@ -23,6 +23,7 @@ export { export { getTimeFilterRange, type TimeRange, + type SetFullTimeRangeApiPath, } from './src/services/full_time_range_selector_service'; export { type GetTimeFieldRangeResponse } from './src/services/types'; export { mlTimefilterRefresh$, type Refresh } from './src/services/timefilter_refresh_service'; diff --git a/x-pack/packages/ml/date_picker/src/components/full_time_range_selector.tsx b/x-pack/packages/ml/date_picker/src/components/full_time_range_selector.tsx index bfdd1cb43138d..3ea3c091907ee 100644 --- a/x-pack/packages/ml/date_picker/src/components/full_time_range_selector.tsx +++ b/x-pack/packages/ml/date_picker/src/components/full_time_range_selector.tsx @@ -26,7 +26,10 @@ import type { DataView } from '@kbn/data-plugin/common'; import type { TimefilterContract } from '@kbn/data-plugin/public'; import { FormattedMessage } from '@kbn/i18n-react'; import { useDatePickerContext } from '../hooks/use_date_picker_context'; -import { setFullTimeRange } from '../services/full_time_range_selector_service'; +import { + setFullTimeRange, + type SetFullTimeRangeApiPath, +} from '../services/full_time_range_selector_service'; import type { GetTimeFieldRangeResponse } from '../services/types'; import { FROZEN_TIER_PREFERENCE, type FrozenTierPreference } from '../storage'; @@ -64,6 +67,11 @@ export interface FullTimeRangeSelectorProps { * @param value - The time field range response. */ callback?: (value: GetTimeFieldRangeResponse) => void; + /** + * Optional API path. + * @param value - The time field range response. + */ + apiPath?: SetFullTimeRangeApiPath; } /** @@ -83,6 +91,7 @@ export const FullTimeRangeSelector: FC = (props) => query, disabled, callback, + apiPath, } = props; const { http, @@ -98,7 +107,8 @@ export const FullTimeRangeSelector: FC = (props) => toasts, http, query, - frozenDataPreference === FROZEN_TIER_PREFERENCE.EXCLUDE + frozenDataPreference === FROZEN_TIER_PREFERENCE.EXCLUDE, + apiPath ); if (typeof callback === 'function') { callback(fullTimeRange); @@ -113,7 +123,7 @@ export const FullTimeRangeSelector: FC = (props) => ) ); } - }, [callback, dataView, frozenDataPreference, http, query, timefilter, toasts]); + }, [callback, dataView, frozenDataPreference, http, query, timefilter, toasts, apiPath]); const [isPopoverOpen, setPopover] = useState(false); diff --git a/x-pack/packages/ml/date_picker/src/services/full_time_range_selector_service.ts b/x-pack/packages/ml/date_picker/src/services/full_time_range_selector_service.ts index ab8d79292fd26..12cd8c724e96d 100644 --- a/x-pack/packages/ml/date_picker/src/services/full_time_range_selector_service.ts +++ b/x-pack/packages/ml/date_picker/src/services/full_time_range_selector_service.ts @@ -17,6 +17,13 @@ import { addExcludeFrozenToQuery } from '@kbn/ml-query-utils'; import { getTimeFieldRange } from './time_field_range'; import type { GetTimeFieldRangeResponse } from './types'; +/** + * Allowed API paths to be passed to `setFullTimeRange`. + */ +export type SetFullTimeRangeApiPath = + | '/internal/file_upload/time_field_range' + | '/api/ml/fields_service/time_field_range'; + /** * Determines the full available time range of the given Data View and updates * the timefilter accordingly. @@ -27,6 +34,7 @@ import type { GetTimeFieldRangeResponse } from './types'; * @param http - HttpStart * @param query - optional query * @param excludeFrozenData - optional boolean flag + * @param path - optional SetFullTimeRangeApiPath * @returns {GetTimeFieldRangeResponse} */ export async function setFullTimeRange( @@ -35,7 +43,8 @@ export async function setFullTimeRange( toasts: ToastsStart, http: HttpStart, query?: QueryDslQueryContainer, - excludeFrozenData?: boolean + excludeFrozenData?: boolean, + path: SetFullTimeRangeApiPath = '/internal/file_upload/time_field_range' ): Promise { try { const runtimeMappings = dataView.getRuntimeMappings(); @@ -45,6 +54,7 @@ export async function setFullTimeRange( query: excludeFrozenData ? addExcludeFrozenToQuery(query) : query, ...(isPopulatedObject(runtimeMappings) ? { runtimeMappings } : {}), http, + path, }); if (resp.start.epoch && resp.end.epoch) { @@ -52,6 +62,16 @@ export async function setFullTimeRange( from: moment(resp.start.epoch).toISOString(), to: moment(resp.end.epoch).toISOString(), }); + } else if (typeof resp.start === 'number' && typeof resp.end === 'number') { + timefilter.setTime({ + from: moment(resp.start).toISOString(), + to: moment(resp.end).toISOString(), + }); + return { + success: true, + start: { epoch: resp.start, string: moment(resp.start).toISOString() }, + end: { epoch: resp.end, string: moment(resp.end).toISOString() }, + }; } else { toasts.addWarning({ title: i18n.translate('xpack.ml.datePicker.fullTimeRangeSelector.noResults', { diff --git a/x-pack/packages/ml/date_picker/src/services/time_field_range.ts b/x-pack/packages/ml/date_picker/src/services/time_field_range.ts index 3cbf382ecc6c7..117dddc9dfaf3 100644 --- a/x-pack/packages/ml/date_picker/src/services/time_field_range.ts +++ b/x-pack/packages/ml/date_picker/src/services/time_field_range.ts @@ -36,6 +36,10 @@ interface GetTimeFieldRangeOptions { * HTTP client */ http: HttpStart; + /** + * API path ('/internal/file_upload/time_field_range') + */ + path: string; } /** @@ -44,10 +48,10 @@ interface GetTimeFieldRangeOptions { * @returns GetTimeFieldRangeResponse */ export async function getTimeFieldRange(options: GetTimeFieldRangeOptions) { - const { http, ...body } = options; + const { http, path, ...body } = options; return await http.fetch({ - path: `/internal/file_upload/time_field_range`, + path, method: 'POST', body: JSON.stringify(body), }); diff --git a/x-pack/performance/configs/cloud_security_posture_config.ts b/x-pack/performance/configs/cloud_security_posture_config.ts new file mode 100644 index 0000000000000..3d4f36bb2c605 --- /dev/null +++ b/x-pack/performance/configs/cloud_security_posture_config.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { FtrConfigProviderContext } from '@kbn/test'; + +// eslint-disable-next-line import/no-default-export +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const xpackFunctionalConfig = await readConfigFile( + // eslint-disable-next-line @kbn/imports/no_boundary_crossing + require.resolve('../../test/functional/config.base.js') + ); + + return { + ...xpackFunctionalConfig.getAll(), + kbnTestServer: { + ...xpackFunctionalConfig.get('kbnTestServer'), + serverArgs: [ + ...xpackFunctionalConfig.get('kbnTestServer.serverArgs'), + /** + * Package version is fixed (not latest) so FTR won't suddenly break when package is changed. + * + * test a new package: + * 1. build the package and start the registry with elastic-package and uncomment the 'registryUrl' flag below + * 2. locally checkout the kibana version that matches the new package + * 3. update the package version below to use the new package version + * 4. run tests with NODE_EXTRA_CA_CERTS pointing to the elastic-package certificate. example: + * NODE_EXTRA_CA_CERTS=HOME/.elastic-package/profiles/default/certs/kibana/ca-cert.pem yarn start + * 5. when test pass: + * 1. release a new package to EPR + * 2. merge the updated version number change to kibana + */ + `--xpack.fleet.packages.0.name=cloud_security_posture`, + `--xpack.fleet.packages.0.version=1.2.8`, + // `--xpack.fleet.registryUrl=https://localhost:8080`, + ], + }, + }; +} diff --git a/x-pack/performance/es_archives/kspm_findings/data.json.gz b/x-pack/performance/es_archives/kspm_findings/data.json.gz new file mode 100644 index 0000000000000..075885aacbb45 Binary files /dev/null and b/x-pack/performance/es_archives/kspm_findings/data.json.gz differ diff --git a/x-pack/performance/es_archives/kspm_findings/mappings.json b/x-pack/performance/es_archives/kspm_findings/mappings.json new file mode 100644 index 0000000000000..148507b88cc73 --- /dev/null +++ b/x-pack/performance/es_archives/kspm_findings/mappings.json @@ -0,0 +1,464 @@ +{ + "type": "index", + "value": { + "aliases": {}, + "index": "logs-cloud_security_posture.findings_latest-default", + "mappings": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "cloud_security_posture" + } + }, + "date_detection": false, + "dynamic": "false", + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "agent": { + "properties": { + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "cloud": { + "properties": { + "account": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "cloudbeat": { + "properties": { + "commit_sha": { + "ignore_above": 1024, + "type": "keyword" + }, + "commit_time": { + "type": "date" + }, + "kubernetes": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "policy": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "cluster_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "constant_keyword", + "value": "cloud_security_posture.findings" + }, + "namespace": { + "type": "constant_keyword", + "value": "default" + }, + "type": { + "type": "constant_keyword", + "value": "logs" + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "event": { + "properties": { + "agent_id_status": { + "ignore_above": 1024, + "type": "keyword" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ingested": { + "format": "strict_date_time_no_millis||strict_date_optional_time||epoch_millis", + "type": "date" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + }, + "outcome": { + "ignore_above": 1024, + "type": "keyword" + }, + "sequence": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "file": { + "properties": { + "accessed": { + "type": "date" + }, + "ctime": { + "type": "date" + }, + "directory": { + "ignore_above": 1024, + "type": "keyword" + }, + "extension": { + "ignore_above": 1024, + "type": "keyword" + }, + "gid": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "ignore_above": 1024, + "type": "keyword" + }, + "inode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mtime": { + "type": "date" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "owner": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "size": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "containerized": { + "type": "boolean" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "codename": { + "ignore_above": 1024, + "type": "keyword" + }, + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "message": { + "type": "match_only_text" + }, + "orchestrator": { + "properties": { + "cluster": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "process": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "command_line": { + "ignore_above": 1024, + "type": "wildcard" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "parent": { + "properties": { + "pid": { + "type": "long" + }, + "start": { + "type": "date" + } + } + }, + "pgid": { + "type": "long" + }, + "pid": { + "type": "long" + }, + "start": { + "type": "date" + }, + "title": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + } + } + }, + "resource": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "raw": { + "enabled": false, + "type": "object" + }, + "sub_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "result": { + "properties": { + "evaluation": { + "ignore_above": 1024, + "type": "keyword" + }, + "evidence": { + "enabled": false, + "type": "object" + }, + "expected": { + "enabled": false, + "type": "object" + } + } + }, + "rule": { + "properties": { + "benchmark": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "posture_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "rule_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "section": { + "ignore_above": 1024, + "type": "keyword" + }, + "tags": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "settings": { + "index": { + "codec": "best_compression", + "lifecycle": { + "name": "" + }, + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} diff --git a/x-pack/performance/journeys/cloud_security_dashboard.ts b/x-pack/performance/journeys/cloud_security_dashboard.ts new file mode 100644 index 0000000000000..39625126e7067 --- /dev/null +++ b/x-pack/performance/journeys/cloud_security_dashboard.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Journey } from '@kbn/journeys'; +import expect from '@kbn/expect'; + +export const journey = new Journey({ + beforeSteps: async ({ kibanaServer, retry }) => { + await retry.try(async () => { + const response = await kibanaServer.request({ + path: '/internal/cloud_security_posture/status?check=init', + method: 'GET', + }); + expect(response.status).to.eql(200); + expect(response.data).to.eql({ isPluginInitialized: true }); + }); + }, + ftrConfigPath: 'x-pack/performance/configs/cloud_security_posture_config.ts', + esArchives: ['x-pack/performance/es_archives/kspm_findings'], + scalabilitySetup: { + warmup: [ + { + action: 'constantConcurrentUsers', + userCount: 10, + duration: '30s', + }, + { + action: 'rampConcurrentUsers', + minUsersCount: 10, + maxUsersCount: 50, + duration: '2m', + }, + ], + test: [ + { + action: 'constantConcurrentUsers', + userCount: 50, + duration: '3m', + }, + ], + maxDuration: '10m', + }, +}).step('Go to cloud security dashboards Page', async ({ page, kbnUrl }) => { + await page.goto(kbnUrl.get(`/app/security/cloud_security_posture/dashboard`)); + await page.waitForSelector(`[data-test-subj="csp:dashboard-sections-table-header-score"]`); +}); diff --git a/x-pack/performance/tsconfig.json b/x-pack/performance/tsconfig.json index 636c4e1f5ed1f..fb60be2e310ce 100644 --- a/x-pack/performance/tsconfig.json +++ b/x-pack/performance/tsconfig.json @@ -12,5 +12,7 @@ "@kbn/journeys", "@kbn/test-subj-selector", "@kbn/tooling-log", + "@kbn/test", + "@kbn/expect", ] } diff --git a/x-pack/plugins/alerting/common/alert_schema/field_maps/component_template_from_field_map.ts b/x-pack/plugins/alerting/common/alert_schema/field_maps/component_template_from_field_map.ts index b4cd25a4f4126..4fc36193a15d9 100644 --- a/x-pack/plugins/alerting/common/alert_schema/field_maps/component_template_from_field_map.ts +++ b/x-pack/plugins/alerting/common/alert_schema/field_maps/component_template_from_field_map.ts @@ -6,8 +6,8 @@ */ import { ClusterPutComponentTemplateRequest } from '@elastic/elasticsearch/lib/api/types'; +import { type FieldMap } from '@kbn/alerts-as-data-utils'; import { mappingFromFieldMap } from './mapping_from_field_map'; -import { FieldMap } from './types'; export interface GetComponentTemplateFromFieldMapOpts { name: string; diff --git a/x-pack/plugins/alerting/common/alert_schema/field_maps/mapping_from_field_map.test.ts b/x-pack/plugins/alerting/common/alert_schema/field_maps/mapping_from_field_map.test.ts index 2f2cac2367e8b..f5eeeb8ba6c35 100644 --- a/x-pack/plugins/alerting/common/alert_schema/field_maps/mapping_from_field_map.test.ts +++ b/x-pack/plugins/alerting/common/alert_schema/field_maps/mapping_from_field_map.test.ts @@ -4,9 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { alertFieldMap, legacyAlertFieldMap, type FieldMap } from '@kbn/alerts-as-data-utils'; import { mappingFromFieldMap } from './mapping_from_field_map'; -import { FieldMap } from './types'; -import { alertFieldMap } from './alert_field_map'; describe('mappingFromFieldMap', () => { const fieldMap: FieldMap = { @@ -118,6 +117,15 @@ describe('mappingFromFieldMap', () => { date_field: { type: 'date', }, + multifield_field: { + fields: { + text: { + type: 'match_only_text', + }, + }, + ignore_above: 1024, + type: 'keyword', + }, geopoint_field: { type: 'geo_point', }, @@ -131,15 +139,6 @@ describe('mappingFromFieldMap', () => { long_field: { type: 'long', }, - multifield_field: { - fields: { - text: { - type: 'match_only_text', - }, - }, - ignore_above: 1024, - type: 'keyword', - }, nested_array_field: { properties: { field1: { @@ -184,6 +183,9 @@ describe('mappingFromFieldMap', () => { expect(mappingFromFieldMap(alertFieldMap)).toEqual({ dynamic: 'strict', properties: { + '@timestamp': { + type: 'date', + }, kibana: { properties: { alert: { @@ -191,6 +193,9 @@ describe('mappingFromFieldMap', () => { action_group: { type: 'keyword', }, + case_ids: { + type: 'keyword', + }, duration: { properties: { us: { @@ -204,8 +209,18 @@ describe('mappingFromFieldMap', () => { flapping: { type: 'boolean', }, - id: { - type: 'keyword', + flapping_history: { + type: 'boolean', + }, + instance: { + properties: { + id: { + type: 'keyword', + }, + }, + }, + last_detected: { + type: 'date', }, reason: { type: 'keyword', @@ -229,8 +244,8 @@ describe('mappingFromFieldMap', () => { type: 'keyword', }, parameters: { - type: 'object', - enabled: false, + type: 'flattened', + ignore_above: 4096, }, producer: { type: 'keyword', @@ -274,6 +289,58 @@ describe('mappingFromFieldMap', () => { }, }, }); + expect(mappingFromFieldMap(legacyAlertFieldMap)).toEqual({ + dynamic: 'strict', + properties: { + kibana: { + properties: { + alert: { + properties: { + risk_score: { type: 'float' }, + rule: { + properties: { + author: { type: 'keyword' }, + created_at: { type: 'date' }, + created_by: { type: 'keyword' }, + description: { type: 'keyword' }, + enabled: { type: 'keyword' }, + from: { type: 'keyword' }, + interval: { type: 'keyword' }, + license: { type: 'keyword' }, + note: { type: 'keyword' }, + references: { type: 'keyword' }, + rule_id: { type: 'keyword' }, + rule_name_override: { type: 'keyword' }, + to: { type: 'keyword' }, + type: { type: 'keyword' }, + updated_at: { type: 'date' }, + updated_by: { type: 'keyword' }, + version: { type: 'keyword' }, + }, + }, + severity: { type: 'keyword' }, + suppression: { + properties: { + docs_count: { type: 'long' }, + end: { type: 'date' }, + terms: { + properties: { field: { type: 'keyword' }, value: { type: 'keyword' } }, + }, + start: { type: 'date' }, + }, + }, + system_status: { type: 'keyword' }, + workflow_reason: { type: 'keyword' }, + workflow_user: { type: 'keyword' }, + }, + }, + }, + }, + ecs: { properties: { version: { type: 'keyword' } } }, + event: { properties: { action: { type: 'keyword' }, kind: { type: 'keyword' } } }, + tags: { type: 'keyword' }, + }, + }); }); it('uses dynamic setting if specified', () => { diff --git a/x-pack/plugins/alerting/common/alert_schema/field_maps/mapping_from_field_map.ts b/x-pack/plugins/alerting/common/alert_schema/field_maps/mapping_from_field_map.ts index 5a1de7a995b36..9d1db8e577aa5 100644 --- a/x-pack/plugins/alerting/common/alert_schema/field_maps/mapping_from_field_map.ts +++ b/x-pack/plugins/alerting/common/alert_schema/field_maps/mapping_from_field_map.ts @@ -7,7 +7,7 @@ import type { MappingTypeMapping } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { set } from '@kbn/safer-lodash-set'; -import { FieldMap, MultiField } from './types'; +import type { FieldMap, MultiField } from '@kbn/alerts-as-data-utils'; export function mappingFromFieldMap( fieldMap: FieldMap, @@ -29,7 +29,6 @@ export function mappingFromFieldMap( fields.forEach((field) => { // eslint-disable-next-line @typescript-eslint/naming-convention const { name, required, array, multi_fields, ...rest } = field; - const mapped = multi_fields ? { ...rest, diff --git a/x-pack/plugins/alerting/common/alert_schema/field_maps/types.ts b/x-pack/plugins/alerting/common/alert_schema/field_maps/types.ts deleted file mode 100644 index b687cbfb0cf7d..0000000000000 --- a/x-pack/plugins/alerting/common/alert_schema/field_maps/types.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export interface MultiField { - flat_name?: string; - name: string; - type: string; -} - -export interface FieldMap { - [key: string]: { - type: string; - required: boolean; - array?: boolean; - doc_values?: boolean; - enabled?: boolean; - format?: string; - ignore_above?: number; - index?: boolean; - multi_fields?: MultiField[]; - path?: string; - scaling_factor?: number; - dynamic?: boolean | string; - }; -} diff --git a/x-pack/plugins/alerting/common/alert_schema/index.ts b/x-pack/plugins/alerting/common/alert_schema/index.ts index acca43450fe34..cccb492b10e17 100644 --- a/x-pack/plugins/alerting/common/alert_schema/index.ts +++ b/x-pack/plugins/alerting/common/alert_schema/index.ts @@ -5,5 +5,5 @@ * 2.0. */ -export { alertFieldMap } from './field_maps/alert_field_map'; +export { mappingFromFieldMap } from './field_maps/mapping_from_field_map'; export { getComponentTemplateFromFieldMap } from './field_maps/component_template_from_field_map'; diff --git a/x-pack/plugins/alerting/common/index.ts b/x-pack/plugins/alerting/common/index.ts index fafadcdb99ee4..64e5a78d21e8d 100644 --- a/x-pack/plugins/alerting/common/index.ts +++ b/x-pack/plugins/alerting/common/index.ts @@ -24,6 +24,8 @@ export * from './parse_duration'; export * from './execution_log_types'; export * from './rule_snooze_type'; +export { mappingFromFieldMap, getComponentTemplateFromFieldMap } from './alert_schema'; + export interface AlertingFrameworkHealth { isSufficientlySecure: boolean; hasPermanentEncryptionKey: boolean; diff --git a/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.test.ts b/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.test.ts index 5da807ddba65d..3fb1877756d15 100644 --- a/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.test.ts @@ -12,7 +12,7 @@ import { createAlertFactory, getPublicAlertFactory } from '../alert/create_alert import { Alert } from '../alert/alert'; import { alertingEventLoggerMock } from '../lib/alerting_event_logger/alerting_event_logger.mock'; import { ruleRunMetricsStoreMock } from '../lib/rule_run_metrics_store.mock'; -import { getAlertsForNotification, processAlerts, setFlapping } from '../lib'; +import { getAlertsForNotification, processAlerts } from '../lib'; import { logAlerts } from '../task_runner/log_alerts'; import { DEFAULT_FLAPPING_SETTINGS } from '../../common/rules_settings'; @@ -254,19 +254,6 @@ describe('Legacy Alerts Client', () => { flappingSettings: DEFAULT_FLAPPING_SETTINGS, }); - expect(setFlapping).toHaveBeenCalledWith( - { - enabled: true, - lookBackWindow: 20, - statusChangeThreshold: 4, - }, - { - '1': new Alert('1', testAlert1), - '2': new Alert('2', testAlert2), - }, - {} - ); - expect(getAlertsForNotification).toHaveBeenCalledWith( { enabled: true, diff --git a/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.ts b/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.ts index 9affe3a67d7eb..8fce6782cd2f1 100644 --- a/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.ts +++ b/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.ts @@ -143,12 +143,6 @@ export class LegacyAlertsClient< flappingSettings, }); - setFlapping( - flappingSettings, - processedAlertsActive, - processedAlertsRecovered - ); - const { trimmedAlertsRecovered, earlyRecoveredAlerts } = trimRecoveredAlerts( this.options.logger, processedAlertsRecovered, @@ -213,4 +207,12 @@ export class LegacyAlertsClient< public getExecutorServices() { return getPublicAlertFactory(this.alertFactory!); } + + public setFlapping(flappingSettings: RulesSettingsFlappingProperties) { + setFlapping( + flappingSettings, + this.processedAlerts.active, + this.processedAlerts.recovered + ); + } } diff --git a/x-pack/plugins/alerting/server/alerts_service/alerts_service.test.ts b/x-pack/plugins/alerting/server/alerts_service/alerts_service.test.ts index 82716f935c6b3..ca64faa7c51ea 100644 --- a/x-pack/plugins/alerting/server/alerts_service/alerts_service.test.ts +++ b/x-pack/plugins/alerting/server/alerts_service/alerts_service.test.ts @@ -6,9 +6,11 @@ */ import { elasticsearchServiceMock, loggingSystemMock } from '@kbn/core/server/mocks'; +import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; import { errors as EsErrors } from '@elastic/elasticsearch'; import { ReplaySubject, Subject } from 'rxjs'; import { AlertsService } from './alerts_service'; +import { IRuleTypeAlerts } from '../types'; let logger: ReturnType; const clusterClient = elasticsearchServiceMock.createClusterClient().asInternalUser; @@ -75,40 +77,52 @@ const IlmPutBody = { name: '.alerts-ilm-policy', }; -const getIndexTemplatePutBody = (context?: string) => ({ - name: `.alerts-${context ? context : 'test'}-default-template`, - body: { - index_patterns: [`.alerts-${context ? context : 'test'}-default-*`], - composed_of: [ - 'alerts-common-component-template', - `alerts-${context ? context : 'test'}-component-template`, - ], - template: { - settings: { - auto_expand_replicas: '0-1', - hidden: true, - 'index.lifecycle': { - name: '.alerts-ilm-policy', - rollover_alias: `.alerts-${context ? context : 'test'}-default`, +interface GetIndexTemplatePutBodyOpts { + context?: string; + useLegacyAlerts?: boolean; + useEcs?: boolean; +} +const getIndexTemplatePutBody = (opts?: GetIndexTemplatePutBodyOpts) => { + const context = opts ? opts.context : undefined; + const useLegacyAlerts = opts ? opts.useLegacyAlerts : undefined; + const useEcs = opts ? opts.useEcs : undefined; + return { + name: `.alerts-${context ? context : 'test'}-default-template`, + body: { + index_patterns: [`.alerts-${context ? context : 'test'}-default-*`], + composed_of: [ + `.alerts-${context ? context : 'test'}-mappings`, + ...(useLegacyAlerts ? ['.alerts-legacy-alert-mappings'] : []), + ...(useEcs ? ['.alerts-ecs-mappings'] : []), + '.alerts-framework-mappings', + ], + template: { + settings: { + auto_expand_replicas: '0-1', + hidden: true, + 'index.lifecycle': { + name: '.alerts-ilm-policy', + rollover_alias: `.alerts-${context ? context : 'test'}-default`, + }, + 'index.mapping.total_fields.limit': 2500, + }, + mappings: { + dynamic: false, }, - 'index.mapping.total_fields.limit': 2500, }, - mappings: { - dynamic: false, + _meta: { + managed: true, }, }, - _meta: { - managed: true, - }, - }, -}); + }; +}; -const TestRegistrationContext = { +const TestRegistrationContext: IRuleTypeAlerts = { context: 'test', fieldMap: { field: { type: 'keyword', required: false } }, }; -const AnotherRegistrationContext = { +const AnotherRegistrationContext: IRuleTypeAlerts = { context: 'another', fieldMap: { field: { type: 'keyword', required: false } }, }; @@ -145,10 +159,14 @@ describe('Alerts Service', () => { expect(alertsService.isInitialized()).toEqual(true); expect(clusterClient.ilm.putLifecycle).toHaveBeenCalledWith(IlmPutBody); - expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(1); + expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(3); const componentTemplate1 = clusterClient.cluster.putComponentTemplate.mock.calls[0][0]; - expect(componentTemplate1.name).toEqual('alerts-common-component-template'); + expect(componentTemplate1.name).toEqual('.alerts-framework-mappings'); + const componentTemplate2 = clusterClient.cluster.putComponentTemplate.mock.calls[1][0]; + expect(componentTemplate2.name).toEqual('.alerts-legacy-alert-mappings'); + const componentTemplate3 = clusterClient.cluster.putComponentTemplate.mock.calls[2][0]; + expect(componentTemplate3.name).toEqual('.alerts-ecs-mappings'); }); test('should log error and set initialized to false if adding ILM policy throws error', async () => { @@ -185,13 +203,105 @@ describe('Alerts Service', () => { expect(alertsService.isInitialized()).toEqual(false); expect(logger.error).toHaveBeenCalledWith( - `Error installing component template alerts-common-component-template - fail` + `Error installing component template .alerts-framework-mappings - fail` ); expect(clusterClient.ilm.putLifecycle).toHaveBeenCalled(); expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(1); }); + test('should update index template field limit and retry initialization if creating/updating common component template fails with field limit error', async () => { + clusterClient.cluster.putComponentTemplate.mockRejectedValueOnce( + new EsErrors.ResponseError( + elasticsearchClientMock.createApiResponse({ + statusCode: 400, + body: { + error: { + root_cause: [ + { + type: 'illegal_argument_exception', + reason: + 'updating component template [.alerts-ecs-mappings] results in invalid composable template [.alerts-security.alerts-default-index-template] after templates are merged', + }, + ], + type: 'illegal_argument_exception', + reason: + 'updating component template [.alerts-ecs-mappings] results in invalid composable template [.alerts-security.alerts-default-index-template] after templates are merged', + caused_by: { + type: 'illegal_argument_exception', + reason: + 'composable template [.alerts-security.alerts-default-index-template] template after composition with component templates [.alerts-ecs-mappings, .alerts-security.alerts-mappings, .alerts-technical-mappings] is invalid', + caused_by: { + type: 'illegal_argument_exception', + reason: + 'invalid composite mappings for [.alerts-security.alerts-default-index-template]', + caused_by: { + type: 'illegal_argument_exception', + reason: 'Limit of total fields [1900] has been exceeded', + }, + }, + }, + }, + }, + }) + ) + ); + const existingIndexTemplate = { + name: 'test-template', + index_template: { + index_patterns: ['test*'], + composed_of: ['.alerts-framework-mappings'], + template: { + settings: { + auto_expand_replicas: '0-1', + hidden: true, + 'index.lifecycle': { + name: '.alerts-ilm-policy', + rollover_alias: `.alerts-empty-default`, + }, + 'index.mapping.total_fields.limit': 1800, + }, + mappings: { + dynamic: false, + }, + }, + }, + }; + clusterClient.indices.getIndexTemplate.mockResolvedValueOnce({ + index_templates: [existingIndexTemplate], + }); + const alertsService = new AlertsService({ + logger, + elasticsearchClientPromise: Promise.resolve(clusterClient), + pluginStop$, + }); + + alertsService.initialize(); + await new Promise((r) => setTimeout(r, 50)); + + expect(alertsService.isInitialized()).toEqual(true); + expect(clusterClient.indices.getIndexTemplate).toHaveBeenCalledTimes(1); + expect(clusterClient.indices.putIndexTemplate).toHaveBeenCalledTimes(1); + expect(clusterClient.indices.putIndexTemplate).toHaveBeenCalledWith({ + name: existingIndexTemplate.name, + body: { + ...existingIndexTemplate.index_template, + template: { + ...existingIndexTemplate.index_template.template, + settings: { + ...existingIndexTemplate.index_template.template?.settings, + 'index.mapping.total_fields.limit': 2500, + }, + }, + }, + }); + + expect(clusterClient.ilm.putLifecycle).toHaveBeenCalled(); + // 3x for framework, legacy-alert and ecs mappings, then 1 extra time to update component template + // after updating index template field limit + expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(4); + }); + test('should install resources for contexts awaiting initialization when common resources are initialized', async () => { const alertsService = new AlertsService({ logger, @@ -214,20 +324,24 @@ describe('Alerts Service', () => { ); expect(clusterClient.ilm.putLifecycle).toHaveBeenCalledWith(IlmPutBody); - // 1x for common component template, 2x for context specific - expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(3); + // 1x for framework component template, 1x for legacy alert, 1x for ecs, 2x for context specific + expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(5); const componentTemplate1 = clusterClient.cluster.putComponentTemplate.mock.calls[0][0]; - expect(componentTemplate1.name).toEqual('alerts-common-component-template'); + expect(componentTemplate1.name).toEqual('.alerts-framework-mappings'); const componentTemplate2 = clusterClient.cluster.putComponentTemplate.mock.calls[1][0]; - expect(componentTemplate2.name).toEqual('alerts-another-component-template'); + expect(componentTemplate2.name).toEqual('.alerts-legacy-alert-mappings'); const componentTemplate3 = clusterClient.cluster.putComponentTemplate.mock.calls[2][0]; - expect(componentTemplate3.name).toEqual('alerts-test-component-template'); + expect(componentTemplate3.name).toEqual('.alerts-ecs-mappings'); + const componentTemplate4 = clusterClient.cluster.putComponentTemplate.mock.calls[3][0]; + expect(componentTemplate4.name).toEqual('.alerts-another-mappings'); + const componentTemplate5 = clusterClient.cluster.putComponentTemplate.mock.calls[4][0]; + expect(componentTemplate5.name).toEqual('.alerts-test-mappings'); expect(clusterClient.indices.putIndexTemplate).toHaveBeenCalledTimes(2); expect(clusterClient.indices.putIndexTemplate).toHaveBeenNthCalledWith( 1, - getIndexTemplatePutBody('another') + getIndexTemplatePutBody({ context: 'another' }) ); expect(clusterClient.indices.putIndexTemplate).toHaveBeenNthCalledWith( 2, @@ -291,11 +405,15 @@ describe('Alerts Service', () => { expect(clusterClient.ilm.putLifecycle).toHaveBeenCalledWith(IlmPutBody); - expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(2); + expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(4); const componentTemplate1 = clusterClient.cluster.putComponentTemplate.mock.calls[0][0]; - expect(componentTemplate1.name).toEqual('alerts-common-component-template'); + expect(componentTemplate1.name).toEqual('.alerts-framework-mappings'); const componentTemplate2 = clusterClient.cluster.putComponentTemplate.mock.calls[1][0]; - expect(componentTemplate2.name).toEqual('alerts-test-component-template'); + expect(componentTemplate2.name).toEqual('.alerts-legacy-alert-mappings'); + const componentTemplate3 = clusterClient.cluster.putComponentTemplate.mock.calls[2][0]; + expect(componentTemplate3.name).toEqual('.alerts-ecs-mappings'); + const componentTemplate4 = clusterClient.cluster.putComponentTemplate.mock.calls[3][0]; + expect(componentTemplate4.name).toEqual('.alerts-test-mappings'); expect(clusterClient.indices.putIndexTemplate).toHaveBeenCalledWith( getIndexTemplatePutBody() @@ -318,7 +436,87 @@ describe('Alerts Service', () => { }); }); - test('should not install component template for context fieldMap is empty', async () => { + test('should correctly install resources for context when useLegacyAlerts is true', async () => { + alertsService.register({ ...TestRegistrationContext, useLegacyAlerts: true }); + await new Promise((r) => setTimeout(r, 50)); + expect(await alertsService.isContextInitialized(TestRegistrationContext.context)).toEqual( + true + ); + + expect(clusterClient.ilm.putLifecycle).toHaveBeenCalledWith(IlmPutBody); + + expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(4); + const componentTemplate1 = clusterClient.cluster.putComponentTemplate.mock.calls[0][0]; + expect(componentTemplate1.name).toEqual('.alerts-framework-mappings'); + const componentTemplate2 = clusterClient.cluster.putComponentTemplate.mock.calls[1][0]; + expect(componentTemplate2.name).toEqual('.alerts-legacy-alert-mappings'); + const componentTemplate3 = clusterClient.cluster.putComponentTemplate.mock.calls[2][0]; + expect(componentTemplate3.name).toEqual('.alerts-ecs-mappings'); + const componentTemplate4 = clusterClient.cluster.putComponentTemplate.mock.calls[3][0]; + expect(componentTemplate4.name).toEqual('.alerts-test-mappings'); + + expect(clusterClient.indices.putIndexTemplate).toHaveBeenCalledWith( + getIndexTemplatePutBody({ useLegacyAlerts: true }) + ); + expect(clusterClient.indices.getAlias).toHaveBeenCalledWith({ + index: '.alerts-test-default-*', + }); + expect(clusterClient.indices.putSettings).toHaveBeenCalledTimes(2); + expect(clusterClient.indices.simulateIndexTemplate).toHaveBeenCalledTimes(2); + expect(clusterClient.indices.putMapping).toHaveBeenCalledTimes(2); + expect(clusterClient.indices.create).toHaveBeenCalledWith({ + index: '.alerts-test-default-000001', + body: { + aliases: { + '.alerts-test-default': { + is_write_index: true, + }, + }, + }, + }); + }); + + test('should correctly install resources for context when useEcs is true', async () => { + alertsService.register({ ...TestRegistrationContext, useEcs: true }); + await new Promise((r) => setTimeout(r, 50)); + expect(await alertsService.isContextInitialized(TestRegistrationContext.context)).toEqual( + true + ); + + expect(clusterClient.ilm.putLifecycle).toHaveBeenCalledWith(IlmPutBody); + + expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(4); + const componentTemplate1 = clusterClient.cluster.putComponentTemplate.mock.calls[0][0]; + expect(componentTemplate1.name).toEqual('.alerts-framework-mappings'); + const componentTemplate2 = clusterClient.cluster.putComponentTemplate.mock.calls[1][0]; + expect(componentTemplate2.name).toEqual('.alerts-legacy-alert-mappings'); + const componentTemplate3 = clusterClient.cluster.putComponentTemplate.mock.calls[2][0]; + expect(componentTemplate3.name).toEqual('.alerts-ecs-mappings'); + const componentTemplate4 = clusterClient.cluster.putComponentTemplate.mock.calls[3][0]; + expect(componentTemplate4.name).toEqual('.alerts-test-mappings'); + + expect(clusterClient.indices.putIndexTemplate).toHaveBeenCalledWith( + getIndexTemplatePutBody({ useEcs: true }) + ); + expect(clusterClient.indices.getAlias).toHaveBeenCalledWith({ + index: '.alerts-test-default-*', + }); + expect(clusterClient.indices.putSettings).toHaveBeenCalledTimes(2); + expect(clusterClient.indices.simulateIndexTemplate).toHaveBeenCalledTimes(2); + expect(clusterClient.indices.putMapping).toHaveBeenCalledTimes(2); + expect(clusterClient.indices.create).toHaveBeenCalledWith({ + index: '.alerts-test-default-000001', + body: { + aliases: { + '.alerts-test-default': { + is_write_index: true, + }, + }, + }, + }); + }); + + test('should not install component template for context if fieldMap is empty', async () => { alertsService.register({ context: 'empty', fieldMap: {}, @@ -328,15 +526,19 @@ describe('Alerts Service', () => { expect(clusterClient.ilm.putLifecycle).toHaveBeenCalledWith(IlmPutBody); - expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(1); + expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(3); const componentTemplate1 = clusterClient.cluster.putComponentTemplate.mock.calls[0][0]; - expect(componentTemplate1.name).toEqual('alerts-common-component-template'); + expect(componentTemplate1.name).toEqual('.alerts-framework-mappings'); + const componentTemplate2 = clusterClient.cluster.putComponentTemplate.mock.calls[1][0]; + expect(componentTemplate2.name).toEqual('.alerts-legacy-alert-mappings'); + const componentTemplate3 = clusterClient.cluster.putComponentTemplate.mock.calls[2][0]; + expect(componentTemplate3.name).toEqual('.alerts-ecs-mappings'); expect(clusterClient.indices.putIndexTemplate).toHaveBeenCalledWith({ name: `.alerts-empty-default-template`, body: { index_patterns: [`.alerts-empty-default-*`], - composed_of: ['alerts-common-component-template'], + composed_of: ['.alerts-framework-mappings'], template: { settings: { auto_expand_replicas: '0-1', @@ -410,7 +612,7 @@ describe('Alerts Service', () => { ); expect(clusterClient.ilm.putLifecycle).toHaveBeenCalled(); - expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(2); + expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(4); expect(clusterClient.indices.simulateTemplate).toHaveBeenCalled(); // putIndexTemplate is skipped but other operations are called as expected expect(clusterClient.indices.putIndexTemplate).not.toHaveBeenCalled(); @@ -443,7 +645,7 @@ describe('Alerts Service', () => { ); expect(clusterClient.ilm.putLifecycle).toHaveBeenCalled(); - expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(2); + expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(4); expect(clusterClient.indices.simulateTemplate).toHaveBeenCalled(); expect(clusterClient.indices.putIndexTemplate).not.toHaveBeenCalled(); expect(clusterClient.indices.getAlias).not.toHaveBeenCalled(); @@ -467,7 +669,7 @@ describe('Alerts Service', () => { ); expect(clusterClient.ilm.putLifecycle).toHaveBeenCalled(); - expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(2); + expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(4); expect(clusterClient.indices.simulateTemplate).toHaveBeenCalled(); expect(clusterClient.indices.putIndexTemplate).toHaveBeenCalled(); expect(clusterClient.indices.getAlias).not.toHaveBeenCalled(); @@ -491,7 +693,7 @@ describe('Alerts Service', () => { ); expect(clusterClient.ilm.putLifecycle).toHaveBeenCalled(); - expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(2); + expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(4); expect(clusterClient.indices.simulateTemplate).toHaveBeenCalled(); expect(clusterClient.indices.putIndexTemplate).toHaveBeenCalled(); expect(clusterClient.indices.putSettings).not.toHaveBeenCalled(); @@ -512,7 +714,7 @@ describe('Alerts Service', () => { ); expect(clusterClient.ilm.putLifecycle).toHaveBeenCalled(); - expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(2); + expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(4); expect(clusterClient.indices.simulateTemplate).toHaveBeenCalled(); expect(clusterClient.indices.putIndexTemplate).toHaveBeenCalled(); expect(clusterClient.indices.putSettings).not.toHaveBeenCalled(); @@ -535,7 +737,7 @@ describe('Alerts Service', () => { ); expect(clusterClient.ilm.putLifecycle).toHaveBeenCalled(); - expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(2); + expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(4); expect(clusterClient.indices.simulateTemplate).toHaveBeenCalled(); expect(clusterClient.indices.putIndexTemplate).toHaveBeenCalled(); expect(clusterClient.indices.getAlias).toHaveBeenCalled(); @@ -559,7 +761,7 @@ describe('Alerts Service', () => { ); expect(clusterClient.ilm.putLifecycle).toHaveBeenCalled(); - expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(2); + expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(4); expect(clusterClient.indices.simulateTemplate).toHaveBeenCalled(); expect(clusterClient.indices.putIndexTemplate).toHaveBeenCalled(); expect(clusterClient.indices.getAlias).toHaveBeenCalled(); @@ -581,7 +783,7 @@ describe('Alerts Service', () => { expect(logger.error).toHaveBeenCalledWith(`Failed to PUT mapping for alias alias_1: fail`); expect(clusterClient.ilm.putLifecycle).toHaveBeenCalled(); - expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(2); + expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(4); expect(clusterClient.indices.simulateTemplate).toHaveBeenCalled(); expect(clusterClient.indices.putIndexTemplate).toHaveBeenCalled(); expect(clusterClient.indices.getAlias).toHaveBeenCalled(); @@ -601,7 +803,7 @@ describe('Alerts Service', () => { ); expect(clusterClient.ilm.putLifecycle).toHaveBeenCalled(); - expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(2); + expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(4); expect(clusterClient.indices.simulateTemplate).toHaveBeenCalled(); expect(clusterClient.indices.putIndexTemplate).toHaveBeenCalled(); expect(clusterClient.indices.getAlias).toHaveBeenCalled(); @@ -640,7 +842,7 @@ describe('Alerts Service', () => { ); expect(clusterClient.ilm.putLifecycle).toHaveBeenCalled(); - expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(2); + expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(4); expect(clusterClient.indices.simulateTemplate).toHaveBeenCalled(); expect(clusterClient.indices.putIndexTemplate).toHaveBeenCalled(); expect(clusterClient.indices.getAlias).toHaveBeenCalled(); @@ -673,7 +875,7 @@ describe('Alerts Service', () => { ); expect(clusterClient.ilm.putLifecycle).toHaveBeenCalled(); - expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(2); + expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(4); expect(clusterClient.indices.simulateTemplate).toHaveBeenCalled(); expect(clusterClient.indices.putIndexTemplate).toHaveBeenCalled(); expect(clusterClient.indices.getAlias).toHaveBeenCalled(); @@ -695,7 +897,7 @@ describe('Alerts Service', () => { expect(logger.error).toHaveBeenCalledWith(`Error creating concrete write index - fail`); expect(clusterClient.ilm.putLifecycle).toHaveBeenCalled(); - expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(2); + expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(4); expect(clusterClient.indices.simulateTemplate).toHaveBeenCalled(); expect(clusterClient.indices.putIndexTemplate).toHaveBeenCalled(); expect(clusterClient.indices.getAlias).toHaveBeenCalled(); @@ -730,7 +932,7 @@ describe('Alerts Service', () => { expect(logger.error).toHaveBeenCalledWith(`Error creating concrete write index - fail`); expect(clusterClient.ilm.putLifecycle).toHaveBeenCalled(); - expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(2); + expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(4); expect(clusterClient.indices.simulateTemplate).toHaveBeenCalled(); expect(clusterClient.indices.putIndexTemplate).toHaveBeenCalled(); expect(clusterClient.indices.getAlias).toHaveBeenCalled(); @@ -766,7 +968,7 @@ describe('Alerts Service', () => { expect(logger.error).toHaveBeenCalledWith(`Error creating concrete write index - fail`); expect(clusterClient.ilm.putLifecycle).toHaveBeenCalled(); - expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(2); + expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(4); expect(clusterClient.indices.simulateTemplate).toHaveBeenCalled(); expect(clusterClient.indices.putIndexTemplate).toHaveBeenCalled(); expect(clusterClient.indices.getAlias).toHaveBeenCalled(); @@ -810,7 +1012,7 @@ describe('Alerts Service', () => { alertsService.initialize(); await new Promise((r) => setTimeout(r, 150)); expect(alertsService.isInitialized()).toEqual(true); - expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(3); + expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(5); }); test('should retry updating index template for transient ES errors', async () => { diff --git a/x-pack/plugins/alerting/server/alerts_service/alerts_service.ts b/x-pack/plugins/alerting/server/alerts_service/alerts_service.ts index 08643caf862f5..22cb9f4df1884 100644 --- a/x-pack/plugins/alerting/server/alerts_service/alerts_service.ts +++ b/x-pack/plugins/alerting/server/alerts_service/alerts_service.ts @@ -13,8 +13,14 @@ import { import { get, isEmpty, isEqual } from 'lodash'; import { Logger, ElasticsearchClient } from '@kbn/core/server'; import { firstValueFrom, Observable } from 'rxjs'; -import { FieldMap } from '../../common/alert_schema/field_maps/types'; -import { alertFieldMap } from '../../common/alert_schema'; +import { + alertFieldMap, + ecsFieldMap, + legacyAlertFieldMap, + type FieldMap, +} from '@kbn/alerts-as-data-utils'; +import { IndicesGetIndexTemplateIndexTemplateItem } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { asyncForEach } from '@kbn/std'; import { DEFAULT_ALERTS_ILM_POLICY_NAME, DEFAULT_ALERTS_ILM_POLICY, @@ -34,7 +40,9 @@ import { const TOTAL_FIELDS_LIMIT = 2500; const INSTALLATION_TIMEOUT = 20 * 60 * 1000; // 20 minutes - +const LEGACY_ALERT_CONTEXT = 'legacy-alert'; +export const ECS_CONTEXT = `ecs`; +export const ECS_COMPONENT_TEMPLATE_NAME = getComponentTemplateName(ECS_CONTEXT); interface AlertsServiceParams { logger: Logger; pluginStop$: Observable; @@ -107,6 +115,16 @@ export class AlertsService implements IAlertsService { const initFns = [ () => this.createOrUpdateIlmPolicy(esClient), () => this.createOrUpdateComponentTemplate(esClient, getComponentTemplate(alertFieldMap)), + () => + this.createOrUpdateComponentTemplate( + esClient, + getComponentTemplate(legacyAlertFieldMap, LEGACY_ALERT_CONTEXT) + ), + () => + this.createOrUpdateComponentTemplate( + esClient, + getComponentTemplate(ecsFieldMap, ECS_CONTEXT) + ), ]; for (const fn of initFns) { @@ -127,7 +145,8 @@ export class AlertsService implements IAlertsService { }); } - public register({ context, fieldMap }: IRuleTypeAlerts, timeoutMs?: number) { + public register(opts: IRuleTypeAlerts, timeoutMs?: number) { + const { context, fieldMap } = opts; // check whether this context has been registered before if (this.registeredContexts.has(context)) { const registeredFieldMap = this.registeredContexts.get(context); @@ -140,37 +159,54 @@ export class AlertsService implements IAlertsService { this.options.logger.info(`Registering resources for context "${context}".`); this.registeredContexts.set(context, fieldMap); - this.resourceInitializationHelper.add({ context, fieldMap }, timeoutMs); + this.resourceInitializationHelper.add(opts, timeoutMs); } - private async initializeContext({ context, fieldMap }: IRuleTypeAlerts, timeoutMs?: number) { + private async initializeContext( + { context, fieldMap, useEcs, useLegacyAlerts }: IRuleTypeAlerts, + timeoutMs?: number + ) { const esClient = await this.options.elasticsearchClientPromise; const indexTemplateAndPattern = getIndexTemplateAndPattern(context); - // Context specific initialization installs component template, index template and write index - // If fieldMap is empty, don't create context specific component template - const initFns = isEmpty(fieldMap) - ? [ - async () => - await this.createOrUpdateIndexTemplate(esClient, indexTemplateAndPattern, [ - getComponentTemplateName(), - ]), - async () => await this.createConcreteWriteIndex(esClient, indexTemplateAndPattern), - ] - : [ - async () => - await this.createOrUpdateComponentTemplate( - esClient, - getComponentTemplate(fieldMap, context) - ), - async () => - await this.createOrUpdateIndexTemplate(esClient, indexTemplateAndPattern, [ - getComponentTemplateName(), - getComponentTemplateName(context), - ]), - async () => await this.createConcreteWriteIndex(esClient, indexTemplateAndPattern), - ]; + let initFns: Array<() => Promise> = []; + + // List of component templates to reference + const componentTemplateRefs: string[] = []; + + // If fieldMap is not empty, create a context specific component template + if (!isEmpty(fieldMap)) { + const componentTemplate = getComponentTemplate(fieldMap, context); + initFns.push( + async () => await this.createOrUpdateComponentTemplate(esClient, componentTemplate) + ); + componentTemplateRefs.push(componentTemplate.name); + } + + // If useLegacy is set to true, add the legacy alert component template to the references + if (useLegacyAlerts) { + componentTemplateRefs.push(getComponentTemplateName(LEGACY_ALERT_CONTEXT)); + } + + // If useEcs is set to true, add the ECS component template to the references + if (useEcs) { + componentTemplateRefs.push(getComponentTemplateName(ECS_CONTEXT)); + } + + // Add framework component template to the references + componentTemplateRefs.push(getComponentTemplateName()); + + // Context specific initialization installs index template and write index + initFns = initFns.concat([ + async () => + await this.createOrUpdateIndexTemplate( + esClient, + indexTemplateAndPattern, + componentTemplateRefs + ), + async () => await this.createConcreteWriteIndex(esClient, indexTemplateAndPattern), + ]); for (const fn of initFns) { await this.installWithTimeout(async () => await fn(), timeoutMs); @@ -200,6 +236,62 @@ export class AlertsService implements IAlertsService { } } + private async getIndexTemplatesUsingComponentTemplate( + esClient: ElasticsearchClient, + componentTemplateName: string + ) { + // Get all index templates and filter down to just the ones referencing this component template + const { index_templates: indexTemplates } = await esClient.indices.getIndexTemplate(); + const indexTemplatesUsingComponentTemplate = (indexTemplates ?? []).filter( + (indexTemplate: IndicesGetIndexTemplateIndexTemplateItem) => + indexTemplate.index_template.composed_of.includes(componentTemplateName) + ); + await asyncForEach( + indexTemplatesUsingComponentTemplate, + async (template: IndicesGetIndexTemplateIndexTemplateItem) => { + await esClient.indices.putIndexTemplate({ + name: template.name, + body: { + ...template.index_template, + template: { + ...template.index_template.template, + settings: { + ...template.index_template.template?.settings, + 'index.mapping.total_fields.limit': TOTAL_FIELDS_LIMIT, + }, + }, + }, + }); + } + ); + } + + private async createOrUpdateComponentTemplateHelper( + esClient: ElasticsearchClient, + template: ClusterPutComponentTemplateRequest + ) { + try { + await esClient.cluster.putComponentTemplate(template); + } catch (error) { + const reason = error?.meta?.body?.error?.caused_by?.caused_by?.caused_by?.reason; + if (reason && reason.match(/Limit of total fields \[\d+\] has been exceeded/) != null) { + // This error message occurs when there is an index template using this component template + // that contains a field limit setting that using this component template exceeds + // Specifically, this can happen for the ECS component template when we add new fields + // to adhere to the ECS spec. Individual index templates specify field limits so if the + // number of new ECS fields pushes the composed mapping above the limit, this error will + // occur. We have to update the field limit inside the index template now otherwise we + // can never update the component template + await this.getIndexTemplatesUsingComponentTemplate(esClient, template.name); + + // Try to update the component template again + await esClient.cluster.putComponentTemplate(template); + } else { + throw error; + } + } + } + private async createOrUpdateComponentTemplate( esClient: ElasticsearchClient, template: ClusterPutComponentTemplateRequest @@ -207,9 +299,12 @@ export class AlertsService implements IAlertsService { this.options.logger.info(`Installing component template ${template.name}`); try { - await retryTransientEsErrors(() => esClient.cluster.putComponentTemplate(template), { - logger: this.options.logger, - }); + await retryTransientEsErrors( + () => this.createOrUpdateComponentTemplateHelper(esClient, template), + { + logger: this.options.logger, + } + ); } catch (err) { this.options.logger.error( `Error installing component template ${template.name} - ${err.message}` diff --git a/x-pack/plugins/alerting/server/alerts_service/index.ts b/x-pack/plugins/alerting/server/alerts_service/index.ts new file mode 100644 index 0000000000000..49247f3baa243 --- /dev/null +++ b/x-pack/plugins/alerting/server/alerts_service/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { + DEFAULT_ALERTS_ILM_POLICY, + DEFAULT_ALERTS_ILM_POLICY_NAME, +} from './default_lifecycle_policy'; +export { ECS_COMPONENT_TEMPLATE_NAME, ECS_CONTEXT } from './alerts_service'; +export { getComponentTemplate } from './types'; diff --git a/x-pack/plugins/alerting/server/alerts_service/types.ts b/x-pack/plugins/alerting/server/alerts_service/types.ts index db47a9a8e0015..aeb73cab6ffd2 100644 --- a/x-pack/plugins/alerting/server/alerts_service/types.ts +++ b/x-pack/plugins/alerting/server/alerts_service/types.ts @@ -6,11 +6,11 @@ */ import { ClusterPutComponentTemplateRequest } from '@elastic/elasticsearch/lib/api/types'; -import { getComponentTemplateFromFieldMap } from '../../common/alert_schema'; -import { FieldMap } from '../../common/alert_schema/field_maps/types'; +import type { FieldMap } from '@kbn/alerts-as-data-utils'; +import { getComponentTemplateFromFieldMap } from '../../common'; export const getComponentTemplateName = (context?: string) => - `alerts-${context ? context : 'common'}-component-template`; + `.alerts-${context || 'framework'}-mappings`; export interface IIndexPatternString { template: string; @@ -40,5 +40,5 @@ export const getComponentTemplate = ( name: getComponentTemplateName(context), fieldMap, // set field limit slightly higher than actual number of fields - fieldLimit: 100, // Math.round(Object.keys(fieldMap).length * 1.5), + fieldLimit: Math.ceil(Object.keys(fieldMap).length / 1000) * 1000 + 500, }); diff --git a/x-pack/plugins/alerting/server/index.ts b/x-pack/plugins/alerting/server/index.ts index a13b06596f557..6b5ad3012d8a9 100644 --- a/x-pack/plugins/alerting/server/index.ts +++ b/x-pack/plugins/alerting/server/index.ts @@ -56,7 +56,10 @@ export { export { DEFAULT_ALERTS_ILM_POLICY, DEFAULT_ALERTS_ILM_POLICY_NAME, -} from './alerts_service/default_lifecycle_policy'; + ECS_COMPONENT_TEMPLATE_NAME, + ECS_CONTEXT, + getComponentTemplate, +} from './alerts_service'; export const plugin = (initContext: PluginInitializerContext) => new AlertingPlugin(initContext); diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.ts index a94512e1be483..82e01a93125e5 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.ts @@ -466,6 +466,8 @@ export class TaskRunner< } }); + this.legacyAlertsClient.setFlapping(flappingSettings); + let alertsToReturn: Record = {}; let recoveredAlertsToReturn: Record = {}; // Only serialize alerts into task state if we're auto-recovering, otherwise diff --git a/x-pack/plugins/alerting/server/types.ts b/x-pack/plugins/alerting/server/types.ts index e319f315440af..4997d9563d59e 100644 --- a/x-pack/plugins/alerting/server/types.ts +++ b/x-pack/plugins/alerting/server/types.ts @@ -22,6 +22,7 @@ import { } from '@kbn/core/server'; import type { PublicMethodsOf } from '@kbn/utility-types'; import { SharePluginStart } from '@kbn/share-plugin/server'; +import { type FieldMap } from '@kbn/alerts-as-data-utils'; import { RuleTypeRegistry as OrigruleTypeRegistry } from './rule_type_registry'; import { PluginSetupContract, PluginStartContract } from './plugin'; import { RulesClient } from './rules_client'; @@ -51,7 +52,6 @@ import { SanitizedRule, } from '../common'; import { PublicAlertFactory } from './alert/create_alert_factory'; -import { FieldMap } from '../common/alert_schema/field_maps/types'; import { RulesSettingsFlappingProperties } from '../common/rules_settings'; export type WithoutQueryAndParams = Pick>; export type SpaceIdToNamespaceFunction = (spaceId?: string) => string | undefined; @@ -172,6 +172,8 @@ export interface IRuleTypeAlerts { context: string; namespace?: string; fieldMap: FieldMap; + useEcs?: boolean; + useLegacyAlerts?: boolean; } export interface RuleType< diff --git a/x-pack/plugins/alerting/tsconfig.json b/x-pack/plugins/alerting/tsconfig.json index 1f7017de59a2e..6fed19209ad12 100644 --- a/x-pack/plugins/alerting/tsconfig.json +++ b/x-pack/plugins/alerting/tsconfig.json @@ -39,6 +39,8 @@ "@kbn/data-views-plugin", "@kbn/share-plugin", "@kbn/safer-lodash-set", + "@kbn/alerts-as-data-utils", + "@kbn/core-elasticsearch-client-server-mocks", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/apm/common/agent_configuration/runtime_types/log_ecs_reformatting_rt.ts b/x-pack/plugins/apm/common/agent_configuration/runtime_types/log_ecs_reformatting_rt.ts new file mode 100644 index 0000000000000..cd8777e7ce1d0 --- /dev/null +++ b/x-pack/plugins/apm/common/agent_configuration/runtime_types/log_ecs_reformatting_rt.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as t from 'io-ts'; + +export const logEcsReformattingRt = t.union([ + t.literal('off'), + t.literal('shade'), + t.literal('replace'), + t.literal('override'), +]); diff --git a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/__snapshots__/index.test.ts.snap b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/__snapshots__/index.test.ts.snap index a4bc508856b45..c38f4382e7f40 100644 --- a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/__snapshots__/index.test.ts.snap +++ b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/__snapshots__/index.test.ts.snap @@ -24,6 +24,11 @@ Array [ ], "validationName": "durationRt", }, + Object { + "key": "application_packages", + "type": "text", + "validationName": "string", + }, Object { "key": "capture_body", "options": Array [ @@ -72,6 +77,26 @@ Array [ "type": "boolean", "validationName": "(\\"true\\" | \\"false\\")", }, + Object { + "key": "disable_instrumentations", + "type": "text", + "validationName": "string", + }, + Object { + "key": "disable_outgoing_tracecontext_headers", + "type": "boolean", + "validationName": "(\\"true\\" | \\"false\\")", + }, + Object { + "key": "enable_experimental_instrumentations", + "type": "boolean", + "validationName": "(\\"true\\" | \\"false\\")", + }, + Object { + "key": "enable_instrumentations", + "type": "text", + "validationName": "string", + }, Object { "key": "enable_log_correlation", "type": "boolean", @@ -98,6 +123,29 @@ Array [ "type": "text", "validationName": "string", }, + Object { + "key": "log_ecs_reformatting", + "options": Array [ + Object { + "text": "off", + "value": "off", + }, + Object { + "text": "shade", + "value": "shade", + }, + Object { + "text": "replace", + "value": "replace", + }, + Object { + "text": "override", + "value": "override", + }, + ], + "type": "select", + "validationName": "(\\"off\\" | \\"shade\\" | \\"replace\\" | \\"override\\")", + }, Object { "key": "log_level", "options": Array [ @@ -133,6 +181,16 @@ Array [ "type": "select", "validationName": "(\\"trace\\" | \\"debug\\" | \\"info\\" | \\"warning\\" | \\"error\\" | \\"critical\\" | \\"off\\")", }, + Object { + "key": "log_sending", + "type": "boolean", + "validationName": "(\\"true\\" | \\"false\\")", + }, + Object { + "key": "mongodb_capture_statement_commands", + "type": "boolean", + "validationName": "(\\"true\\" | \\"false\\")", + }, Object { "key": "profiling_inferred_spans_enabled", "type": "boolean", @@ -230,6 +288,28 @@ Array [ ], "validationName": "durationRt", }, + Object { + "key": "span_min_duration", + "min": "0ms", + "type": "duration", + "units": Array [ + "ms", + "s", + "m", + ], + "validationName": "durationRt", + }, + Object { + "key": "span_stack_trace_min_duration", + "min": "-1ms", + "type": "duration", + "units": Array [ + "ms", + "s", + "m", + ], + "validationName": "durationRt", + }, Object { "key": "stack_trace_limit", "max": undefined, diff --git a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/general_settings.ts b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/general_settings.ts index 4e0f37fc76f3b..4dc7a6bb35da4 100644 --- a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/general_settings.ts +++ b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/general_settings.ts @@ -8,6 +8,7 @@ import { i18n } from '@kbn/i18n'; import { captureBodyRt } from '../runtime_types/capture_body_rt'; import { logLevelRt } from '../runtime_types/log_level_rt'; +import { logEcsReformattingRt } from '../runtime_types/log_ecs_reformatting_rt'; import { traceContinuationStrategyRt } from '../runtime_types/trace_continuation_strategy_rt'; import { RawSettingDefinition } from './types'; @@ -93,7 +94,7 @@ export const generalSettings: RawSettingDefinition[] = [ 'The defaults end with a wildcard so that content types like `text/plain; charset=utf-8` are captured as well.', } ), - includeAgents: ['java'], + includeAgents: ['java', 'dotnet'], }, // Capture headers @@ -135,6 +136,53 @@ export const generalSettings: RawSettingDefinition[] = [ includeAgents: ['java'], }, + { + key: 'disable_instrumentations', + type: 'text', + defaultValue: '', + label: i18n.translate( + 'xpack.apm.agentConfig.disableInstrumentations.label', + { + defaultMessage: 'Disable instrumentations', + } + ), + description: i18n.translate( + 'xpack.apm.agentConfig.disableInstrumentations.description', + { + defaultMessage: + 'Comma-separated list of modules to disable instrumentation for.\n' + + 'When instrumentation is disabled for a module, no spans will be collected for that module.\n' + + '\n' + + 'The up-to-date list of modules for which instrumentation can be disabled is language specific ' + + 'and can be found under the following links: ' + + '[Java](https://www.elastic.co/guide/en/apm/agent/java/current/config-core.html#config-disable-instrumentations)', + } + ), + includeAgents: ['java'], + }, + + { + key: 'disable_outgoing_tracecontext_headers', + type: 'boolean', + defaultValue: 'true', + label: i18n.translate( + 'xpack.apm.agentConfig.disableOutgoingTracecontextHeaders.label', + { + defaultMessage: 'Disable outgoing tracecontext headers', + } + ), + description: i18n.translate( + 'xpack.apm.agentConfig.disableOutgoingTracecontextHeaders.description', + { + defaultMessage: + 'Use this option to disable `tracecontext` headers injection to any outgoing communication.\n' + + '\n' + + 'WARNING: Disabling `tracecontext` headers injection means that distributed tracing will not work on downstream services.', + } + ), + includeAgents: ['java'], + }, + { key: 'exit_span_min_duration', type: 'duration', @@ -152,7 +200,7 @@ export const generalSettings: RawSettingDefinition[] = [ 'NOTE: If a span propagates distributed tracing ids, it will not be ignored, even if it is shorter than the configured threshold. This is to ensure that no broken traces are recorded.', } ), - includeAgents: ['java'], + includeAgents: ['java', 'dotnet', 'nodejs', 'python'], }, { @@ -172,6 +220,32 @@ export const generalSettings: RawSettingDefinition[] = [ 'When set, sends-to and receives-from the specified queues/topic will be ignored.', } ), + includeAgents: ['java', 'dotnet', 'nodejs'], + }, + + { + key: 'log_ecs_reformatting', + validation: logEcsReformattingRt, + type: 'select', + defaultValue: 'off', + label: i18n.translate('xpack.apm.agentConfig.logEcsReformatting.label', { + defaultMessage: 'Log ECS reformatting', + }), + description: i18n.translate( + 'xpack.apm.agentConfig.logEcsReformatting.description', + { + defaultMessage: + 'Specifying whether and how the agent should automatically reformat application logs into ' + + '[ECS-compatible JSON](https://www.elastic.co/guide/en/ecs-logging/overview/master/intro.html), ' + + 'suitable for ingestion into Elasticsearch for further Log analysis.', + } + ), + options: [ + { text: 'off', value: 'off' }, + { text: 'shade', value: 'shade' }, + { text: 'replace', value: 'replace' }, + { text: 'override', value: 'override' }, + ], includeAgents: ['java'], }, @@ -199,6 +273,30 @@ export const generalSettings: RawSettingDefinition[] = [ includeAgents: ['dotnet', 'ruby', 'java', 'python', 'nodejs', 'go', 'php'], }, + { + key: 'mongodb_capture_statement_commands', + type: 'boolean', + defaultValue: 'false', + label: i18n.translate( + 'xpack.apm.agentConfig.mongodbCaptureStatementCommands.label', + { + defaultMessage: 'MongoDB capture statement commands', + } + ), + description: i18n.translate( + 'xpack.apm.agentConfig.mongodbCaptureStatementCommands.description', + { + defaultMessage: + 'MongoDB command names for which the command document will be captured, limited to common read-only operations by default. ' + + 'Set to `""` (empty) to disable capture, and `*` to capture all (which is discouraged as it may lead to sensitive information capture).\n' + + '\n' + + 'This option supports the wildcard `*`, which matches zero or more characters. Examples: `/foo/*/bar/*/baz*`, `*foo*`. ' + + 'Matching is case insensitive by default. Prepending an element with `(?-i)` makes the matching case sensitive.', + } + ), + includeAgents: ['java'], + }, + // Recording { key: 'recording', @@ -250,7 +348,7 @@ export const generalSettings: RawSettingDefinition[] = [ 'Span compression reduces the collection, processing, and storage overhead, and removes clutter from the UI. The tradeoff is that some information such as DB statements of all the compressed spans will not be collected.', } ), - includeAgents: ['java'], + includeAgents: ['java', 'dotnet', 'python'], }, { @@ -271,7 +369,7 @@ export const generalSettings: RawSettingDefinition[] = [ 'Consecutive spans that are exact match and that are under this threshold will be compressed into a single composite span. This option does not apply to composite spans. This reduces the collection, processing, and storage overhead, and removes clutter from the UI. The tradeoff is that the DB statements of all the compressed spans will not be collected.', } ), - includeAgents: ['java'], + includeAgents: ['java', 'dotnet', 'python'], }, { key: 'span_compression_same_kind_max_duration', @@ -291,7 +389,7 @@ export const generalSettings: RawSettingDefinition[] = [ 'Consecutive spans to the same destination that are under this threshold will be compressed into a single composite span. This option does not apply to composite spans. This reduces the collection, processing, and storage overhead, and removes clutter from the UI. The tradeoff is that the DB statements of all the compressed spans will not be collected.', } ), - includeAgents: ['java'], + includeAgents: ['java', 'dotnet', 'python'], }, // SPAN_FRAMES_MIN_DURATION @@ -307,12 +405,38 @@ export const generalSettings: RawSettingDefinition[] = [ 'xpack.apm.agentConfig.spanFramesMinDuration.description', { defaultMessage: - 'In its default settings, the APM agent will collect a stack trace with every recorded span.\nWhile this is very helpful to find the exact place in your code that causes the span, collecting this stack trace does have some overhead. \nWhen setting this option to a negative value, like `-1ms`, stack traces will be collected for all spans. Setting it to a positive value, e.g. `5ms`, will limit stack trace collection to spans with durations equal to or longer than the given value, e.g. 5 milliseconds.\n\nTo disable stack trace collection for spans completely, set the value to `0ms`.', + '(Deprecated, use `span_stack_trace_min_duration` instead!) In its default settings, the APM agent will collect a stack trace with every recorded span.\nWhile this is very helpful to find the exact place in your code that causes the span, collecting this stack trace does have some overhead. \nWhen setting this option to a negative value, like `-1ms`, stack traces will be collected for all spans. Setting it to a positive value, e.g. `5ms`, will limit stack trace collection to spans with durations equal to or longer than the given value, e.g. 5 milliseconds.\n\nTo disable stack trace collection for spans completely, set the value to `0ms`.', } ), excludeAgents: ['js-base', 'rum-js', 'nodejs', 'php'], }, + { + key: 'span_stack_trace_min_duration', + type: 'duration', + min: '-1ms', + defaultValue: '5ms', + label: i18n.translate( + 'xpack.apm.agentConfig.spanStackTraceMinDuration.label', + { + defaultMessage: 'Span stack trace minimum duration', + } + ), + description: i18n.translate( + 'xpack.apm.agentConfig.spanStackTraceMinDuration.description', + { + defaultMessage: + 'While this is very helpful to find the exact place in your code that causes the span, ' + + 'collecting this stack trace does have some overhead. When setting this option to the value `0ms`, ' + + 'stack traces will be collected for all spans. Setting it to a positive value, e.g. `5ms`, will limit ' + + 'stack trace collection to spans with durations equal to or longer than the given value, e.g. 5 milliseconds.\n' + + '\n' + + 'To disable stack trace collection for spans completely, set the value to `-1ms`.', + } + ), + includeAgents: ['java', 'dotnet', 'nodejs', 'python'], + }, + // STACK_TRACE_LIMIT { key: 'stack_trace_limit', @@ -328,7 +452,7 @@ export const generalSettings: RawSettingDefinition[] = [ 'Setting it to 0 will disable stack trace collection. Any positive integer value will be used as the maximum number of frames to collect. Setting it -1 means that all frames will be collected.', } ), - includeAgents: ['java', 'dotnet', 'go'], + includeAgents: ['java', 'dotnet', 'go', 'python'], }, { @@ -366,7 +490,7 @@ export const generalSettings: RawSettingDefinition[] = [ { text: 'restart', value: 'restart' }, { text: 'restart_external', value: 'restart_external' }, ], - includeAgents: ['java'], + includeAgents: ['java', 'nodejs', 'python'], }, // Transaction max spans diff --git a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/java_settings.ts b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/java_settings.ts index 694eef7885b31..45abfb43edb00 100644 --- a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/java_settings.ts +++ b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/java_settings.ts @@ -9,6 +9,28 @@ import { i18n } from '@kbn/i18n'; import { RawSettingDefinition } from './types'; export const javaSettings: RawSettingDefinition[] = [ + { + key: 'application_packages', + type: 'text', + defaultValue: '', + label: i18n.translate('xpack.apm.agentConfig.applicationPackages.label', { + defaultMessage: 'Application packages', + }), + description: i18n.translate( + 'xpack.apm.agentConfig.applicationPackages.description', + { + defaultMessage: + 'Used to determine whether a stack trace frame is an in-app frame or a library frame. ' + + 'This allows the APM app to collapse the stack frames of library code, and highlight the stack frames that originate from your application. ' + + 'Multiple root packages can be set as a comma-separated list; there’s no need to configure sub-packages. ' + + 'Because this setting helps determine which classes to scan on startup, setting this option can also improve startup time.\n' + + '\n' + + 'You must set this option in order to use the API annotations `@CaptureTransaction` and `@CaptureSpan`.', + } + ), + includeAgents: ['java'], + }, + // ENABLE_LOG_CORRELATION { key: 'enable_log_correlation', @@ -47,6 +69,101 @@ export const javaSettings: RawSettingDefinition[] = [ ), includeAgents: ['java'], }, + + { + key: 'enable_experimental_instrumentations', + type: 'boolean', + defaultValue: 'false', + label: i18n.translate( + 'xpack.apm.agentConfig.enableExperimentalInstrumentations.label', + { + defaultMessage: 'Enable experimental instrumentations', + } + ), + description: i18n.translate( + 'xpack.apm.agentConfig.enableExperimentalInstrumentations.description', + { + defaultMessage: + 'Whether to apply experimental instrumentations.\n' + + '\n' + + 'NOTE: Changing this value at runtime can slow down the application temporarily. ' + + 'Setting to true will enable instrumentations in the experimental group.', + } + ), + includeAgents: ['java'], + }, + + { + key: 'enable_instrumentations', + type: 'text', + defaultValue: '', + label: i18n.translate( + 'xpack.apm.agentConfig.enableInstrumentations.label', + { + defaultMessage: 'Disable instrumentations', + } + ), + description: i18n.translate( + 'xpack.apm.agentConfig.enableInstrumentations.description', + { + defaultMessage: + 'A list of instrumentations which should be selectively enabled. ' + + 'Valid options are listed in the ' + + '[Java APM Agent documentation](https://www.elastic.co/guide/en/apm/agent/java/current/config-core.html#config-disable-instrumentations).\n' + + '\n' + + 'When set to non-empty value, only listed instrumentations will be enabled ' + + 'if they are not disabled through `disable_instrumentations`or `enable_experimental_instrumentations`.\n' + + 'When not set or empty (default), all instrumentations enabled by default will be enabled ' + + 'unless they are disabled through `disable_instrumentations` or `enable_experimental_instrumentations`.', + } + ), + includeAgents: ['java'], + }, + + { + key: 'log_sending', + type: 'boolean', + defaultValue: 'false', + label: i18n.translate('xpack.apm.agentConfig.logSending.label', { + defaultMessage: 'Log sending (experimental)', + }), + description: i18n.translate( + 'xpack.apm.agentConfig.logSending.description', + { + defaultMessage: + 'Experimental, requires latest version of the Java agent.\n' + + '\n' + + 'If set to `true`,\n' + + 'agent will send logs directly to APM server.', + } + ), + includeAgents: ['java'], + }, + + { + key: 'span_min_duration', + type: 'duration', + defaultValue: '0ms', + min: '0ms', + label: i18n.translate('xpack.apm.agentConfig.spanMinDuration.label', { + defaultMessage: 'Span minimum duration', + }), + description: i18n.translate( + 'xpack.apm.agentConfig.spanMinDuration.description', + { + defaultMessage: + 'Sets the minimum duration of spans. Spans that execute faster than this threshold are attempted to be discarded.\n' + + '\n' + + 'The attempt fails if they lead up to a span that can’t be discarded. Spans that propagate the trace context to ' + + 'downstream services, such as outgoing HTTP requests, can’t be discarded. Additionally, spans that lead to an error ' + + 'or that may be a parent of an async operation can’t be discarded.\n' + + '\n' + + 'However, external calls that don’t propagate context, such as calls to a database, can be discarded using this threshold.', + } + ), + includeAgents: ['java'], + }, + { key: 'stress_monitor_gc_stress_threshold', label: i18n.translate( diff --git a/x-pack/plugins/apm/dev_docs/testing.md b/x-pack/plugins/apm/dev_docs/testing.md index d6fc05f286d5a..1fa7ac8774b08 100644 --- a/x-pack/plugins/apm/dev_docs/testing.md +++ b/x-pack/plugins/apm/dev_docs/testing.md @@ -72,7 +72,9 @@ node x-pack/plugins/apm/scripts/test/api --runner --basic --updateSnapshots The E2E tests are located in [`x-pack/plugins/apm/ftr_e2e`](../ftr_e2e). -Test runs are recorded to the [Cypress Dashboard](https://dashboard.cypress.io). Tests run on buildkite PR pipeline are parallelized (4 parallel jobs) and are orchestrated by the Cypress dashboard service. It can be configured in [.buildkite/pipelines/pull_request/apm_cypress.yml](https://github.com/elastic/kibana/blob/main/.buildkite/pipelines/pull_request/apm_cypress.yml) with the property `parallelism`. +When PR is labeled with `apm:cypress-record`, test runs are recorded to the [Cypress Dashboard](https://dashboard.cypress.io). + +Tests run on buildkite PR pipeline are parallelized (4 parallel jobs) and are orchestrated by the Cypress dashboard service. It can be configured in [.buildkite/pipelines/pull_request/apm_cypress.yml](https://github.com/elastic/kibana/blob/main/.buildkite/pipelines/pull_request/apm_cypress.yml) with the property `parallelism`. ```yml ... diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/instances_table.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/instances_table.cy.ts index 578b116a10592..bda4e78f56c48 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/instances_table.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/instances_table.cy.ts @@ -24,7 +24,7 @@ const serviceRumOverviewHref = url.format({ const testServiveHref = url.format({ pathname: '/app/apm/services/test-service/overview', - query: { rangeFrom: start, rangeTo: end }, + query: { rangeFrom: start, rangeTo: end, transactionType: 'type' }, }); const serviceNodeName = 'opbeans-java-prod-1'; diff --git a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx index 9c7a52dc5859d..3e2d178357a80 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx @@ -5,37 +5,37 @@ * 2.0. */ -import React from 'react'; import { i18n } from '@kbn/i18n'; +import React from 'react'; import { - EuiFlexGroupProps, EuiFlexGroup, + EuiFlexGroupProps, EuiFlexItem, EuiLink, EuiPanel, } from '@elastic/eui'; +import { + isRumAgentName, + isServerlessAgent, +} from '../../../../common/agent_name'; import { AnnotationsContextProvider } from '../../../context/annotations/annotations_context'; import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; import { ChartPointerEventContextProvider } from '../../../context/chart_pointer_event/chart_pointer_event_context'; -import { useBreakpoints } from '../../../hooks/use_breakpoints'; import { useApmParams } from '../../../hooks/use_apm_params'; -import { useTimeRange } from '../../../hooks/use_time_range'; import { useApmRouter } from '../../../hooks/use_apm_router'; +import { useBreakpoints } from '../../../hooks/use_breakpoints'; +import { useTimeRange } from '../../../hooks/use_time_range'; +import { AggregatedTransactionsBadge } from '../../shared/aggregated_transactions_badge'; +import { FailedTransactionRateChart } from '../../shared/charts/failed_transaction_rate_chart'; import { LatencyChart } from '../../shared/charts/latency_chart'; import { TransactionBreakdownChart } from '../../shared/charts/transaction_breakdown_chart'; import { TransactionColdstartRateChart } from '../../shared/charts/transaction_coldstart_rate_chart'; -import { FailedTransactionRateChart } from '../../shared/charts/failed_transaction_rate_chart'; +import { TransactionsTable } from '../../shared/transactions_table'; import { ServiceOverviewDependenciesTable } from './service_overview_dependencies_table'; import { ServiceOverviewErrorsTable } from './service_overview_errors_table'; import { ServiceOverviewInstancesChartAndTable } from './service_overview_instances_chart_and_table'; import { ServiceOverviewThroughputChart } from './service_overview_throughput_chart'; -import { TransactionsTable } from '../../shared/transactions_table'; -import { AggregatedTransactionsBadge } from '../../shared/aggregated_transactions_badge'; -import { - isRumAgentName, - isServerlessAgent, -} from '../../../../common/agent_name'; /** * The height a chart should be if it's next to a table with 5 rows and a title. * Add the height of the pagination row. diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview.stories.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview.stories.tsx index e269f198b5954..f8b5dc3e0b992 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview.stories.tsx @@ -8,6 +8,7 @@ import type { CoreStart } from '@kbn/core/public'; import { Meta, Story } from '@storybook/react'; import React from 'react'; +import { FETCH_STATUS } from '../../../hooks/use_fetcher'; import { ServiceOverview } from '.'; import type { ApmPluginContextValue } from '../../../context/apm_plugin/apm_plugin_context'; import { MockApmPluginStorybook } from '../../../context/apm_plugin/mock_apm_plugin_storybook'; @@ -19,6 +20,8 @@ const stories: Meta<{}> = { decorators: [ (StoryComponent) => { const serviceName = 'testServiceName'; + const transactionType = 'type'; + const transactionTypeStatus = FETCH_STATUS.SUCCESS; const mockCore = { http: { get: (endpoint: string) => { @@ -37,6 +40,8 @@ const stories: Meta<{}> = { } as unknown as CoreStart; const serviceContextValue = { serviceName, + transactionType, + transactionTypeStatus, } as unknown as APMServiceContextValue; return ( diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx index 13bd5324f6444..ab6d66ab2130d 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx @@ -67,7 +67,7 @@ export function ServiceOverviewInstancesChartAndTable({ chartHeight, serviceName, }: ServiceOverviewInstancesChartAndTableProps) { - const { transactionType } = useApmServiceContext(); + const { transactionType, transactionTypeStatus } = useApmServiceContext(); const [tableOptions, setTableOptions] = useState({ pageIndex: 0, sort: DEFAULT_SORT, @@ -95,6 +95,10 @@ export function ServiceOverviewInstancesChartAndTable({ status: mainStatsStatus, } = useFetcher( (callApmApi) => { + if (!transactionType && transactionTypeStatus === FETCH_STATUS.SUCCESS) { + return Promise.resolve(INITIAL_STATE_MAIN_STATS); + } + if (!start || !end || !transactionType || !latencyAggregationType) { return; } @@ -125,9 +129,9 @@ export function ServiceOverviewInstancesChartAndTable({ return { // Everytime the main statistics is refetched, updates the requestId making the detailed API to be refetched. requestId: uuidv4(), - currentPeriodItems: response.currentPeriod, - currentPeriodItemsCount: response.currentPeriod.length, - previousPeriodItems: response.previousPeriod, + currentPeriodItems: response?.currentPeriod ?? [], + currentPeriodItemsCount: response?.currentPeriod.length, + previousPeriodItems: response?.previousPeriod, }; }); }, @@ -140,6 +144,7 @@ export function ServiceOverviewInstancesChartAndTable({ end, serviceName, transactionType, + transactionTypeStatus, pageIndex, field, direction, diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_throughput_chart.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_throughput_chart.tsx index fcc4f3a97cd5c..30df64a9b4d0c 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_throughput_chart.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_throughput_chart.tsx @@ -21,7 +21,7 @@ import { asExactTransactionRate } from '../../../../common/utils/formatters'; import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; import { useEnvironmentsContext } from '../../../context/environments_context/use_environments_context'; import { useAnyOfApmParams } from '../../../hooks/use_apm_params'; -import { useFetcher } from '../../../hooks/use_fetcher'; +import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; import { usePreferredServiceAnomalyTimeseries } from '../../../hooks/use_preferred_service_anomaly_timeseries'; import { useTimeRange } from '../../../hooks/use_time_range'; import { TimeseriesChartWithContext } from '../../shared/charts/timeseries_chart_with_context'; @@ -60,12 +60,17 @@ export function ServiceOverviewThroughputChart({ const { start, end } = useTimeRange({ rangeFrom, rangeTo }); - const { transactionType, serviceName } = useApmServiceContext(); + const { transactionType, serviceName, transactionTypeStatus } = + useApmServiceContext(); const comparisonChartTheme = getComparisonChartTheme(); const { data = INITIAL_STATE, status } = useFetcher( (callApmApi) => { + if (!transactionType && transactionTypeStatus === FETCH_STATUS.SUCCESS) { + return Promise.resolve(INITIAL_STATE); + } + if (serviceName && transactionType && start && end) { return callApmApi( 'GET /internal/apm/services/{serviceName}/throughput', @@ -98,6 +103,7 @@ export function ServiceOverviewThroughputChart({ start, end, transactionType, + transactionTypeStatus, offset, transactionName, comparisonEnabled, @@ -111,7 +117,7 @@ export function ServiceOverviewThroughputChart({ const previousPeriodLabel = usePreviousPeriodLabel(); const timeseries = [ { - data: data.currentPeriod, + data: data?.currentPeriod ?? [], type: 'linemark', color: currentPeriodColor, title: i18n.translate('xpack.apm.serviceOverview.throughtputChartTitle', { @@ -121,7 +127,7 @@ export function ServiceOverviewThroughputChart({ ...(comparisonEnabled ? [ { - data: data.previousPeriod, + data: data?.previousPeriod ?? [], type: 'area', color: previousPeriodColor, title: previousPeriodLabel, diff --git a/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx index 21a51babd04b3..21b830fb314af 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx @@ -8,6 +8,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiSpacer } from '@elastic/eui'; import React from 'react'; import { useHistory } from 'react-router-dom'; +import { isServerlessAgent } from '../../../../common/agent_name'; import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; import { useApmParams } from '../../../hooks/use_apm_params'; import { useTimeRange } from '../../../hooks/use_time_range'; @@ -15,7 +16,6 @@ import { AggregatedTransactionsBadge } from '../../shared/aggregated_transaction import { TransactionCharts } from '../../shared/charts/transaction_charts'; import { replace } from '../../shared/links/url_helpers'; import { TransactionsTable } from '../../shared/transactions_table'; -import { isServerlessAgent } from '../../../../common/agent_name'; export function TransactionOverview() { const { diff --git a/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_overview.test.tsx b/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_overview.test.tsx index 5b3118e527fa4..f27aab3724ab5 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_overview.test.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_overview.test.tsx @@ -49,7 +49,10 @@ function setup({ // mock transaction types jest .spyOn(useServiceTransactionTypesHook, 'useServiceTransactionTypesFetcher') - .mockReturnValue(serviceTransactionTypes); + .mockReturnValue({ + transactionTypes: serviceTransactionTypes, + status: useFetcherHook.FETCH_STATUS.SUCCESS, + }); // mock agent jest diff --git a/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/analyze_data_button.stories.tsx b/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/analyze_data_button.stories.tsx index 57a0e789267b1..a08e575dcd752 100644 --- a/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/analyze_data_button.stories.tsx +++ b/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/analyze_data_button.stories.tsx @@ -49,6 +49,7 @@ export default { { + if (!transactionType && transactionTypeStatus === FETCH_STATUS.SUCCESS) { + return Promise.resolve(INITIAL_STATE); + } + if (transactionType && serviceName && start && end) { return callApmApi( 'GET /internal/apm/services/{serviceName}/transactions/charts/error_rate', @@ -115,6 +120,7 @@ export function FailedTransactionRateChart({ start, end, transactionType, + transactionTypeStatus, transactionName, offset, comparisonEnabled, @@ -128,7 +134,7 @@ export function FailedTransactionRateChart({ const previousPeriodLabel = usePreviousPeriodLabel(); const timeseries = [ { - data: data.currentPeriod.timeseries, + data: data?.currentPeriod?.timeseries ?? [], type: 'linemark', color: currentPeriodColor, title: i18n.translate('xpack.apm.errorRate.chart.errorRate', { @@ -138,7 +144,7 @@ export function FailedTransactionRateChart({ ...(comparisonEnabled ? [ { - data: data.previousPeriod.timeseries, + data: data?.previousPeriod?.timeseries ?? [], type: 'area', color: previousPeriodColor, title: previousPeriodLabel, diff --git a/x-pack/plugins/apm/public/components/shared/charts/helper/helper.ts b/x-pack/plugins/apm/public/components/shared/charts/helper/helper.ts index d203bd8cfe022..6558c6fd03af6 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/helper/helper.ts +++ b/x-pack/plugins/apm/public/components/shared/charts/helper/helper.ts @@ -39,6 +39,7 @@ export const onBrushEnd = ({ export function isTimeseriesEmpty(timeseries?: Array>) { return ( !timeseries || + timeseries.length === 0 || timeseries .map((serie) => serie.data) .flat() diff --git a/x-pack/plugins/apm/public/components/shared/charts/latency_chart/latency_chart.stories.tsx b/x-pack/plugins/apm/public/components/shared/charts/latency_chart/latency_chart.stories.tsx index 23ec2140f5be7..8f084b17381ec 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/latency_chart/latency_chart.stories.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/latency_chart/latency_chart.stories.tsx @@ -84,6 +84,7 @@ const stories: Meta = { value={{ serviceName, transactionType, + transactionTypeStatus: FETCH_STATUS.SUCCESS, transactionTypes: [], fallbackToTransactions: false, serviceAgentStatus: FETCH_STATUS.SUCCESS, diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/use_transaction_breakdown.ts b/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/use_transaction_breakdown.ts index 742bef523a143..538e7bb26fef3 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/use_transaction_breakdown.ts +++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/use_transaction_breakdown.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { useFetcher } from '../../../../hooks/use_fetcher'; +import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; import { useAnyOfApmParams } from '../../../../hooks/use_apm_params'; @@ -31,7 +31,8 @@ export function useTransactionBreakdown({ const { start, end } = useTimeRange({ rangeFrom, rangeTo }); - const { transactionType, serviceName } = useApmServiceContext(); + const { transactionType, serviceName, transactionTypeStatus } = + useApmServiceContext(); const { data = { timeseries: undefined }, @@ -39,6 +40,10 @@ export function useTransactionBreakdown({ status, } = useFetcher( (callApmApi) => { + if (!transactionType && transactionTypeStatus === FETCH_STATUS.SUCCESS) { + return Promise.resolve({ timeseries: undefined }); + } + if (serviceName && start && end && transactionType) { return callApmApi( 'GET /internal/apm/services/{serviceName}/transaction/charts/breakdown', @@ -65,6 +70,7 @@ export function useTransactionBreakdown({ start, end, transactionType, + transactionTypeStatus, transactionName, ] ); diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_coldstart_rate_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/transaction_coldstart_rate_chart/index.tsx index 3dfc22a1c2809..b236f92d0f892 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/transaction_coldstart_rate_chart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_coldstart_rate_chart/index.tsx @@ -18,7 +18,7 @@ import { usePreviousPeriodLabel } from '../../../../hooks/use_previous_period_te import { isTimeComparison } from '../../time_comparison/get_comparison_options'; import { APIReturnType } from '../../../../services/rest/create_call_apm_api'; import { asPercent } from '../../../../../common/utils/formatters'; -import { useFetcher } from '../../../../hooks/use_fetcher'; +import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; import { useTheme } from '../../../../hooks/use_theme'; import { TimeseriesChartWithContext } from '../timeseries_chart_with_context'; import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; @@ -71,7 +71,8 @@ export function TransactionColdstartRateChart({ const { start, end } = useTimeRange({ rangeFrom, rangeTo }); - const { serviceName, transactionType } = useApmServiceContext(); + const { serviceName, transactionType, transactionTypeStatus } = + useApmServiceContext(); const comparisonChartTheme = getComparisonChartTheme(); const endpoint = transactionName @@ -80,6 +81,10 @@ export function TransactionColdstartRateChart({ const { data = INITIAL_STATE, status } = useFetcher( (callApmApi) => { + if (!transactionType && transactionTypeStatus === FETCH_STATUS.SUCCESS) { + return Promise.resolve(INITIAL_STATE); + } + if (transactionType && serviceName && start && end) { return callApmApi(endpoint, { params: { @@ -109,6 +114,7 @@ export function TransactionColdstartRateChart({ start, end, transactionType, + transactionTypeStatus, transactionName, offset, endpoint, @@ -119,7 +125,7 @@ export function TransactionColdstartRateChart({ const timeseries = [ { - data: data.currentPeriod.transactionColdstartRate, + data: data?.currentPeriod?.transactionColdstartRate ?? [], type: 'linemark', color: theme.eui.euiColorVis5, title: i18n.translate('xpack.apm.coldstartRate.chart.coldstartRate', { @@ -129,7 +135,7 @@ export function TransactionColdstartRateChart({ ...(comparisonEnabled ? [ { - data: data.previousPeriod.transactionColdstartRate, + data: data?.previousPeriod?.transactionColdstartRate ?? [], type: 'area', color: theme.eui.euiColorMediumShade, title: previousPeriodLabel, diff --git a/x-pack/plugins/apm/public/components/shared/search_bar/search_bar.test.tsx b/x-pack/plugins/apm/public/components/shared/search_bar/search_bar.test.tsx index f07fc415eaf7d..27eecaf573bf6 100644 --- a/x-pack/plugins/apm/public/components/shared/search_bar/search_bar.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/search_bar/search_bar.test.tsx @@ -44,7 +44,10 @@ function setup({ // mock transaction types jest .spyOn(useServiceTransactionTypesHook, 'useServiceTransactionTypesFetcher') - .mockReturnValue(serviceTransactionTypes); + .mockReturnValue({ + transactionTypes: serviceTransactionTypes, + status: useFetcherHook.FETCH_STATUS.SUCCESS, + }); // mock transaction types jest diff --git a/x-pack/plugins/apm/public/components/shared/transactions_table/index.tsx b/x-pack/plugins/apm/public/components/shared/transactions_table/index.tsx index 3c2520e9b219e..fc97216c4ae2f 100644 --- a/x-pack/plugins/apm/public/components/shared/transactions_table/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/transactions_table/index.tsx @@ -132,7 +132,7 @@ export function TransactionsTable({ const { data = INITIAL_STATE, status } = useFetcher( (callApmApi) => { if (!start || !end || !latencyAggregationType || !transactionType) { - return; + return Promise.resolve(undefined); } return callApmApi( 'GET /internal/apm/services/{serviceName}/transactions/groups/main_statistics', @@ -260,10 +260,6 @@ export function TransactionsTable({ transactionOverflowCount, }); - const isLoading = status === FETCH_STATUS.LOADING; - const isNotInitiated = status === FETCH_STATUS.NOT_INITIATED; - const hasFailed = status === FETCH_STATUS.FAILURE; - const pagination = useMemo( () => ({ pageIndex: index, @@ -338,13 +334,14 @@ export function TransactionsTable({ ({ serviceName: '', + transactionTypeStatus: FETCH_STATUS.NOT_INITIATED, transactionTypes: [], fallbackToTransactions: false, serviceAgentStatus: FETCH_STATUS.NOT_INITIATED, @@ -65,11 +67,12 @@ export function ApmServiceContextProvider({ end, }); - const transactionTypes = useServiceTransactionTypesFetcher({ - serviceName, - start, - end, - }); + const { transactionTypes, status: transactionTypeStatus } = + useServiceTransactionTypesFetcher({ + serviceName, + start, + end, + }); const currentTransactionType = getOrRedirectToTransactionType({ transactionType: query.transactionType, @@ -89,6 +92,7 @@ export function ApmServiceContextProvider({ agentName, serverlessType, transactionType: currentTransactionType, + transactionTypeStatus, transactionTypes, runtimeName, fallbackToTransactions, diff --git a/x-pack/plugins/apm/public/context/apm_service/use_service_transaction_types_fetcher.tsx b/x-pack/plugins/apm/public/context/apm_service/use_service_transaction_types_fetcher.tsx index 96a4141e824cc..fa00f7f45e743 100644 --- a/x-pack/plugins/apm/public/context/apm_service/use_service_transaction_types_fetcher.tsx +++ b/x-pack/plugins/apm/public/context/apm_service/use_service_transaction_types_fetcher.tsx @@ -18,7 +18,7 @@ export function useServiceTransactionTypesFetcher({ start?: string; end?: string; }) { - const { data = INITIAL_DATA } = useFetcher( + const { data = INITIAL_DATA, status } = useFetcher( (callApmApi) => { if (serviceName && start && end) { return callApmApi( @@ -35,5 +35,5 @@ export function useServiceTransactionTypesFetcher({ [serviceName, start, end] ); - return data.transactionTypes; + return { transactionTypes: data.transactionTypes, status }; } diff --git a/x-pack/plugins/apm/public/hooks/use_transaction_latency_chart_fetcher.ts b/x-pack/plugins/apm/public/hooks/use_transaction_latency_chart_fetcher.ts index 32b0c06e99198..d3640afe9f9ea 100644 --- a/x-pack/plugins/apm/public/hooks/use_transaction_latency_chart_fetcher.ts +++ b/x-pack/plugins/apm/public/hooks/use_transaction_latency_chart_fetcher.ts @@ -6,14 +6,14 @@ */ import { useMemo } from 'react'; -import { usePreviousPeriodLabel } from './use_previous_period_text'; import { isTimeComparison } from '../components/shared/time_comparison/get_comparison_options'; -import { useFetcher } from './use_fetcher'; -import { useLegacyUrlParams } from '../context/url_params_context/use_url_params'; import { useApmServiceContext } from '../context/apm_service/use_apm_service_context'; +import { useLegacyUrlParams } from '../context/url_params_context/use_url_params'; import { getLatencyChartSelector } from '../selectors/latency_chart_selectors'; -import { useTimeRange } from './use_time_range'; import { useAnyOfApmParams } from './use_apm_params'; +import { FETCH_STATUS, useFetcher } from './use_fetcher'; +import { usePreviousPeriodLabel } from './use_previous_period_text'; +import { useTimeRange } from './use_time_range'; export function useTransactionLatencyChartsFetcher({ kuery, @@ -22,7 +22,8 @@ export function useTransactionLatencyChartsFetcher({ kuery: string; environment: string; }) { - const { transactionType, serviceName } = useApmServiceContext(); + const { transactionType, serviceName, transactionTypeStatus } = + useApmServiceContext(); const { urlParams: { transactionName, latencyAggregationType }, } = useLegacyUrlParams(); @@ -38,6 +39,10 @@ export function useTransactionLatencyChartsFetcher({ const { data, error, status } = useFetcher( (callApmApi) => { + if (!transactionType && transactionTypeStatus === FETCH_STATUS.SUCCESS) { + return Promise.resolve(undefined); + } + if ( serviceName && start && @@ -76,6 +81,7 @@ export function useTransactionLatencyChartsFetcher({ end, transactionName, transactionType, + transactionTypeStatus, latencyAggregationType, offset, comparisonEnabled, diff --git a/x-pack/plugins/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts b/x-pack/plugins/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts index 85c9550fd230d..636c7b2fde323 100644 --- a/x-pack/plugins/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts +++ b/x-pack/plugins/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts @@ -136,12 +136,12 @@ async function createAnomalyDetectionJob({ ], }); - waitForIndexStatus({ + await waitForIndexStatus({ client: esClient, index: '.ml-*', timeout: DEFAULT_TIMEOUT, status: 'yellow', - }); + })(); return anomalyDetectionJob; }); diff --git a/x-pack/plugins/apm/server/plugin.ts b/x-pack/plugins/apm/server/plugin.ts index 86907fdd570be..5b98c8e437782 100644 --- a/x-pack/plugins/apm/server/plugin.ts +++ b/x-pack/plugins/apm/server/plugin.ts @@ -15,10 +15,10 @@ import { PluginInitializerContext, } from '@kbn/core/server'; import { isEmpty, mapValues } from 'lodash'; -import { mappingFromFieldMap } from '@kbn/rule-registry-plugin/common/mapping_from_field_map'; import { experimentalRuleFieldMap } from '@kbn/rule-registry-plugin/common/assets/field_maps/experimental_rule_field_map'; import { Dataset } from '@kbn/rule-registry-plugin/server'; import { UI_SETTINGS } from '@kbn/data-plugin/common'; +import { mappingFromFieldMap } from '@kbn/alerting-plugin/common'; import { APMConfig, APM_SERVER_FEATURE_ID } from '.'; import { APM_FEATURE, registerFeaturesUsage } from './feature'; import { registerApmRuleTypes } from './routes/alerts/register_apm_rule_types'; @@ -130,25 +130,32 @@ export class APMPlugin ...experimentalRuleFieldMap, [SERVICE_NAME]: { type: 'keyword', + required: false, }, [SERVICE_ENVIRONMENT]: { type: 'keyword', + required: false, }, [TRANSACTION_TYPE]: { type: 'keyword', + required: false, }, [PROCESSOR_EVENT]: { type: 'keyword', + required: false, }, [AGENT_NAME]: { type: 'keyword', + required: false, }, [SERVICE_LANGUAGE_NAME]: { type: 'keyword', + required: false, }, labels: { type: 'object', dynamic: true, + required: false, }, }, 'strict' diff --git a/x-pack/plugins/apm/server/routes/source_maps/schedule_source_map_migration.ts b/x-pack/plugins/apm/server/routes/source_maps/schedule_source_map_migration.ts index 08c8a29ef6712..3ab48db456845 100644 --- a/x-pack/plugins/apm/server/routes/source_maps/schedule_source_map_migration.ts +++ b/x-pack/plugins/apm/server/routes/source_maps/schedule_source_map_migration.ts @@ -44,7 +44,6 @@ export async function scheduleSourceMapMigration({ description: `Migrates fleet source map artifacts to "${APM_SOURCE_MAP_INDEX}" index`, timeout: '1h', maxAttempts: 5, - maxConcurrency: 1, createTaskRunner() { const taskState: TaskState = { isAborted: false }; diff --git a/x-pack/plugins/canvas/kibana.jsonc b/x-pack/plugins/canvas/kibana.jsonc index 3ef7326dcd460..c52f6628b4fad 100644 --- a/x-pack/plugins/canvas/kibana.jsonc +++ b/x-pack/plugins/canvas/kibana.jsonc @@ -29,13 +29,16 @@ "presentationUtil", "visualizations", "uiActions", - "share" + "share", + "savedObjectsManagement", + "savedObjectsFinder" ], "optionalPlugins": [ "home", "reporting", "spaces", - "usageCollection" + "usageCollection", + "savedObjects", ], "requiredBundles": [ "discover", @@ -43,9 +46,8 @@ "kibanaUtils", "lens", "maps", - "savedObjects", "visualizations", "fieldFormats" - ] + ], } } diff --git a/x-pack/plugins/canvas/public/components/embeddable_flyout/flyout.component.tsx b/x-pack/plugins/canvas/public/components/embeddable_flyout/flyout.component.tsx index e92c8277488ca..d9c665646e2b4 100644 --- a/x-pack/plugins/canvas/public/components/embeddable_flyout/flyout.component.tsx +++ b/x-pack/plugins/canvas/public/components/embeddable_flyout/flyout.component.tsx @@ -9,7 +9,7 @@ import React, { FC, useCallback } from 'react'; import { EuiFlyout, EuiFlyoutHeader, EuiFlyoutBody, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { SavedObjectFinderUi, SavedObjectMetaData } from '@kbn/saved-objects-plugin/public'; +import { SavedObjectFinder, SavedObjectMetaData } from '@kbn/saved-objects-finder-plugin/public'; import { useEmbeddablesService, usePlatformService } from '../../services'; const strings = { @@ -38,7 +38,7 @@ export const AddEmbeddableFlyout: FC = ({ const embeddablesService = useEmbeddablesService(); const platformService = usePlatformService(); const { getEmbeddableFactories } = embeddablesService; - const { getHttp, getUISettings } = platformService; + const { getHttp, getUISettings, getSavedObjectsManagement } = platformService; const onAddPanel = useCallback( (id: string, savedObjectType: string) => { @@ -77,13 +77,16 @@ export const AddEmbeddableFlyout: FC = ({ - diff --git a/x-pack/plugins/canvas/public/plugin.tsx b/x-pack/plugins/canvas/public/plugin.tsx index 18facb65202f3..20dee0bf5fa75 100644 --- a/x-pack/plugins/canvas/public/plugin.tsx +++ b/x-pack/plugins/canvas/public/plugin.tsx @@ -30,6 +30,7 @@ import { Start as InspectorStart } from '@kbn/inspector-plugin/public'; import { BfetchPublicSetup } from '@kbn/bfetch-plugin/public'; import { PresentationUtilPluginStart } from '@kbn/presentation-util-plugin/public'; import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; +import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public'; import { featureCatalogueEntry } from './feature_catalogue_entry'; import { CanvasAppLocatorDefinition } from '../common/locator'; import { SESSIONSTORAGE_LASTPATH, CANVAS_APP } from '../common/lib/constants'; @@ -67,6 +68,7 @@ export interface CanvasStartDeps { presentationUtil: PresentationUtilPluginStart; visualizations: VisualizationsStart; spaces?: SpacesPluginStart; + savedObjectsManagement: SavedObjectsManagementPluginStart; } /** diff --git a/x-pack/plugins/canvas/public/services/kibana/platform.ts b/x-pack/plugins/canvas/public/services/kibana/platform.ts index 0b51963eeee31..ab5fb175bd6d1 100644 --- a/x-pack/plugins/canvas/public/services/kibana/platform.ts +++ b/x-pack/plugins/canvas/public/services/kibana/platform.ts @@ -46,5 +46,6 @@ export const platformServiceFactory: CanvaPlatformServiceFactory = ({ getSavedObjectsClient: () => coreStart.savedObjects.client, getUISettings: () => coreStart.uiSettings, getHttp: () => coreStart.http, + getSavedObjectsManagement: () => startPlugins.savedObjectsManagement, }; }; diff --git a/x-pack/plugins/canvas/public/services/platform.ts b/x-pack/plugins/canvas/public/services/platform.ts index b436f51bb33f9..77649a8fa0592 100644 --- a/x-pack/plugins/canvas/public/services/platform.ts +++ b/x-pack/plugins/canvas/public/services/platform.ts @@ -17,6 +17,7 @@ import { } from '@kbn/core/public'; import { SpacesPluginStart } from '@kbn/spaces-plugin/public'; +import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public'; export interface CanvasPlatformService { getBasePath: () => string; @@ -39,4 +40,5 @@ export interface CanvasPlatformService { getSavedObjectsClient: () => SavedObjectsClientContract; getUISettings: () => IUiSettingsClient; getHttp: () => HttpStart; + getSavedObjectsManagement: () => SavedObjectsManagementPluginStart; } diff --git a/x-pack/plugins/canvas/public/services/stubs/platform.ts b/x-pack/plugins/canvas/public/services/stubs/platform.ts index d96599257f9b6..cdb75dc96322b 100644 --- a/x-pack/plugins/canvas/public/services/stubs/platform.ts +++ b/x-pack/plugins/canvas/public/services/stubs/platform.ts @@ -37,4 +37,5 @@ export const platformServiceFactory: CanvasPlatformServiceFactory = () => ({ redirectLegacyUrl: noop, getLegacyUrlConflict: undefined, getHttp: noop, + getSavedObjectsManagement: noop, }); diff --git a/x-pack/plugins/canvas/shareable_runtime/components/__snapshots__/app.test.tsx.snap b/x-pack/plugins/canvas/shareable_runtime/components/__snapshots__/app.test.tsx.snap index 5c431dee43fe6..f9583adaa84c7 100644 --- a/x-pack/plugins/canvas/shareable_runtime/components/__snapshots__/app.test.tsx.snap +++ b/x-pack/plugins/canvas/shareable_runtime/components/__snapshots__/app.test.tsx.snap @@ -1,11 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[` App renders properly 1`] = ` -"
      markdown mock
      markdown mock
      My Canvas Workpad
      " -`; +exports[` App renders properly 1`] = `"
      markdown mock
      markdown mock
      My Canvas Workpad
      "`; diff --git a/x-pack/plugins/canvas/shareable_runtime/components/app.test.tsx b/x-pack/plugins/canvas/shareable_runtime/components/app.test.tsx index b68642d184542..acf71cad3f3ba 100644 --- a/x-pack/plugins/canvas/shareable_runtime/components/app.test.tsx +++ b/x-pack/plugins/canvas/shareable_runtime/components/app.test.tsx @@ -59,8 +59,7 @@ const getWrapper: (name?: WorkpadNames) => ReactWrapper = (name = 'hello') => { return mount(); }; -// FLAKY: https://github.com/elastic/kibana/issues/95899 -describe.skip('', () => { +describe('', () => { test('App renders properly', () => { expect(getWrapper().html()).toMatchSnapshot(); }); diff --git a/x-pack/plugins/canvas/tsconfig.json b/x-pack/plugins/canvas/tsconfig.json index ef10a58a1e65b..228ae010ddf43 100644 --- a/x-pack/plugins/canvas/tsconfig.json +++ b/x-pack/plugins/canvas/tsconfig.json @@ -45,7 +45,6 @@ "@kbn/kibana-react-plugin", "@kbn/kibana-utils-plugin", "@kbn/presentation-util-plugin", - "@kbn/saved-objects-plugin", "@kbn/ui-actions-plugin", "@kbn/usage-collection-plugin", "@kbn/visualizations-plugin", @@ -79,6 +78,8 @@ "@kbn/shared-ux-router", "@kbn/babel-register", "@kbn/shared-ux-button-toolbar", + "@kbn/saved-objects-finder-plugin", + "@kbn/saved-objects-management-plugin", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/cases/common/constants/application.ts b/x-pack/plugins/cases/common/constants/application.ts new file mode 100644 index 0000000000000..4b43a17708ab6 --- /dev/null +++ b/x-pack/plugins/cases/common/constants/application.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 { CASE_VIEW_PAGE_TABS } from '../types'; + +/** + * Application + */ + +export const APP_ID = 'cases' as const; +export const FEATURE_ID = 'generalCases' as const; +export const APP_OWNER = 'cases' as const; +export const APP_PATH = '/app/management/insightsAndAlerting/cases' as const; +export const CASES_CREATE_PATH = '/create' as const; +export const CASES_CONFIGURE_PATH = '/configure' as const; +export const CASE_VIEW_PATH = '/:detailName' as const; +export const CASE_VIEW_COMMENT_PATH = `${CASE_VIEW_PATH}/:commentId` as const; +export const CASE_VIEW_ALERT_TABLE_PATH = + `${CASE_VIEW_PATH}/?tabId=${CASE_VIEW_PAGE_TABS.ALERTS}` as const; +export const CASE_VIEW_TAB_PATH = `${CASE_VIEW_PATH}/?tabId=:tabId` as const; + +/** + * The main Cases application is in the stack management under the + * Alerts and Insights section. To do that, Cases registers to the management + * application. This constant holds the application ID of the management plugin + */ +export const STACK_APP_ID = 'management' as const; diff --git a/x-pack/plugins/cases/common/constants/files.ts b/x-pack/plugins/cases/common/constants/files.ts new file mode 100644 index 0000000000000..7cd7109137976 --- /dev/null +++ b/x-pack/plugins/cases/common/constants/files.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { HttpApiTagOperation, Owner } from './types'; + +export const MAX_FILE_SIZE = 100 * 1024 * 1024; // 100 MiB + +export const constructFilesHttpOperationTag = (owner: Owner, operation: HttpApiTagOperation) => { + return `${owner}FilesCases${operation}`; +}; diff --git a/x-pack/plugins/cases/common/constants.ts b/x-pack/plugins/cases/common/constants/index.ts similarity index 73% rename from x-pack/plugins/cases/common/constants.ts rename to x-pack/plugins/cases/common/constants/index.ts index 601599104641b..f837223e6a07c 100644 --- a/x-pack/plugins/cases/common/constants.ts +++ b/x-pack/plugins/cases/common/constants/index.ts @@ -4,34 +4,15 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { CASE_VIEW_PAGE_TABS } from './types'; -import type { CasesFeaturesAllRequired } from './ui/types'; -export const DEFAULT_DATE_FORMAT = 'dateFormat' as const; -export const DEFAULT_DATE_FORMAT_TZ = 'dateFormat:tz' as const; - -/** - * Application - */ +import type { CasesFeaturesAllRequired } from '../ui/types'; -export const APP_ID = 'cases' as const; -export const FEATURE_ID = 'generalCases' as const; -export const APP_OWNER = 'cases' as const; -export const APP_PATH = '/app/management/insightsAndAlerting/cases' as const; -export const CASES_CREATE_PATH = '/create' as const; -export const CASES_CONFIGURE_PATH = '/configure' as const; -export const CASE_VIEW_PATH = '/:detailName' as const; -export const CASE_VIEW_COMMENT_PATH = `${CASE_VIEW_PATH}/:commentId` as const; -export const CASE_VIEW_ALERT_TABLE_PATH = - `${CASE_VIEW_PATH}/?tabId=${CASE_VIEW_PAGE_TABS.ALERTS}` as const; -export const CASE_VIEW_TAB_PATH = `${CASE_VIEW_PATH}/?tabId=:tabId` as const; +export * from './owners'; +export * from './files'; +export * from './application'; -/** - * The main Cases application is in the stack management under the - * Alerts and Insights section. To do that, Cases registers to the management - * application. This constant holds the application ID of the management plugin - */ -export const STACK_APP_ID = 'management' as const; +export const DEFAULT_DATE_FORMAT = 'dateFormat' as const; +export const DEFAULT_DATE_FORMAT_TZ = 'dateFormat:tz' as const; /** * Saved objects @@ -111,37 +92,6 @@ export const CONNECTORS_URL = `${ACTION_URL}/connectors` as const; */ export const MAX_ALERTS_PER_CASE = 1000 as const; -/** - * Owner - */ -export const SECURITY_SOLUTION_OWNER = 'securitySolution' as const; -export const OBSERVABILITY_OWNER = 'observability' as const; -export const GENERAL_CASES_OWNER = APP_ID; - -export const OWNER_INFO = { - [SECURITY_SOLUTION_OWNER]: { - id: SECURITY_SOLUTION_OWNER, - appId: 'securitySolutionUI', - label: 'Security', - iconType: 'logoSecurity', - appRoute: '/app/security', - }, - [OBSERVABILITY_OWNER]: { - id: OBSERVABILITY_OWNER, - appId: 'observability-overview', - label: 'Observability', - iconType: 'logoObservability', - appRoute: '/app/observability', - }, - [GENERAL_CASES_OWNER]: { - id: GENERAL_CASES_OWNER, - appId: 'management', - label: 'Stack', - iconType: 'casesApp', - appRoute: '/app/management/insightsAndAlerting', - }, -} as const; - /** * Searching */ @@ -186,6 +136,20 @@ export const UPDATE_CASES_CAPABILITY = 'update_cases' as const; export const DELETE_CASES_CAPABILITY = 'delete_cases' as const; export const PUSH_CASES_CAPABILITY = 'push_cases' as const; +/** + * Cases API Tags + */ + +/** + * This tag registered for the cases suggest user profiles API + */ +export const SUGGEST_USER_PROFILES_API_TAG = 'casesSuggestUserProfiles'; + +/** + * This tag is registered for the security bulk get API + */ +export const BULK_GET_USER_PROFILES_API_TAG = 'bulkGetUserProfiles'; + /** * User profiles */ diff --git a/x-pack/plugins/cases/common/constants/mime_types.ts b/x-pack/plugins/cases/common/constants/mime_types.ts new file mode 100644 index 0000000000000..9f1f455513dab --- /dev/null +++ b/x-pack/plugins/cases/common/constants/mime_types.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. + */ + +/** + * These were retrieved from https://www.iana.org/assignments/media-types/media-types.xhtml#image + */ +const imageMimeTypes = [ + 'image/aces', + 'image/apng', + 'image/avci', + 'image/avcs', + 'image/avif', + 'image/bmp', + 'image/cgm', + 'image/dicom-rle', + 'image/dpx', + 'image/emf', + 'image/example', + 'image/fits', + 'image/g3fax', + 'image/heic', + 'image/heic-sequence', + 'image/heif', + 'image/heif-sequence', + 'image/hej2k', + 'image/hsj2', + 'image/jls', + 'image/jp2', + 'image/jpeg', + 'image/jph', + 'image/jphc', + 'image/jpm', + 'image/jpx', + 'image/jxr', + 'image/jxrA', + 'image/jxrS', + 'image/jxs', + 'image/jxsc', + 'image/jxsi', + 'image/jxss', + 'image/ktx', + 'image/ktx2', + 'image/naplps', + 'image/png', + 'image/prs.btif', + 'image/prs.pti', + 'image/pwg-raster', + 'image/svg+xml', + 'image/t38', + 'image/tiff', + 'image/tiff-fx', + 'image/vnd.adobe.photoshop', + 'image/vnd.airzip.accelerator.azv', + 'image/vnd.cns.inf2', + 'image/vnd.dece.graphic', + 'image/vnd.djvu', + 'image/vnd.dwg', + 'image/vnd.dxf', + 'image/vnd.dvb.subtitle', + 'image/vnd.fastbidsheet', + 'image/vnd.fpx', + 'image/vnd.fst', + 'image/vnd.fujixerox.edmics-mmr', + 'image/vnd.fujixerox.edmics-rlc', + 'image/vnd.globalgraphics.pgb', + 'image/vnd.microsoft.icon', + 'image/vnd.mix', + 'image/vnd.ms-modi', + 'image/vnd.mozilla.apng', + 'image/vnd.net-fpx', + 'image/vnd.pco.b16', + 'image/vnd.radiance', + 'image/vnd.sealed.png', + 'image/vnd.sealedmedia.softseal.gif', + 'image/vnd.sealedmedia.softseal.jpg', + 'image/vnd.svf', + 'image/vnd.tencent.tap', + 'image/vnd.valve.source.texture', + 'image/vnd.wap.wbmp', + 'image/vnd.xiff', + 'image/vnd.zbrush.pcx', + 'image/webp', + 'image/wmf', +]; + +const textMimeTypes = ['text/plain', 'text/csv', 'text/json', 'application/json']; + +const compressionMimeTypes = [ + 'application/zip', + 'application/gzip', + 'application/x-bzip', + 'application/x-bzip2', + 'application/x-7z-compressed', + 'application/x-tar', +]; + +const pdfMimeTypes = ['application/pdf']; + +export const ALLOWED_MIME_TYPES = [ + ...imageMimeTypes, + ...textMimeTypes, + ...compressionMimeTypes, + ...pdfMimeTypes, +]; + +export const IMAGE_MIME_TYPES = new Set(imageMimeTypes); diff --git a/x-pack/plugins/cases/common/constants/owners.ts b/x-pack/plugins/cases/common/constants/owners.ts new file mode 100644 index 0000000000000..60463fa57a976 --- /dev/null +++ b/x-pack/plugins/cases/common/constants/owners.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { APP_ID } from './application'; +import type { Owner } from './types'; + +/** + * Owner + */ +export const SECURITY_SOLUTION_OWNER = 'securitySolution' as const; +export const OBSERVABILITY_OWNER = 'observability' as const; +export const GENERAL_CASES_OWNER = APP_ID; + +export const OWNERS = [SECURITY_SOLUTION_OWNER, OBSERVABILITY_OWNER, GENERAL_CASES_OWNER] as const; + +interface RouteInfo { + id: Owner; + appId: string; + label: string; + iconType: string; + appRoute: string; +} + +export const OWNER_INFO: Record = { + [SECURITY_SOLUTION_OWNER]: { + id: SECURITY_SOLUTION_OWNER, + appId: 'securitySolutionUI', + label: 'Security', + iconType: 'logoSecurity', + appRoute: '/app/security', + }, + [OBSERVABILITY_OWNER]: { + id: OBSERVABILITY_OWNER, + appId: 'observability-overview', + label: 'Observability', + iconType: 'logoObservability', + appRoute: '/app/observability', + }, + [GENERAL_CASES_OWNER]: { + id: GENERAL_CASES_OWNER, + appId: 'management', + label: 'Stack', + iconType: 'casesApp', + appRoute: '/app/management/insightsAndAlerting', + }, +} as const; diff --git a/x-pack/plugins/maps/server/maps_telemetry/index_pattern_stats/types.ts b/x-pack/plugins/cases/common/constants/types.ts similarity index 55% rename from x-pack/plugins/maps/server/maps_telemetry/index_pattern_stats/types.ts rename to x-pack/plugins/cases/common/constants/types.ts index 0a0060606b423..27ee99fa95c9a 100644 --- a/x-pack/plugins/maps/server/maps_telemetry/index_pattern_stats/types.ts +++ b/x-pack/plugins/cases/common/constants/types.ts @@ -5,9 +5,12 @@ * 2.0. */ -export interface IndexPatternStats { - indexPatternsWithGeoFieldCount: number; - indexPatternsWithGeoPointFieldCount: number; - indexPatternsWithGeoShapeFieldCount: number; - geoShapeAggLayersCount: number; +import type { OWNERS } from './owners'; + +export enum HttpApiTagOperation { + Read = 'Read', + Create = 'Create', + Delete = 'Delete', } + +export type Owner = typeof OWNERS[number]; diff --git a/x-pack/plugins/cases/common/index.ts b/x-pack/plugins/cases/common/index.ts index 8ca2c23c1eb8d..d41de5543654d 100644 --- a/x-pack/plugins/cases/common/index.ts +++ b/x-pack/plugins/cases/common/index.ts @@ -55,3 +55,4 @@ export { StatusAll } from './ui/types'; export { getCreateConnectorUrl, getAllConnectorsUrl } from './utils/connectors_api'; export { createUICapabilities } from './utils/capabilities'; +export { getApiTags } from './utils/api_tags'; diff --git a/x-pack/plugins/cases/common/utils/__snapshots__/api_tags.test.ts.snap b/x-pack/plugins/cases/common/utils/__snapshots__/api_tags.test.ts.snap new file mode 100644 index 0000000000000..ea1ef29e71c59 --- /dev/null +++ b/x-pack/plugins/cases/common/utils/__snapshots__/api_tags.test.ts.snap @@ -0,0 +1,58 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`api_tags getApiTags constructs the https tags for the owner: cases 1`] = ` +Object { + "all": Array [ + "casesSuggestUserProfiles", + "bulkGetUserProfiles", + "casesFilesCasesCreate", + "casesFilesCasesRead", + ], + "delete": Array [ + "casesFilesCasesDelete", + ], + "read": Array [ + "casesSuggestUserProfiles", + "bulkGetUserProfiles", + "casesFilesCasesRead", + ], +} +`; + +exports[`api_tags getApiTags constructs the https tags for the owner: observability 1`] = ` +Object { + "all": Array [ + "casesSuggestUserProfiles", + "bulkGetUserProfiles", + "observabilityFilesCasesCreate", + "observabilityFilesCasesRead", + ], + "delete": Array [ + "observabilityFilesCasesDelete", + ], + "read": Array [ + "casesSuggestUserProfiles", + "bulkGetUserProfiles", + "observabilityFilesCasesRead", + ], +} +`; + +exports[`api_tags getApiTags constructs the https tags for the owner: securitySolution 1`] = ` +Object { + "all": Array [ + "casesSuggestUserProfiles", + "bulkGetUserProfiles", + "securitySolutionFilesCasesCreate", + "securitySolutionFilesCasesRead", + ], + "delete": Array [ + "securitySolutionFilesCasesDelete", + ], + "read": Array [ + "casesSuggestUserProfiles", + "bulkGetUserProfiles", + "securitySolutionFilesCasesRead", + ], +} +`; diff --git a/x-pack/plugins/cases/common/utils/api_tags.test.ts b/x-pack/plugins/cases/common/utils/api_tags.test.ts new file mode 100644 index 0000000000000..61e8e335be2cf --- /dev/null +++ b/x-pack/plugins/cases/common/utils/api_tags.test.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 { OWNERS } from '../constants'; +import { getApiTags } from './api_tags'; + +describe('api_tags', () => { + describe('getApiTags', () => { + it.each(OWNERS)('constructs the https tags for the owner: %s', (owner) => { + expect(getApiTags(owner)).toMatchSnapshot(); + }); + }); +}); diff --git a/x-pack/plugins/cases/common/utils/api_tags.ts b/x-pack/plugins/cases/common/utils/api_tags.ts new file mode 100644 index 0000000000000..707188a0fba33 --- /dev/null +++ b/x-pack/plugins/cases/common/utils/api_tags.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 { + BULK_GET_USER_PROFILES_API_TAG, + constructFilesHttpOperationTag, + SUGGEST_USER_PROFILES_API_TAG, +} from '../constants'; +import { HttpApiTagOperation } from '../constants/types'; +import type { Owner } from '../constants/types'; + +export const getApiTags = (owner: Owner) => { + const create = constructFilesHttpOperationTag(owner, HttpApiTagOperation.Create); + const deleteTag = constructFilesHttpOperationTag(owner, HttpApiTagOperation.Delete); + const read = constructFilesHttpOperationTag(owner, HttpApiTagOperation.Read); + + return { + all: [SUGGEST_USER_PROFILES_API_TAG, BULK_GET_USER_PROFILES_API_TAG, create, read] as const, + read: [SUGGEST_USER_PROFILES_API_TAG, BULK_GET_USER_PROFILES_API_TAG, read] as const, + delete: [deleteTag] as const, + }; +}; diff --git a/x-pack/plugins/cases/docs/openapi/bundled.json b/x-pack/plugins/cases/docs/openapi/bundled.json index 37647324ccf7a..2a188586b6389 100644 --- a/x-pack/plugins/cases/docs/openapi/bundled.json +++ b/x-pack/plugins/cases/docs/openapi/bundled.json @@ -209,7 +209,7 @@ "/s/{spaceId}/api/cases/_find": { "get": { "summary": "Retrieves a paginated subset of cases.", - "operationId": "getCases", + "operationId": "findCases", "description": "You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're seeking.\n", "tags": [ "cases" @@ -1201,7 +1201,7 @@ "/s/{spaceId}/api/cases/configure/connectors/_find": { "get": { "summary": "Retrieves information about connectors.", - "operationId": "getCaseConnectors", + "operationId": "findCaseConnectors", "description": "In particular, only the connectors that are supported for use in cases are returned. You must have `read` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges.\n", "tags": [ "cases" @@ -1371,7 +1371,7 @@ "get": { "summary": "Returns the number of cases that are open, closed, and in progress.", "operationId": "getCaseStatus", - "description": "You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're seeking.\n", + "description": "Deprecated in 8.1.0. This API is deprecated and will be removed in a future release; use the find cases API instead. You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're seeking.\n", "deprecated": true, "tags": [ "cases" @@ -1525,7 +1525,7 @@ { "in": "query", "name": "includeComments", - "description": "Determines whether case comments are returned.", + "description": "Deprecated in 8.1.0. This parameter is deprecated and will be removed in a future release. It determines whether case comments are returned.", "deprecated": true, "schema": { "type": "boolean", @@ -1806,7 +1806,7 @@ "get": { "summary": "Retrieves all the comments from a case.", "operationId": "getAllCaseComments", - "description": "You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases with the comments you're seeking.\n", + "description": "Deprecated in 8.1.0. This API is deprecated and will be removed in a future release; instead, use the get case comment API, which requires a comment identifier in the path. You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases with the comments you're seeking.\n", "deprecated": true, "tags": [ "cases" @@ -2034,7 +2034,7 @@ "/s/{spaceId}/api/cases/{caseId}/user_actions": { "get": { "summary": "Returns all user activity for a case.", - "description": "You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're seeking.\n", + "description": "Deprecated in 8.1.0. This API is deprecated and will be removed in a future release; use the find user actions API instead. You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're seeking.\n", "deprecated": true, "operationId": "getCaseActivity", "tags": [ diff --git a/x-pack/plugins/cases/docs/openapi/bundled.yaml b/x-pack/plugins/cases/docs/openapi/bundled.yaml index 8098a2d8787ff..511898bfff1cd 100644 --- a/x-pack/plugins/cases/docs/openapi/bundled.yaml +++ b/x-pack/plugins/cases/docs/openapi/bundled.yaml @@ -124,7 +124,7 @@ paths: /s/{spaceId}/api/cases/_find: get: summary: Retrieves a paginated subset of cases. - operationId: getCases + operationId: findCases description: | You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're seeking. tags: @@ -783,7 +783,7 @@ paths: /s/{spaceId}/api/cases/configure/connectors/_find: get: summary: Retrieves information about connectors. - operationId: getCaseConnectors + operationId: findCaseConnectors description: | In particular, only the connectors that are supported for use in cases are returned. You must have `read` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges. tags: @@ -889,7 +889,7 @@ paths: summary: Returns the number of cases that are open, closed, and in progress. operationId: getCaseStatus description: | - You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're seeking. + Deprecated in 8.1.0. This API is deprecated and will be removed in a future release; use the find cases API instead. You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're seeking. deprecated: true tags: - cases @@ -977,7 +977,7 @@ paths: - $ref: '#/components/parameters/space_id' - in: query name: includeComments - description: Determines whether case comments are returned. + description: Deprecated in 8.1.0. This parameter is deprecated and will be removed in a future release. It determines whether case comments are returned. deprecated: true schema: type: boolean @@ -1139,7 +1139,7 @@ paths: summary: Retrieves all the comments from a case. operationId: getAllCaseComments description: | - You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases with the comments you're seeking. + Deprecated in 8.1.0. This API is deprecated and will be removed in a future release; instead, use the get case comment API, which requires a comment identifier in the path. You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases with the comments you're seeking. deprecated: true tags: - cases @@ -1263,7 +1263,7 @@ paths: get: summary: Returns all user activity for a case. description: | - You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're seeking. + Deprecated in 8.1.0. This API is deprecated and will be removed in a future release; use the find user actions API instead. You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're seeking. deprecated: true operationId: getCaseActivity tags: diff --git a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@_find.yaml b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@_find.yaml index f64fdbf0b9bf8..6e61d14a27110 100644 --- a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@_find.yaml +++ b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@_find.yaml @@ -1,6 +1,6 @@ get: summary: Retrieves a paginated subset of cases. - operationId: getCases + operationId: findCases description: > You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana diff --git a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@configure@connectors@_find.yaml b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@configure@connectors@_find.yaml index 7cdd1bf63ae61..bfe60072b8f50 100644 --- a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@configure@connectors@_find.yaml +++ b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@configure@connectors@_find.yaml @@ -1,6 +1,6 @@ get: summary: Retrieves information about connectors. - operationId: getCaseConnectors + operationId: findCaseConnectors description: > In particular, only the connectors that are supported for use in cases are returned. You must have `read` privileges for the **Actions and Connectors** diff --git a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@status.yaml b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@status.yaml index a9db413e2eaef..d3c4b40fa3aea 100644 --- a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@status.yaml +++ b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@status.yaml @@ -2,6 +2,7 @@ get: summary: Returns the number of cases that are open, closed, and in progress. operationId: getCaseStatus description: > + Deprecated in 8.1.0. This API is deprecated and will be removed in a future release; use the find cases API instead. You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're seeking. diff --git a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}.yaml b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}.yaml index aca09f4a74420..856a5dc24096f 100644 --- a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}.yaml +++ b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}.yaml @@ -12,7 +12,7 @@ get: - $ref: '../components/parameters/space_id.yaml' - in: query name: includeComments - description: Determines whether case comments are returned. + description: Deprecated in 8.1.0. This parameter is deprecated and will be removed in a future release. It determines whether case comments are returned. deprecated: true schema: type: boolean diff --git a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@comments.yaml b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@comments.yaml index 1b69926377ffb..9c50a70619b41 100644 --- a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@comments.yaml +++ b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@comments.yaml @@ -111,6 +111,8 @@ get: summary: Retrieves all the comments from a case. operationId: getAllCaseComments description: > + Deprecated in 8.1.0. This API is deprecated and will be removed in a future release; + instead, use the get case comment API, which requires a comment identifier in the path. You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases with the comments you're seeking. diff --git a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@user_actions.yaml b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@user_actions.yaml index a21988cd5434f..4aa52bdbc44b5 100644 --- a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@user_actions.yaml +++ b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@user_actions.yaml @@ -1,6 +1,7 @@ get: summary: Returns all user activity for a case. description: > + Deprecated in 8.1.0. This API is deprecated and will be removed in a future release; use the find user actions API instead. You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're seeking. diff --git a/x-pack/plugins/cases/kibana.jsonc b/x-pack/plugins/cases/kibana.jsonc index 94263c383745f..afed1cd7631f8 100644 --- a/x-pack/plugins/cases/kibana.jsonc +++ b/x-pack/plugins/cases/kibana.jsonc @@ -25,7 +25,8 @@ "management", "security", "notifications", - "ruleRegistry" + "ruleRegistry", + "files", ], "optionalPlugins": [ "home", diff --git a/x-pack/plugins/cases/public/components/case_view/components/case_view_activity.test.tsx b/x-pack/plugins/cases/public/components/case_view/components/case_view_activity.test.tsx index c454a864d9af5..cb2b501ed4041 100644 --- a/x-pack/plugins/cases/public/components/case_view/components/case_view_activity.test.tsx +++ b/x-pack/plugins/cases/public/components/case_view/components/case_view_activity.test.tsx @@ -111,7 +111,10 @@ const usePostPushToServiceMock = usePostPushToService as jest.Mock; const useGetCaseConnectorsMock = useGetCaseConnectors as jest.Mock; const useGetCaseUsersMock = useGetCaseUsers as jest.Mock; -describe('Case View Page activity tab', () => { +// FLAKY: https://github.com/elastic/kibana/issues/151979 +// FLAKY: https://github.com/elastic/kibana/issues/151980 +// FLAKY: https://github.com/elastic/kibana/issues/151981 +describe.skip('Case View Page activity tab', () => { const caseConnectors = getCaseConnectorsMockResponse(); beforeAll(() => { diff --git a/x-pack/plugins/cases/public/components/configure_cases/index.tsx b/x-pack/plugins/cases/public/components/configure_cases/index.tsx index e44f61315641c..0f1ee58680129 100644 --- a/x-pack/plugins/cases/public/components/configure_cases/index.tsx +++ b/x-pack/plugins/cases/public/components/configure_cases/index.tsx @@ -94,6 +94,20 @@ export const ConfigureCases: React.FC = React.memo(() => { [refetchActionTypes, refetchCaseConfigure, refetchConnectors, setEditedConnectorItem] ); + const onConnectorCreated = useCallback( + async (createdConnector) => { + const caseConnector = normalizeActionConnector(createdConnector); + + await persistCaseConfigure({ + connector: caseConnector, + closureType, + }); + onConnectorUpdated(createdConnector); + setConnector(caseConnector); + }, + [onConnectorUpdated, closureType, setConnector, persistCaseConfigure] + ); + const isLoadingAny = isLoadingConnectors || persistLoading || loadingCaseConfigure || isLoadingActionTypes; const updateConnectorDisabled = isLoadingAny || !connectorIsValid || connector.id === 'none'; @@ -168,7 +182,7 @@ export const ConfigureCases: React.FC = React.memo(() => { ? triggersActionsUi.getAddConnectorFlyout({ onClose: onCloseAddFlyout, featureId: CasesConnectorFeatureId, - onConnectorCreated: onConnectorUpdated, + onConnectorCreated, }) : null, // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/x-pack/plugins/cases/public/components/description/description_wrapper.test.tsx b/x-pack/plugins/cases/public/components/description/description_wrapper.test.tsx index b4fb76b8dc94b..40fcf91ffe1fa 100644 --- a/x-pack/plugins/cases/public/components/description/description_wrapper.test.tsx +++ b/x-pack/plugins/cases/public/components/description/description_wrapper.test.tsx @@ -33,7 +33,8 @@ jest.mock('../../common/lib/kibana'); const useUpdateCommentMock = useUpdateComment as jest.Mock; const patchComment = jest.fn(); -describe(`DescriptionWrapper`, () => { +// FLAKY: +describe.skip(`DescriptionWrapper`, () => { const sampleData = { content: 'what a great comment update', }; diff --git a/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/plugin.tsx b/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/plugin.tsx index 71750bbc2a515..ae88e17637627 100644 --- a/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/plugin.tsx +++ b/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/plugin.tsx @@ -28,11 +28,11 @@ import styled from 'styled-components'; import type { TypedLensByValueInput } from '@kbn/lens-plugin/public'; import type { EmbeddablePackageState } from '@kbn/embeddable-plugin/public'; +import { SavedObjectFinderUi } from '@kbn/saved-objects-plugin/public'; import { useKibana } from '../../../../common/lib/kibana'; import { DRAFT_COMMENT_STORAGE_ID, ID } from './constants'; import { CommentEditorContext } from '../../context'; import { ModalContainer } from './modal_container'; -import { SavedObjectFinderUi } from './saved_objects_finder'; import { useLensDraftComment } from './use_lens_draft_comment'; import { VISUALIZATION } from './translations'; import { useIsMainApplication } from '../../../../common/hooks'; @@ -72,7 +72,7 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ embeddable, lens, storage, - savedObjects, + http, uiSettings, data: { query: { @@ -362,9 +362,8 @@ const LensEditorComponent: LensEuiMarkdownEditorUiPlugin['editor'] = ({ savedObjectMetaData={savedObjectMetaData} fixedPageSize={10} uiSettings={uiSettings} - savedObjects={savedObjects} + http={http} euiFieldSearchProps={euiFieldSearchProps} - // @ts-expect-error update types euiFormRowProps={euiFormRowProps} /> diff --git a/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx b/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx deleted file mode 100644 index 5b96ea377fc74..0000000000000 --- a/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx +++ /dev/null @@ -1,555 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -// TODO: merge with src/plugins/saved_objects/public/finder/saved_object_finder.tsx - -import { debounce } from 'lodash'; -import PropTypes from 'prop-types'; -import React from 'react'; - -import type { EuiFieldSearchProps, IconType, EuiFormRowProps } from '@elastic/eui'; -import { - EuiContextMenuItem, - EuiContextMenuPanel, - EuiEmptyPrompt, - EuiFieldSearch, - EuiFilterButton, - EuiFilterGroup, - EuiFlexGroup, - EuiFlexItem, - EuiListGroup, - EuiListGroupItem, - EuiLoadingSpinner, - EuiPagination, - EuiPopover, - EuiSpacer, - EuiTablePagination, - EuiFormRow, -} from '@elastic/eui'; -import type { Direction } from '@elastic/eui/src/services/sort/sort_direction'; -import { i18n } from '@kbn/i18n'; - -import type { SimpleSavedObject, CoreStart } from '@kbn/core/public'; - -import { LISTING_LIMIT_SETTING } from '@kbn/saved-objects-plugin/public'; - -export interface SavedObjectMetaData { - type: string; - name: string; - getIconForSavedObject(savedObject: SimpleSavedObject): IconType; - getTooltipForSavedObject?(savedObject: SimpleSavedObject): string; - showSavedObject?(savedObject: SimpleSavedObject): boolean; - getSavedObjectSubType?(savedObject: SimpleSavedObject): string; - includeFields?: string[]; -} - -interface FinderAttributes { - title?: string; - name?: string; - type: string; -} - -interface SavedObjectFinderState { - items: Array<{ - title: string | null; - name: string | null; - id: SimpleSavedObject['id']; - type: SimpleSavedObject['type']; - savedObject: SimpleSavedObject; - }>; - query: string; - isFetchingItems: boolean; - page: number; - perPage: number; - sortDirection?: Direction; - sortOpen: boolean; - filterOpen: boolean; - filteredTypes: string[]; -} - -interface BaseSavedObjectFinder { - onChoose?: ( - id: SimpleSavedObject['id'], - type: SimpleSavedObject['type'], - name: string, - savedObject: SimpleSavedObject - ) => void; - noItemsMessage?: React.ReactNode; - savedObjectMetaData: Array>; - showFilter?: boolean; - euiFormRowProps?: EuiFormRowProps; - euiFieldSearchProps?: EuiFieldSearchProps; -} - -interface SavedObjectFinderFixedPage extends BaseSavedObjectFinder { - initialPageSize?: undefined; - fixedPageSize: number; -} - -interface SavedObjectFinderInitialPageSize extends BaseSavedObjectFinder { - initialPageSize?: 5 | 10 | 15 | 25; - fixedPageSize?: undefined; -} - -export type SavedObjectFinderProps = SavedObjectFinderFixedPage | SavedObjectFinderInitialPageSize; - -export type SavedObjectFinderUiProps = { - savedObjects: CoreStart['savedObjects']; - uiSettings: CoreStart['uiSettings']; -} & SavedObjectFinderProps; - -// TODO: Fix this manually. Issue #123375 -// eslint-disable-next-line react/display-name -export class SavedObjectFinderUi extends React.Component< - SavedObjectFinderUiProps, - SavedObjectFinderState -> { - public static propTypes = { - onChoose: PropTypes.func, - noItemsMessage: PropTypes.node, - savedObjectMetaData: PropTypes.array.isRequired, - initialPageSize: PropTypes.oneOf([5, 10, 15, 25]), - fixedPageSize: PropTypes.number, - showFilter: PropTypes.bool, - euiFormRowProps: PropTypes.object, - euiFieldSearchProps: PropTypes.object, - }; - - private isComponentMounted: boolean = false; - - private debouncedFetch = debounce(async (query: string) => { - const metaDataMap = this.getSavedObjectMetaDataMap(); - - const fields = Object.values(metaDataMap) - .map((metaData) => metaData.includeFields || []) - .reduce((allFields, currentFields) => allFields.concat(currentFields), ['title', 'name']); - - const perPage = this.props.uiSettings.get(LISTING_LIMIT_SETTING); - const resp = await this.props.savedObjects.client.find({ - type: Object.keys(metaDataMap), - fields: [...new Set(fields)], - search: query ? `${query}*` : undefined, - page: 1, - perPage, - searchFields: ['title^3', 'description'], - defaultSearchOperator: 'AND', - }); - - resp.savedObjects = resp.savedObjects.filter((savedObject) => { - const metaData = metaDataMap[savedObject.type]; - if (metaData.showSavedObject) { - return metaData.showSavedObject(savedObject); - } else { - return true; - } - }); - - if (!this.isComponentMounted) { - return; - } - - // We need this check to handle the case where search results come back in a different - // order than they were sent out. Only load results for the most recent search. - if (query === this.state.query) { - this.setState({ - isFetchingItems: false, - page: 0, - items: resp.savedObjects.map((savedObject) => { - const { - attributes: { name, title }, - id, - type, - } = savedObject; - const titleToUse = typeof title === 'string' ? title : ''; - const nameToUse = name && typeof name === 'string' ? name : titleToUse; - return { - title: titleToUse, - name: nameToUse, - id, - type, - savedObject, - }; - }), - }); - } - }, 300); - - constructor(props: SavedObjectFinderUiProps) { - super(props); - - this.state = { - items: [], - isFetchingItems: false, - page: 0, - perPage: props.initialPageSize || props.fixedPageSize || 10, - query: '', - filterOpen: false, - filteredTypes: [], - sortOpen: false, - }; - } - - public componentWillUnmount() { - this.isComponentMounted = false; - this.debouncedFetch.cancel(); - } - - public componentDidMount() { - this.isComponentMounted = true; - this.fetchItems(); - } - - public render() { - return ( - <> - {this.renderSearchBar()} - {this.renderListing()} - - ); - } - - private getSavedObjectMetaDataMap(): Record { - return this.props.savedObjectMetaData.reduce( - (map, metaData) => ({ ...map, [metaData.type]: metaData }), - {} - ); - } - - private getPageCount() { - return Math.ceil( - (this.state.filteredTypes.length === 0 - ? this.state.items.length - : this.state.items.filter( - (item) => - this.state.filteredTypes.length === 0 || this.state.filteredTypes.includes(item.type) - ).length) / this.state.perPage - ); - } - - // server-side paging not supported - // 1) saved object client does not support sorting by title because title is only mapped as analyzed - // 2) can not search on anything other than title because all other fields are stored in opaque JSON strings, - // for example, visualizations need to be search by isLab but this is not possible in Elasticsearch side - // with the current mappings - private getPageOfItems = () => { - // do not sort original list to preserve elasticsearch ranking order - const items = this.state.items.slice(); - const { sortDirection } = this.state; - - if (sortDirection || !this.state.query) { - items.sort(({ title: titleA }, { title: titleB }) => { - let order = 1; - if (sortDirection === 'desc') { - order = -1; - } - return order * (titleA || '').toLowerCase().localeCompare((titleB || '').toLowerCase()); - }); - } - - // If begin is greater than the length of the sequence, an empty array is returned. - const startIndex = this.state.page * this.state.perPage; - // If end is greater than the length of the sequence, slice extracts through to the end of the sequence (arr.length). - const lastIndex = startIndex + this.state.perPage; - return items - .filter( - (item) => - this.state.filteredTypes.length === 0 || this.state.filteredTypes.includes(item.type) - ) - .slice(startIndex, lastIndex); - }; - - private fetchItems = () => { - this.setState( - { - isFetchingItems: true, - }, - this.debouncedFetch.bind(null, this.state.query) - ); - }; - - private getAvailableSavedObjectMetaData() { - const typesInItems = new Set(); - this.state.items.forEach((item) => { - typesInItems.add(item.type); - }); - return this.props.savedObjectMetaData.filter((metaData) => typesInItems.has(metaData.type)); - } - - private getSortOptions() { - const sortOptions = [ - { - this.setState({ - sortDirection: 'asc', - }); - }} - > - {i18n.translate('xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.sortAsc', { - defaultMessage: 'Ascending', - })} - , - { - this.setState({ - sortDirection: 'desc', - }); - }} - > - {i18n.translate('xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.sortDesc', { - defaultMessage: 'Descending', - })} - , - ]; - if (this.state.query) { - sortOptions.push( - { - this.setState({ - sortDirection: undefined, - }); - }} - > - {i18n.translate('xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.sortAuto', { - defaultMessage: 'Best match', - })} - - ); - } - return sortOptions; - } - - private renderSearchBar() { - const availableSavedObjectMetaData = this.getAvailableSavedObjectMetaData(); - - return ( - - - - { - this.setState( - { - query: e.target.value, - }, - this.fetchItems - ); - }} - data-test-subj="savedObjectFinderSearchInput" - isLoading={this.state.isFetchingItems} - {...(this.props.euiFieldSearchProps || {})} - /> - - - - this.setState({ sortOpen: false })} - button={ - - this.setState(({ sortOpen }) => ({ - sortOpen: !sortOpen, - })) - } - iconType="arrowDown" - isSelected={this.state.sortOpen} - data-test-subj="savedObjectFinderSortButton" - > - {i18n.translate( - 'xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.sortButtonLabel', - { - defaultMessage: 'Sort', - } - )} - - } - > - - - {this.props.showFilter && ( - this.setState({ filterOpen: false })} - button={ - - this.setState(({ filterOpen }) => ({ - filterOpen: !filterOpen, - })) - } - iconType="arrowDown" - data-test-subj="savedObjectFinderFilterButton" - isSelected={this.state.filterOpen} - numFilters={this.props.savedObjectMetaData.length} - hasActiveFilters={this.state.filteredTypes.length > 0} - numActiveFilters={this.state.filteredTypes.length} - > - {i18n.translate( - 'xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.filterButtonLabel', - { - defaultMessage: 'Types', - } - )} - - } - > - ( - { - this.setState(({ filteredTypes }) => ({ - filteredTypes: filteredTypes.includes(metaData.type) - ? filteredTypes.filter((t) => t !== metaData.type) - : [...filteredTypes, metaData.type], - page: 0, - })); - }} - > - {metaData.name} - - ))} - /> - - )} - - - {this.props.children ? ( - {this.props.children} - ) : null} - - - ); - } - - private renderListing() { - const items = this.state.items.length === 0 ? [] : this.getPageOfItems(); - const { onChoose, savedObjectMetaData } = this.props; - - return ( - <> - {this.state.isFetchingItems && this.state.items.length === 0 && ( - - - - - - - )} - {items.length > 0 ? ( - <> - - - {items.map((item) => { - const currentSavedObjectMetaData = savedObjectMetaData.find( - (metaData) => metaData.type === item.type - ); - - if (currentSavedObjectMetaData == null) { - return null; - } - - const fullName = currentSavedObjectMetaData.getTooltipForSavedObject - ? currentSavedObjectMetaData.getTooltipForSavedObject(item.savedObject) - : `${item.name} (${currentSavedObjectMetaData.name})`; - - const iconType = ( - currentSavedObjectMetaData || - ({ - getIconForSavedObject: () => 'document', - } as Pick, 'getIconForSavedObject'>) - ).getIconForSavedObject(item.savedObject); - - return ( - { - onChoose(item.id, item.type, fullName, item.savedObject); - } - : undefined - } - title={fullName} - data-test-subj={`savedObjectTitle${(item.title || '').split(' ').join('-')}`} - /> - ); - })} - - - ) : ( - !this.state.isFetchingItems && - )} - {this.getPageCount() > 1 && - (this.props.fixedPageSize ? ( - { - this.setState({ - page, - }); - }} - /> - ) : ( - { - this.setState({ - page, - }); - }} - onChangeItemsPerPage={(perPage) => { - this.setState({ - perPage, - }); - }} - itemsPerPage={this.state.perPage} - itemsPerPageOptions={[5, 10, 15, 25]} - /> - ))} - - ); - } -} diff --git a/x-pack/plugins/cases/public/files/index.ts b/x-pack/plugins/cases/public/files/index.ts new file mode 100644 index 0000000000000..e06c8eda615a0 --- /dev/null +++ b/x-pack/plugins/cases/public/files/index.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { FilesSetup } from '@kbn/files-plugin/public'; +import type { FileKindBrowser } from '@kbn/shared-ux-file-types'; +import { ALLOWED_MIME_TYPES } from '../../common/constants/mime_types'; +import { MAX_FILE_SIZE } from '../../common/constants'; +import type { Owner } from '../../common/constants/types'; +import { APP_ID, OBSERVABILITY_OWNER, SECURITY_SOLUTION_OWNER } from '../../common'; + +const buildFileKind = (owner: Owner): FileKindBrowser => { + return { + id: owner, + allowedMimeTypes: ALLOWED_MIME_TYPES, + maxSizeBytes: MAX_FILE_SIZE, + }; +}; + +/** + * The file kind definition for interacting with the file service for the UI + */ +const CASES_FILE_KINDS: Record = { + [APP_ID]: buildFileKind(APP_ID), + [SECURITY_SOLUTION_OWNER]: buildFileKind(SECURITY_SOLUTION_OWNER), + [OBSERVABILITY_OWNER]: buildFileKind(OBSERVABILITY_OWNER), +}; + +export const registerCaseFileKinds = (filesSetupPlugin: FilesSetup) => { + for (const fileKind of Object.values(CASES_FILE_KINDS)) { + filesSetupPlugin.registerFileKind(fileKind); + } +}; diff --git a/x-pack/plugins/cases/public/plugin.ts b/x-pack/plugins/cases/public/plugin.ts index 51f2ae92e3094..83b0f2fb0f009 100644 --- a/x-pack/plugins/cases/public/plugin.ts +++ b/x-pack/plugins/cases/public/plugin.ts @@ -27,6 +27,7 @@ import { groupAlertsByRule } from './client/helpers/group_alerts_by_rule'; import { getUICapabilities } from './client/helpers/capabilities'; import { ExternalReferenceAttachmentTypeRegistry } from './client/attachment_framework/external_reference_registry'; import { PersistableStateAttachmentTypeRegistry } from './client/attachment_framework/persistable_state_registry'; +import { registerCaseFileKinds } from './files'; /** * @public @@ -52,6 +53,8 @@ export class CasesUiPlugin const externalReferenceAttachmentTypeRegistry = this.externalReferenceAttachmentTypeRegistry; const persistableStateAttachmentTypeRegistry = this.persistableStateAttachmentTypeRegistry; + registerCaseFileKinds(plugins.files); + if (plugins.home) { plugins.home.featureCatalogue.register({ id: APP_ID, diff --git a/x-pack/plugins/cases/public/types.ts b/x-pack/plugins/cases/public/types.ts index 732fcfee5f0d6..f430b99ae17f2 100644 --- a/x-pack/plugins/cases/public/types.ts +++ b/x-pack/plugins/cases/public/types.ts @@ -22,6 +22,7 @@ import type { TriggersAndActionsUIPublicPluginStart as TriggersActionsStart } fr import type { DistributiveOmit } from '@elastic/eui'; import type { ApmBase } from '@elastic/apm-rum'; import type { LicensingPluginStart } from '@kbn/licensing-plugin/public'; +import type { FilesSetup, FilesStart } from '@kbn/files-plugin/public'; import type { CasesByAlertId, CasesByAlertIDRequest, @@ -50,6 +51,7 @@ import type { ExternalReferenceAttachmentTypeRegistry } from './client/attachmen import type { PersistableStateAttachmentTypeRegistry } from './client/attachment_framework/persistable_state_registry'; export interface CasesPluginSetup { + files: FilesSetup; security: SecurityPluginSetup; management: ManagementSetup; home?: HomePublicPluginSetup; @@ -58,6 +60,7 @@ export interface CasesPluginSetup { export interface CasesPluginStart { data: DataPublicPluginStart; embeddable: EmbeddableStart; + files: FilesStart; licensing?: LicensingPluginStart; lens: LensPublicStart; storage: Storage; diff --git a/x-pack/plugins/cases/server/features.ts b/x-pack/plugins/cases/server/features.ts index 2573e5f58b3f3..05ee00cb1b037 100644 --- a/x-pack/plugins/cases/server/features.ts +++ b/x-pack/plugins/cases/server/features.ts @@ -8,10 +8,11 @@ import { i18n } from '@kbn/i18n'; import type { KibanaFeatureConfig } from '@kbn/features-plugin/common'; +import { hiddenTypes as filesSavedObjectTypes } from '@kbn/files-plugin/server/saved_objects'; import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server'; import { APP_ID, FEATURE_ID } from '../common/constants'; -import { createUICapabilities } from '../common'; +import { createUICapabilities, getApiTags } from '../common'; /** * The order of appearance in the feature privilege page @@ -23,6 +24,7 @@ const FEATURE_ORDER = 3100; export const getCasesKibanaFeature = (): KibanaFeatureConfig => { const capabilities = createUICapabilities(); + const apiTags = getApiTags(APP_ID); return { id: FEATURE_ID, @@ -38,7 +40,7 @@ export const getCasesKibanaFeature = (): KibanaFeatureConfig => { cases: [APP_ID], privileges: { all: { - api: ['casesSuggestUserProfiles', 'bulkGetUserProfiles'], + api: apiTags.all, cases: { create: [APP_ID], read: [APP_ID], @@ -49,13 +51,13 @@ export const getCasesKibanaFeature = (): KibanaFeatureConfig => { insightsAndAlerting: [APP_ID], }, savedObject: { - all: [], - read: [], + all: [...filesSavedObjectTypes], + read: [...filesSavedObjectTypes], }, ui: capabilities.all, }, read: { - api: ['casesSuggestUserProfiles', 'bulkGetUserProfiles'], + api: apiTags.read, cases: { read: [APP_ID], }, @@ -64,7 +66,7 @@ export const getCasesKibanaFeature = (): KibanaFeatureConfig => { }, savedObject: { all: [], - read: [], + read: [...filesSavedObjectTypes], }, ui: capabilities.read, }, @@ -79,7 +81,7 @@ export const getCasesKibanaFeature = (): KibanaFeatureConfig => { groupType: 'independent', privileges: [ { - api: [], + api: apiTags.delete, id: 'cases_delete', name: i18n.translate('xpack.cases.features.deleteSubFeatureDetails', { defaultMessage: 'Delete cases and comments', diff --git a/x-pack/plugins/cases/server/files/index.ts b/x-pack/plugins/cases/server/files/index.ts new file mode 100644 index 0000000000000..fb17fd50fa870 --- /dev/null +++ b/x-pack/plugins/cases/server/files/index.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 type { FileJSON, FileKind } from '@kbn/files-plugin/common'; +import type { FilesSetup } from '@kbn/files-plugin/server'; +import { + APP_ID, + constructFilesHttpOperationTag, + MAX_FILE_SIZE, + OBSERVABILITY_OWNER, + SECURITY_SOLUTION_OWNER, +} from '../../common/constants'; +import type { Owner } from '../../common/constants/types'; +import { HttpApiTagOperation } from '../../common/constants/types'; +import { ALLOWED_MIME_TYPES, IMAGE_MIME_TYPES } from '../../common/constants/mime_types'; + +const buildFileKind = (owner: Owner): FileKind => { + return { + id: owner, + http: fileKindHttpTags(owner), + maxSizeBytes, + allowedMimeTypes: ALLOWED_MIME_TYPES, + }; +}; + +const fileKindHttpTags = (owner: Owner): FileKind['http'] => { + return { + create: buildTag(owner, HttpApiTagOperation.Create), + delete: buildTag(owner, HttpApiTagOperation.Delete), + download: buildTag(owner, HttpApiTagOperation.Read), + getById: buildTag(owner, HttpApiTagOperation.Read), + list: buildTag(owner, HttpApiTagOperation.Read), + }; +}; + +const access = 'access:'; + +const buildTag = (owner: Owner, operation: HttpApiTagOperation) => { + return { + tags: [`${access}${constructFilesHttpOperationTag(owner, operation)}`], + }; +}; + +const MAX_IMAGE_FILE_SIZE = 10 * 1024 * 1024; // 10 MiB + +const maxSizeBytes = (file: FileJSON): number => { + if (file.mimeType != null && IMAGE_MIME_TYPES.has(file.mimeType)) { + return MAX_IMAGE_FILE_SIZE; + } + + return MAX_FILE_SIZE; +}; + +/** + * The file kind definition for interacting with the file service for the backend + */ +const CASES_FILE_KINDS: Record = { + [APP_ID]: buildFileKind(APP_ID), + [SECURITY_SOLUTION_OWNER]: buildFileKind(SECURITY_SOLUTION_OWNER), + [OBSERVABILITY_OWNER]: buildFileKind(OBSERVABILITY_OWNER), +}; + +export const registerCaseFileKinds = (filesSetupPlugin: FilesSetup) => { + for (const fileKind of Object.values(CASES_FILE_KINDS)) { + filesSetupPlugin.registerFileKind(fileKind); + } +}; diff --git a/x-pack/plugins/cases/server/plugin.ts b/x-pack/plugins/cases/server/plugin.ts index a5a1d728311d4..8d2036921d84b 100644 --- a/x-pack/plugins/cases/server/plugin.ts +++ b/x-pack/plugins/cases/server/plugin.ts @@ -14,6 +14,7 @@ import type { CoreStart, } from '@kbn/core/server'; +import type { FilesSetup } from '@kbn/files-plugin/server'; import type { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/server'; import type { PluginSetupContract as ActionsPluginSetup, @@ -56,11 +57,13 @@ import { PersistableStateAttachmentTypeRegistry } from './attachment_framework/p import { ExternalReferenceAttachmentTypeRegistry } from './attachment_framework/external_reference_registry'; import { UserProfileService } from './services'; import { LICENSING_CASE_ASSIGNMENT_FEATURE } from './common/constants'; +import { registerCaseFileKinds } from './files'; export interface PluginsSetup { actions: ActionsPluginSetup; lens: LensServerPluginSetup; features: FeaturesPluginSetup; + files: FilesSetup; security: SecurityPluginSetup; licensing: LicensingPluginSetup; taskManager?: TaskManagerSetupContract; @@ -104,6 +107,8 @@ export class CasePlugin { )}] and plugins [${Object.keys(plugins)}]` ); + registerCaseFileKinds(plugins.files); + this.securityPluginSetup = plugins.security; this.lensEmbeddableFactory = plugins.lens.lensEmbeddableFactory; diff --git a/x-pack/plugins/cases/server/saved_object_types/cases.ts b/x-pack/plugins/cases/server/saved_object_types/cases.ts index e32aa462d7121..1d70808f14db2 100644 --- a/x-pack/plugins/cases/server/saved_object_types/cases.ts +++ b/x-pack/plugins/cases/server/saved_object_types/cases.ts @@ -26,6 +26,7 @@ export const createCaseSavedObjectType = ( namespaceType: 'multiple-isolated', convertToMultiNamespaceTypeVersion: '8.0.0', mappings: { + dynamic: false, properties: { assignees: { properties: { diff --git a/x-pack/plugins/cases/server/saved_object_types/comments.ts b/x-pack/plugins/cases/server/saved_object_types/comments.ts index c795b7d960e61..1d02a26f732c8 100644 --- a/x-pack/plugins/cases/server/saved_object_types/comments.ts +++ b/x-pack/plugins/cases/server/saved_object_types/comments.ts @@ -20,6 +20,7 @@ export const createCaseCommentSavedObjectType = ({ namespaceType: 'multiple-isolated', convertToMultiNamespaceTypeVersion: '8.0.0', mappings: { + dynamic: false, properties: { comment: { type: 'text', @@ -32,115 +33,34 @@ export const createCaseCommentSavedObjectType = ({ }, actions: { properties: { - targets: { - type: 'nested', - properties: { - hostname: { type: 'keyword' }, - endpointId: { type: 'keyword' }, - }, - }, type: { type: 'keyword' }, }, }, alertId: { type: 'keyword', }, - index: { - type: 'keyword', - }, created_at: { type: 'date', }, created_by: { properties: { - full_name: { - type: 'keyword', - }, username: { type: 'keyword', }, - email: { - type: 'keyword', - }, - profile_uid: { - type: 'keyword', - }, - }, - }, - externalReferenceId: { - type: 'keyword', - }, - externalReferenceStorage: { - dynamic: false, - properties: { - // externalReferenceStorage.type - type: { - type: 'keyword', - }, }, }, externalReferenceAttachmentTypeId: { type: 'keyword', }, - externalReferenceMetadata: { - dynamic: false, - properties: {}, - }, persistableStateAttachmentTypeId: { type: 'keyword', }, - persistableStateAttachmentState: { - dynamic: false, - properties: {}, - }, pushed_at: { type: 'date', }, - pushed_by: { - properties: { - username: { - type: 'keyword', - }, - full_name: { - type: 'keyword', - }, - email: { - type: 'keyword', - }, - profile_uid: { - type: 'keyword', - }, - }, - }, - rule: { - properties: { - id: { - type: 'keyword', - }, - name: { - type: 'keyword', - }, - }, - }, updated_at: { type: 'date', }, - updated_by: { - properties: { - username: { - type: 'keyword', - }, - full_name: { - type: 'keyword', - }, - email: { - type: 'keyword', - }, - profile_uid: { - type: 'keyword', - }, - }, - }, }, }, migrations: () => createCommentsMigrations(migrationDeps), diff --git a/x-pack/plugins/cases/server/saved_object_types/configure.ts b/x-pack/plugins/cases/server/saved_object_types/configure.ts index 2ee1e3458c647..879640c4b6e40 100644 --- a/x-pack/plugins/cases/server/saved_object_types/configure.ts +++ b/x-pack/plugins/cases/server/saved_object_types/configure.ts @@ -15,71 +15,17 @@ export const caseConfigureSavedObjectType: SavedObjectsType = { namespaceType: 'multiple-isolated', convertToMultiNamespaceTypeVersion: '8.0.0', mappings: { + dynamic: false, properties: { created_at: { type: 'date', }, - created_by: { - properties: { - email: { - type: 'keyword', - }, - username: { - type: 'keyword', - }, - full_name: { - type: 'keyword', - }, - profile_uid: { - type: 'keyword', - }, - }, - }, - connector: { - properties: { - name: { - type: 'text', - }, - type: { - type: 'keyword', - }, - fields: { - properties: { - key: { - type: 'text', - }, - value: { - type: 'text', - }, - }, - }, - }, - }, closure_type: { type: 'keyword', }, owner: { type: 'keyword', }, - updated_at: { - type: 'date', - }, - updated_by: { - properties: { - email: { - type: 'keyword', - }, - username: { - type: 'keyword', - }, - full_name: { - type: 'keyword', - }, - profile_uid: { - type: 'keyword', - }, - }, - }, }, }, migrations: configureMigrations, diff --git a/x-pack/plugins/cases/server/saved_object_types/connector_mappings.ts b/x-pack/plugins/cases/server/saved_object_types/connector_mappings.ts index 7d89b090847d3..1335570e8a132 100644 --- a/x-pack/plugins/cases/server/saved_object_types/connector_mappings.ts +++ b/x-pack/plugins/cases/server/saved_object_types/connector_mappings.ts @@ -15,20 +15,8 @@ export const caseConnectorMappingsSavedObjectType: SavedObjectsType = { namespaceType: 'multiple-isolated', convertToMultiNamespaceTypeVersion: '8.0.0', mappings: { + dynamic: false, properties: { - mappings: { - properties: { - source: { - type: 'keyword', - }, - target: { - type: 'keyword', - }, - action_type: { - type: 'keyword', - }, - }, - }, owner: { type: 'keyword', }, diff --git a/x-pack/plugins/cases/server/saved_object_types/user_actions.ts b/x-pack/plugins/cases/server/saved_object_types/user_actions.ts index 067ecaaf8fe1e..d8c442b39dab1 100644 --- a/x-pack/plugins/cases/server/saved_object_types/user_actions.ts +++ b/x-pack/plugins/cases/server/saved_object_types/user_actions.ts @@ -18,6 +18,7 @@ export const createCaseUserActionSavedObjectType = ( namespaceType: 'multiple-isolated', convertToMultiNamespaceTypeVersion: '8.0.0', mappings: { + dynamic: false, properties: { action: { type: 'keyword', @@ -27,18 +28,9 @@ export const createCaseUserActionSavedObjectType = ( }, created_by: { properties: { - email: { - type: 'keyword', - }, username: { type: 'keyword', }, - full_name: { - type: 'keyword', - }, - profile_uid: { - type: 'keyword', - }, }, }, payload: { diff --git a/x-pack/plugins/cases/tsconfig.json b/x-pack/plugins/cases/tsconfig.json index a1737094134f2..5ba7e85918975 100644 --- a/x-pack/plugins/cases/tsconfig.json +++ b/x-pack/plugins/cases/tsconfig.json @@ -56,6 +56,8 @@ "@kbn/core-saved-objects-base-server-mocks", "@kbn/core-saved-objects-utils-server", "@kbn/shared-ux-router", + "@kbn/files-plugin", + "@kbn/shared-ux-file-types", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/cloud_defend/README.md b/x-pack/plugins/cloud_defend/README.md index c0175af9cc2a6..9df1a13d328d2 100755 --- a/x-pack/plugins/cloud_defend/README.md +++ b/x-pack/plugins/cloud_defend/README.md @@ -42,6 +42,7 @@ responses: ``` node scripts/type_check.js --project x-pack/plugins/cloud_defend/tsconfig.json +node scripts/eslint.js x-pack/plugins/cloud_defend yarn test:jest x-pack/plugins/cloud_defend ``` diff --git a/x-pack/plugins/cloud_defend/common/constants.ts b/x-pack/plugins/cloud_defend/common/constants.ts index 860f3d4adffea..8fc54773da295 100755 --- a/x-pack/plugins/cloud_defend/common/constants.ts +++ b/x-pack/plugins/cloud_defend/common/constants.ts @@ -4,9 +4,16 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common'; export const PLUGIN_ID = 'cloudDefend'; -export const PLUGIN_NAME = 'cloudDefend'; +export const PLUGIN_NAME = 'Cloud Defend'; export const INTEGRATION_PACKAGE_NAME = 'cloud_defend'; export const INPUT_CONTROL = 'cloud_defend/control'; export const ALERTS_DATASET = 'cloud_defend.alerts'; +export const ALERTS_INDEX_PATTERN = 'cloud_defend.alerts*'; + +export const POLICIES_ROUTE_PATH = '/internal/cloud_defend/policies'; +export const STATUS_ROUTE_PATH = '/internal/cloud_defend/status'; + +export const CLOUD_DEFEND_FLEET_PACKAGE_KUERY = `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${INTEGRATION_PACKAGE_NAME}`; diff --git a/x-pack/plugins/cloud_defend/common/schemas/policy.ts b/x-pack/plugins/cloud_defend/common/schemas/policy.ts new file mode 100644 index 0000000000000..c6fff63b6bb81 --- /dev/null +++ b/x-pack/plugins/cloud_defend/common/schemas/policy.ts @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { type TypeOf, schema } from '@kbn/config-schema'; + +export const DEFAULT_POLICIES_PER_PAGE = 20; +export const POLICIES_PACKAGE_POLICY_PREFIX = 'package_policy.'; +export const policiesQueryParamsSchema = schema.object({ + /** + * The page of objects to return + */ + page: schema.number({ defaultValue: 1, min: 1 }), + /** + * The number of objects to include in each page + */ + per_page: schema.number({ defaultValue: DEFAULT_POLICIES_PER_PAGE, min: 0 }), + /** + * Once of PackagePolicy fields for sorting the found objects. + * Sortable fields: + * - package_policy.id + * - package_policy.name + * - package_policy.policy_id + * - package_policy.namespace + * - package_policy.updated_at + * - package_policy.updated_by + * - package_policy.created_at + * - package_policy.created_by, + * - package_policy.package.name + * - package_policy.package.title + * - package_policy.package.version + */ + sort_field: schema.oneOf( + [ + schema.literal('package_policy.id'), + schema.literal('package_policy.name'), + schema.literal('package_policy.policy_id'), + schema.literal('package_policy.namespace'), + schema.literal('package_policy.updated_at'), + schema.literal('package_policy.updated_by'), + schema.literal('package_policy.created_at'), + schema.literal('package_policy.created_by'), + schema.literal('package_policy.package.name'), + schema.literal('package_policy.package.title'), + ], + { defaultValue: 'package_policy.name' } + ), + /** + * The order to sort by + */ + sort_order: schema.oneOf([schema.literal('asc'), schema.literal('desc')], { + defaultValue: 'asc', + }), + /** + * Policy filter + */ + policy_name: schema.maybe(schema.string()), +}); + +export type PoliciesQueryParams = TypeOf; diff --git a/x-pack/plugins/cloud_defend/common/types.ts b/x-pack/plugins/cloud_defend/common/types.ts new file mode 100644 index 0000000000000..c3bdbb4418183 --- /dev/null +++ b/x-pack/plugins/cloud_defend/common/types.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { PackagePolicy, AgentPolicy } from '@kbn/fleet-plugin/common'; + +export type IndexStatus = + | 'not-empty' // Index contains documents + | 'empty' // Index doesn't contain documents (or doesn't exist) + | 'unprivileged'; // User doesn't have access to query the index + +export type CloudDefendStatusCode = + | 'indexed' // alerts index exists and has results + | 'indexing' // index timeout was not surpassed since installation, assumes data is being indexed + | 'unprivileged' // user lacks privileges for the alerts index + | 'index-timeout' // index timeout was surpassed since installation + | 'not-deployed' // no healthy agents were deployed + | 'not-installed'; // number of installed integrations is 0; + +export interface IndexDetails { + index: string; + status: IndexStatus; +} + +interface BaseCloudDefendSetupStatus { + indicesDetails: IndexDetails[]; + latestPackageVersion: string; + installedPackagePolicies: number; + healthyAgents: number; +} + +interface CloudDefendSetupNotInstalledStatus extends BaseCloudDefendSetupStatus { + status: Extract; +} + +interface CloudDefendSetupInstalledStatus extends BaseCloudDefendSetupStatus { + status: Exclude; + // status can be `indexed` but return with undefined package information in this case + installedPackageVersion: string | undefined; +} + +export type CloudDefendSetupStatus = + | CloudDefendSetupInstalledStatus + | CloudDefendSetupNotInstalledStatus; + +export type AgentPolicyStatus = Pick & { agents: number }; + +export interface CloudDefendPolicy { + package_policy: PackagePolicy; + agent_policy: AgentPolicyStatus; +} diff --git a/x-pack/plugins/cloud_defend/common/utils/helpers.ts b/x-pack/plugins/cloud_defend/common/utils/helpers.ts new file mode 100644 index 0000000000000..14b506e8d2e70 --- /dev/null +++ b/x-pack/plugins/cloud_defend/common/utils/helpers.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 { Truthy } from 'lodash'; +import { INTEGRATION_PACKAGE_NAME } from '../constants'; + +/** + * @example + * declare const foo: Array + * foo.filter(isNonNullable) // foo is Array + */ +export const isNonNullable = (v: T): v is NonNullable => + v !== null && v !== undefined; + +export const truthy = (value: T): value is Truthy => !!value; + +export const extractErrorMessage = (e: unknown, defaultMessage = 'Unknown Error'): string => { + if (e instanceof Error) return e.message; + if (typeof e === 'string') return e; + + return defaultMessage; // TODO: i18n +}; + +export function assert(condition: any, msg?: string): asserts condition { + if (!condition) { + throw new Error(msg); + } +} + +export const isCloudDefendPackage = (packageName?: string) => + packageName === INTEGRATION_PACKAGE_NAME; diff --git a/x-pack/plugins/cloud_defend/common/utils/subscription.test.ts b/x-pack/plugins/cloud_defend/common/utils/subscription.test.ts new file mode 100644 index 0000000000000..e47b887ae520f --- /dev/null +++ b/x-pack/plugins/cloud_defend/common/utils/subscription.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { LicenseType } from '@kbn/licensing-plugin/common/types'; +import { isSubscriptionAllowed } from './subscription'; +import { licenseMock } from '@kbn/licensing-plugin/common/licensing.mock'; + +const ON_PREM_ALLOWED_LICENSES: readonly LicenseType[] = ['enterprise', 'trial']; +const ON_PREM_NOT_ALLOWED_LICENSES: readonly LicenseType[] = ['basic', 'gold', 'platinum']; +const ALL_LICENSE_TYPES: readonly LicenseType[] = [ + 'standard', + ...ON_PREM_NOT_ALLOWED_LICENSES, + ...ON_PREM_NOT_ALLOWED_LICENSES, +]; + +describe('isSubscriptionAllowed', () => { + it('should allow any cloud subscription', () => { + const isCloudEnabled = true; + ALL_LICENSE_TYPES.forEach((licenseType) => { + const license = licenseMock.createLicense({ license: { type: licenseType } }); + expect(isSubscriptionAllowed(isCloudEnabled, license)).toBeTruthy(); + }); + }); + + it('should allow enterprise and trial licenses for on-prem', () => { + const isCloudEnabled = false; + ON_PREM_ALLOWED_LICENSES.forEach((licenseType) => { + const license = licenseMock.createLicense({ license: { type: licenseType } }); + expect(isSubscriptionAllowed(isCloudEnabled, license)).toBeTruthy(); + }); + }); + + it('should not allow enterprise and trial licenses for on-prem', () => { + const isCloudEnabled = false; + ON_PREM_NOT_ALLOWED_LICENSES.forEach((licenseType) => { + const license = licenseMock.createLicense({ license: { type: licenseType } }); + expect(isSubscriptionAllowed(isCloudEnabled, license)).toBeFalsy(); + }); + }); +}); diff --git a/x-pack/plugins/cloud_defend/common/utils/subscription.ts b/x-pack/plugins/cloud_defend/common/utils/subscription.ts new file mode 100644 index 0000000000000..e54eb0c4d4581 --- /dev/null +++ b/x-pack/plugins/cloud_defend/common/utils/subscription.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 type { ILicense, LicenseType } from '@kbn/licensing-plugin/common/types'; +import { PLUGIN_NAME } from '../constants'; + +const MINIMUM_NON_CLOUD_LICENSE_TYPE: LicenseType = 'enterprise'; + +export const isSubscriptionAllowed = (isCloudEnabled?: boolean, license?: ILicense): boolean => { + if (isCloudEnabled) { + return true; + } + + if (!license) { + return false; + } + + const licenseCheck = license.check(PLUGIN_NAME, MINIMUM_NON_CLOUD_LICENSE_TYPE); + return licenseCheck.state === 'valid'; +}; diff --git a/x-pack/plugins/cloud_defend/kibana.jsonc b/x-pack/plugins/cloud_defend/kibana.jsonc index f561c33a5f832..392724467fd70 100644 --- a/x-pack/plugins/cloud_defend/kibana.jsonc +++ b/x-pack/plugins/cloud_defend/kibana.jsonc @@ -2,14 +2,31 @@ "type": "plugin", "id": "@kbn/cloud-defend-plugin", "owner": "@elastic/sec-cloudnative-integrations", - "description": "Defend for Containers", + "description": "Defend for containers (D4C)", "plugin": { "id": "cloudDefend", - "server": false, + "server": true, "browser": true, + "configPath": [ + "xpack", + "cloudDefend" + ], "requiredPlugins": [ + "navigation", + "data", "fleet", - "kibanaReact" + "unifiedSearch", + "kibanaReact", + "cloud", + "security", + "licensing" + ], + "optionalPlugins": [ + "usageCollection" + ], + "requiredBundles": [ + "kibanaReact", + "usageCollection" ] } } diff --git a/x-pack/plugins/cloud_defend/public/application/route.tsx b/x-pack/plugins/cloud_defend/public/application/route.tsx new file mode 100644 index 0000000000000..27959ec0845e5 --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/application/route.tsx @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { Route } from '@kbn/shared-ux-router'; +import { type RouteProps } from 'react-router-dom'; +import { TrackApplicationView } from '@kbn/usage-collection-plugin/public'; +import { cloudDefendPages } from '../common/navigation/constants'; +import { useSecuritySolutionContext } from './security_solution_context'; +import type { CloudDefendPageNavigationItem } from '../common/navigation/types'; + +type CloudDefendRouteProps = Omit & CloudDefendPageNavigationItem; + +// Security SpyRoute can be automatically rendered for pages with static paths, Security will manage everything using the `links` object. +// Pages with dynamic paths are not in the Security `links` object, they must render SpyRoute with the parameters values, if needed. +const STATIC_PATH_PAGE_IDS = Object.fromEntries( + Object.values(cloudDefendPages).map(({ id }) => [id, true]) +); + +export const CloudDefendRoute: React.FC = ({ + id, + children, + component: Component, + disabled = false, + ...cloudDefendRouteProps +}) => { + const SpyRoute = useSecuritySolutionContext()?.getSpyRouteComponent(); + + if (disabled) { + return null; + } + + const routeProps: RouteProps = { + ...cloudDefendRouteProps, + ...(Component && { + render: (renderProps) => ( + + {STATIC_PATH_PAGE_IDS[id] && SpyRoute && } + + + ), + }), + }; + + return {children}; +}; diff --git a/x-pack/plugins/cloud_defend/public/application/router.test.tsx b/x-pack/plugins/cloud_defend/public/application/router.test.tsx new file mode 100644 index 0000000000000..af7766a290fca --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/application/router.test.tsx @@ -0,0 +1,94 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import CloudDefendRouter from './router'; +import React from 'react'; +import { render } from '@testing-library/react'; +import { Router } from 'react-router-dom'; +import type { CloudDefendPage, CloudDefendPageNavigationItem } from '../common/navigation/types'; +import { CloudDefendSecuritySolutionContext } from '../types'; +import { createMemoryHistory, MemoryHistory } from 'history'; +import * as constants from '../common/navigation/constants'; +import { QueryClientProviderProps } from '@tanstack/react-query'; + +jest.mock('../pages/policies', () => ({ + Policies: () =>
      Policies
      , +})); + +jest.mock('@tanstack/react-query', () => ({ + QueryClientProvider: ({ children }: QueryClientProviderProps) => <>{children}, + QueryClient: jest.fn(), +})); + +describe('CloudDefendRouter', () => { + const originalCloudDefendPages = { ...constants.cloudDefendPages }; + const mockConstants = constants as { + cloudDefendPages: Record; + }; + + const securityContext: CloudDefendSecuritySolutionContext = { + getFiltersGlobalComponent: jest.fn(), + getSpyRouteComponent: () => () =>
      , + }; + + let history: MemoryHistory; + + const renderCloudDefendRouter = () => + render( + + + + ); + + beforeEach(() => { + mockConstants.cloudDefendPages = originalCloudDefendPages; + jest.clearAllMocks(); + history = createMemoryHistory(); + }); + + describe('happy path', () => { + it('should render Policies', () => { + history.push('/cloud_defend/policies'); + const result = renderCloudDefendRouter(); + + expect(result.queryByTestId('Policies')).toBeInTheDocument(); + }); + }); + + describe('unhappy path', () => { + it('should redirect base path to policies', () => { + history.push('/cloud_defend/some_wrong_path'); + const result = renderCloudDefendRouter(); + + expect(history.location.pathname).toEqual('/cloud_defend/policies'); + expect(result.queryByTestId('Policies')).toBeInTheDocument(); + }); + }); + + describe('CloudDefendRoute', () => { + it('should not render disabled path', () => { + mockConstants.cloudDefendPages = { + ...constants.cloudDefendPages, + policies: { + ...constants.cloudDefendPages.policies, + disabled: true, + }, + }; + + history.push('/cloud_defend/policies'); + const result = renderCloudDefendRouter(); + + expect(result.queryByTestId('Policies')).not.toBeInTheDocument(); + }); + + it('should render SpyRoute for static paths', () => { + history.push('/cloud_defend/policies'); + const result = renderCloudDefendRouter(); + + expect(result.queryByTestId('mockedSpyRoute')).toBeInTheDocument(); + }); + }); +}); diff --git a/x-pack/plugins/cloud_defend/public/application/router.tsx b/x-pack/plugins/cloud_defend/public/application/router.tsx new file mode 100644 index 0000000000000..3fa261d35c765 --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/application/router.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 { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { Redirect, Switch } from 'react-router-dom'; +import { Route } from '@kbn/shared-ux-router'; +import { cloudDefendPages } from '../common/navigation/constants'; +import type { CloudDefendSecuritySolutionContext } from '../types'; +import { SecuritySolutionContext } from './security_solution_context'; +import { Policies } from '../pages/policies'; +import { CloudDefendRoute } from './route'; + +const queryClient = new QueryClient({ + defaultOptions: { queries: { refetchOnWindowFocus: false } }, +}); + +export interface CloudDefendRouterProps { + securitySolutionContext?: CloudDefendSecuritySolutionContext; +} + +export const CloudDefendRouter = ({ securitySolutionContext }: CloudDefendRouterProps) => { + const routerElement = ( + + + + + + + + + + ); + + if (securitySolutionContext) { + return ( + + {routerElement} + + ); + } + + return <>{routerElement}; +}; + +// Using a default export for usage with `React.lazy` +// eslint-disable-next-line import/no-default-export +export { CloudDefendRouter as default }; diff --git a/x-pack/plugins/cloud_defend/public/application/security_solution_context.ts b/x-pack/plugins/cloud_defend/public/application/security_solution_context.ts new file mode 100644 index 0000000000000..fcbd10057021c --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/application/security_solution_context.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 React, { useContext } from 'react'; +import type { CloudDefendSecuritySolutionContext } from '../types'; + +export const SecuritySolutionContext = React.createContext< + CloudDefendSecuritySolutionContext | undefined +>(undefined); + +export const useSecuritySolutionContext = () => { + return useContext(SecuritySolutionContext); +}; diff --git a/x-pack/plugins/cloud_defend/public/application/setup_context.ts b/x-pack/plugins/cloud_defend/public/application/setup_context.ts new file mode 100644 index 0000000000000..574404ace38ce --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/application/setup_context.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 { createContext } from 'react'; + +interface SetupContextValue { + isCloudEnabled?: boolean; +} + +/** + * A utility to pass data from the plugin setup lifecycle stage to application components + */ +export const SetupContext = createContext({}); diff --git a/x-pack/plugins/cloud_defend/public/assets/icons/logo.svg b/x-pack/plugins/cloud_defend/public/assets/icons/logo.svg new file mode 100644 index 0000000000000..a0534292eb717 --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/assets/icons/logo.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/x-pack/plugins/cloud_defend/public/common/api/use_cloud_defend_integration.tsx b/x-pack/plugins/cloud_defend/public/common/api/use_cloud_defend_integration.tsx new file mode 100644 index 0000000000000..c41ffdab0851c --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/common/api/use_cloud_defend_integration.tsx @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useQuery } from '@tanstack/react-query'; +import { + epmRouteService, + type GetInfoResponse, + type DefaultPackagesInstallationError, +} from '@kbn/fleet-plugin/common'; +import { INTEGRATION_PACKAGE_NAME } from '../../../common/constants'; +import { useKibana } from '../hooks/use_kibana'; + +/** + * This hook will find our integration and return its PackageInfo + * */ +export const useCloudDefendIntegration = () => { + const { http } = useKibana().services; + + return useQuery(['integrations'], () => + http.get(epmRouteService.getInfoPath(INTEGRATION_PACKAGE_NAME)) + ); +}; diff --git a/x-pack/plugins/cloud_defend/public/common/api/use_setup_status_api.ts b/x-pack/plugins/cloud_defend/public/common/api/use_setup_status_api.ts new file mode 100644 index 0000000000000..2d07d138e8d92 --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/common/api/use_setup_status_api.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useQuery } from '@tanstack/react-query'; +import { useKibana } from '../hooks/use_kibana'; +import { CloudDefendSetupStatus } from '../../../common/types'; +import { STATUS_ROUTE_PATH } from '../../../common/constants'; + +const getCloudDefendSetupStatusQueryKey = 'cloud_defend_status_key'; + +export const useCloudDefendSetupStatusApi = () => { + const { http } = useKibana().services; + return useQuery( + [getCloudDefendSetupStatusQueryKey], + () => http.get(STATUS_ROUTE_PATH) + ); +}; diff --git a/x-pack/plugins/cloud_defend/public/common/constants.ts b/x-pack/plugins/cloud_defend/public/common/constants.ts index d0baec8804ff7..1e101a47c0122 100644 --- a/x-pack/plugins/cloud_defend/public/common/constants.ts +++ b/x-pack/plugins/cloud_defend/public/common/constants.ts @@ -4,6 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +export const DEFAULT_VISIBLE_ROWS_PER_PAGE = 10; // generic default # of table rows to show (currently we only have a list of policies) +export const LOCAL_STORAGE_PAGE_SIZE = 'cloudDefend:userPageSize'; export const VALID_SELECTOR_NAME_REGEX = /^[a-z0-9][a-z0-9_\-]+$/i; // alphanumberic (no - or _ allowed on first char) export const MAX_SELECTOR_NAME_LENGTH = 128; // chars export const MAX_CONDITION_VALUE_LENGTH_BYTES = 511; diff --git a/x-pack/plugins/cloud_defend/public/common/hooks/use_kibana.ts b/x-pack/plugins/cloud_defend/public/common/hooks/use_kibana.ts new file mode 100644 index 0000000000000..261afc07db00f --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/common/hooks/use_kibana.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { CoreStart } from '@kbn/core/public'; +import { useKibana as useKibanaBase } from '@kbn/kibana-react-plugin/public'; +import type { CloudDefendPluginStartDeps } from '../../types'; + +type CloudDefendKibanaContext = CoreStart & CloudDefendPluginStartDeps; + +export const useKibana = () => useKibanaBase(); diff --git a/x-pack/plugins/cloud_defend/public/common/hooks/use_page_size.ts b/x-pack/plugins/cloud_defend/public/common/hooks/use_page_size.ts new file mode 100644 index 0000000000000..314dfbe661d93 --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/common/hooks/use_page_size.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import useLocalStorage from 'react-use/lib/useLocalStorage'; +import { DEFAULT_VISIBLE_ROWS_PER_PAGE } from '../constants'; + +/** + * @description handles persisting the users table row size selection + */ +export const usePageSize = (localStorageKey: string) => { + const [persistedPageSize, setPersistedPageSize] = useLocalStorage( + localStorageKey, + DEFAULT_VISIBLE_ROWS_PER_PAGE + ); + + let pageSize: number = DEFAULT_VISIBLE_ROWS_PER_PAGE; + + if (persistedPageSize) { + pageSize = persistedPageSize; + } + + return { pageSize, setPageSize: setPersistedPageSize }; +}; diff --git a/x-pack/plugins/cloud_defend/public/common/hooks/use_subscription_status.ts b/x-pack/plugins/cloud_defend/public/common/hooks/use_subscription_status.ts new file mode 100644 index 0000000000000..26dd4caa33c4d --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/common/hooks/use_subscription_status.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 { useContext } from 'react'; +import { useQuery } from '@tanstack/react-query'; +import { SetupContext } from '../../application/setup_context'; +import { isSubscriptionAllowed } from '../../../common/utils/subscription'; +import { useKibana } from './use_kibana'; + +const SUBSCRIPTION_QUERY_KEY = 'cloud_defend_subscription_query_key'; + +export const useSubscriptionStatus = () => { + const { licensing } = useKibana().services; + const { isCloudEnabled } = useContext(SetupContext); + return useQuery([SUBSCRIPTION_QUERY_KEY], async () => { + const license = await licensing.refresh(); + return isSubscriptionAllowed(isCloudEnabled, license); + }); +}; diff --git a/x-pack/plugins/cloud_defend/public/common/navigation/constants.ts b/x-pack/plugins/cloud_defend/public/common/navigation/constants.ts new file mode 100644 index 0000000000000..b166184dddb87 --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/common/navigation/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 { i18n } from '@kbn/i18n'; +import type { CloudDefendPage, CloudDefendPageNavigationItem } from './types'; + +const NAV_ITEMS_NAMES = { + POLICIES: i18n.translate('xpack.cloudDefend.navigation.policiesNavItemLabel', { + defaultMessage: 'Defend for containers (D4C)', + }), +}; + +/** The base path for all cloud defend pages. */ +export const CLOUD_DEFEND_BASE_PATH = '/cloud_defend'; + +export const cloudDefendPages: Record = { + policies: { + name: NAV_ITEMS_NAMES.POLICIES, + path: `${CLOUD_DEFEND_BASE_PATH}/policies`, + id: 'cloud_defend-policies', + }, +}; diff --git a/x-pack/plugins/cloud_defend/public/common/navigation/security_solution_links.test.ts b/x-pack/plugins/cloud_defend/public/common/navigation/security_solution_links.test.ts new file mode 100644 index 0000000000000..f6cfe42d0583a --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/common/navigation/security_solution_links.test.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { cloudDefendPages } from './constants'; +import { getSecuritySolutionLink, getSecuritySolutionNavTab } from './security_solution_links'; +import { Chance } from 'chance'; +import type { CloudDefendPage } from './types'; + +const chance = new Chance(); + +describe('getSecuritySolutionLink', () => { + it('gets the correct link properties', () => { + const cloudDefendPage = chance.pickone(['policies']); + + const link = getSecuritySolutionLink(cloudDefendPage); + + expect(link.id).toEqual(cloudDefendPages[cloudDefendPage].id); + expect(link.path).toEqual(cloudDefendPages[cloudDefendPage].path); + expect(link.title).toEqual(cloudDefendPages[cloudDefendPage].name); + }); +}); + +describe('getSecuritySolutionNavTab', () => { + it('gets the correct nav tab properties', () => { + const cloudDefendPage = chance.pickone(['policies']); + const basePath = chance.word(); + + const navTab = getSecuritySolutionNavTab(cloudDefendPage, basePath); + + expect(navTab.id).toEqual(cloudDefendPages[cloudDefendPage].id); + expect(navTab.name).toEqual(cloudDefendPages[cloudDefendPage].name); + expect(navTab.href).toEqual(`${basePath}${cloudDefendPages[cloudDefendPage].path}`); + expect(navTab.disabled).toEqual(!!cloudDefendPages[cloudDefendPage].disabled); + }); +}); diff --git a/x-pack/plugins/cloud_defend/public/common/navigation/security_solution_links.ts b/x-pack/plugins/cloud_defend/public/common/navigation/security_solution_links.ts new file mode 100644 index 0000000000000..58e816c135593 --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/common/navigation/security_solution_links.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 { cloudDefendPages } from './constants'; +import type { CloudDefendPageId, CloudDefendPage } from './types'; + +interface CloudDefendLinkItem { + id: TId; + title: string; + path: string; +} + +interface CloudDefendNavTab { + id: TId; + name: string; + href: string; + disabled: boolean; +} + +/** + * Gets the cloud_defend link properties of a Cloud Defend page for navigation in the security solution. + * @param cloudDefendPage the name of the cloud defend page. + */ +export const getSecuritySolutionLink = ( + cloudDefendPage: CloudDefendPage +): CloudDefendLinkItem => { + return { + id: cloudDefendPages[cloudDefendPage].id as TId, + title: cloudDefendPages[cloudDefendPage].name, + path: cloudDefendPages[cloudDefendPage].path, + }; +}; + +/** + * Gets the link properties of a Cloud Defend page for navigation in the old security solution navigation. + * @param cloudDefendPage the name of the cloud defend page. + * @param basePath the base path for links. + */ +export const getSecuritySolutionNavTab = ( + cloudDefendPage: CloudDefendPage, + basePath: string +): CloudDefendNavTab => ({ + id: cloudDefendPages[cloudDefendPage].id as TId, + name: cloudDefendPages[cloudDefendPage].name, + href: `${basePath}${cloudDefendPages[cloudDefendPage].path}`, + disabled: !!cloudDefendPages[cloudDefendPage].disabled, +}); diff --git a/x-pack/plugins/cloud_defend/public/common/navigation/types.ts b/x-pack/plugins/cloud_defend/public/common/navigation/types.ts new file mode 100644 index 0000000000000..56da6ac4c3948 --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/common/navigation/types.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 interface CloudDefendNavigationItem { + readonly name: string; + readonly path: string; + readonly disabled?: boolean; +} + +export interface CloudDefendPageNavigationItem extends CloudDefendNavigationItem { + id: CloudDefendPageId; +} + +export type CloudDefendPage = 'policies'; + +/** + * All the IDs for the cloud defend pages. + * This needs to match the cloud defend page entries in `SecurityPageName` in `x-pack/plugins/security_solution/common/constants.ts`. + */ +export type CloudDefendPageId = 'cloud_defend-policies'; diff --git a/x-pack/plugins/cloud_defend/public/common/navigation/use_cloud_defend_integration_links.ts b/x-pack/plugins/cloud_defend/public/common/navigation/use_cloud_defend_integration_links.ts new file mode 100644 index 0000000000000..b2d0cca31fcba --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/common/navigation/use_cloud_defend_integration_links.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { pagePathGetters, pkgKeyFromPackageInfo } from '@kbn/fleet-plugin/public'; +import { INTEGRATION_PACKAGE_NAME } from '../../../common/constants'; +import { useCloudDefendIntegration } from '../api/use_cloud_defend_integration'; +import { useKibana } from '../hooks/use_kibana'; + +export const useCloudDefendIntegrationLinks = (): { + addIntegrationLink: string | undefined; + docsLink: string; +} => { + const { http } = useKibana().services; + const cloudDefendIntegration = useCloudDefendIntegration(); + + if (!cloudDefendIntegration.isSuccess) + return { + addIntegrationLink: undefined, + docsLink: 'https://www.elastic.co/guide/index.html', + }; + + const addIntegrationLink = pagePathGetters + .add_integration_to_policy({ + integration: INTEGRATION_PACKAGE_NAME, + pkgkey: pkgKeyFromPackageInfo({ + name: cloudDefendIntegration.data.item.name, + version: cloudDefendIntegration.data.item.version, + }), + }) + .join(''); + + const docsLink = pagePathGetters + .integration_details_overview({ + integration: INTEGRATION_PACKAGE_NAME, + pkgkey: pkgKeyFromPackageInfo({ + name: cloudDefendIntegration.data.item.name, + version: cloudDefendIntegration.data.item.version, + }), + }) + .join(''); + + return { + addIntegrationLink: http.basePath.prepend(addIntegrationLink), + docsLink: http.basePath.prepend(docsLink), + }; +}; diff --git a/x-pack/plugins/cloud_defend/public/components/cloud_defend_page/index.test.tsx b/x-pack/plugins/cloud_defend/public/components/cloud_defend_page/index.test.tsx new file mode 100644 index 0000000000000..4c620a73dcfa9 --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/components/cloud_defend_page/index.test.tsx @@ -0,0 +1,361 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useSubscriptionStatus } from '../../common/hooks/use_subscription_status'; +import Chance from 'chance'; +import { + CloudDefendPage, + DEFAULT_NO_DATA_TEST_SUBJECT, + ERROR_STATE_TEST_SUBJECT, + isCommonError, + LOADING_STATE_TEST_SUBJECT, + PACKAGE_NOT_INSTALLED_TEST_SUBJECT, + SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT, +} from '.'; +import { createReactQueryResponse } from '../../test/fixtures/react_query'; +import { TestProvider } from '../../test/test_provider'; +import { coreMock } from '@kbn/core/public/mocks'; +import { render, screen } from '@testing-library/react'; +import React, { ComponentProps } from 'react'; +import { UseQueryResult } from '@tanstack/react-query'; +import { NoDataPage } from '@kbn/kibana-react-plugin/public'; +import { useCloudDefendSetupStatusApi } from '../../common/api/use_setup_status_api'; +import { useCloudDefendIntegrationLinks } from '../../common/navigation/use_cloud_defend_integration_links'; + +const chance = new Chance(); + +jest.mock('../../common/api/use_setup_status_api'); +jest.mock('../../common/hooks/use_subscription_status'); +jest.mock('../../common/navigation/use_cloud_defend_integration_links'); + +describe('', () => { + beforeEach(() => { + jest.resetAllMocks(); + (useCloudDefendSetupStatusApi as jest.Mock).mockImplementation(() => + createReactQueryResponse({ + status: 'success', + data: { status: 'indexed' }, + }) + ); + + (useCloudDefendIntegrationLinks as jest.Mock).mockImplementation(() => ({ + addIntegrationLink: chance.url(), + docsLink: chance.url(), + })); + + (useSubscriptionStatus as jest.Mock).mockImplementation(() => + createReactQueryResponse({ + status: 'success', + data: true, + }) + ); + }); + + const renderCloudDefendPage = ( + props: ComponentProps = { children: null } + ) => { + const mockCore = coreMock.createStart(); + + render( + + + + ); + }; + + it('renders children if setup status is indexed', () => { + const children = chance.sentence(); + renderCloudDefendPage({ children }); + + expect(screen.getByText(children)).toBeInTheDocument(); + expect(screen.queryByTestId(LOADING_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument(); + }); + + it('renders default loading state when the subscription query is loading', () => { + (useSubscriptionStatus as jest.Mock).mockImplementation( + () => + createReactQueryResponse({ + status: 'loading', + }) as unknown as UseQueryResult + ); + + const children = chance.sentence(); + renderCloudDefendPage({ children }); + + expect(screen.getByTestId(LOADING_STATE_TEST_SUBJECT)).toBeInTheDocument(); + expect(screen.queryByText(children)).not.toBeInTheDocument(); + expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument(); + }); + + it('renders default error state when the subscription query has an error', () => { + (useSubscriptionStatus as jest.Mock).mockImplementation( + () => + createReactQueryResponse({ + status: 'error', + error: new Error('error'), + }) as unknown as UseQueryResult + ); + + const children = chance.sentence(); + renderCloudDefendPage({ children }); + + expect(screen.getByTestId(ERROR_STATE_TEST_SUBJECT)).toBeInTheDocument(); + expect(screen.queryByTestId(LOADING_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByText(children)).not.toBeInTheDocument(); + expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument(); + }); + + it('renders subscription not allowed prompt if subscription is not installed', () => { + (useSubscriptionStatus as jest.Mock).mockImplementation(() => + createReactQueryResponse({ + status: 'success', + data: false, + }) + ); + + const children = chance.sentence(); + renderCloudDefendPage({ children }); + + expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByText(children)).not.toBeInTheDocument(); + expect(screen.queryByTestId(LOADING_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.getByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).toBeInTheDocument(); + expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + }); + + it('renders integrations installation prompt if integration is not installed', () => { + (useCloudDefendSetupStatusApi as jest.Mock).mockImplementation(() => + createReactQueryResponse({ + status: 'success', + data: { status: 'not-installed' }, + }) + ); + + const children = chance.sentence(); + renderCloudDefendPage({ children }); + + expect(screen.getByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).toBeInTheDocument(); + expect(screen.queryByText(children)).not.toBeInTheDocument(); + expect(screen.queryByTestId(LOADING_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + }); + + it('renders default loading state when the integration query is loading', () => { + (useCloudDefendSetupStatusApi as jest.Mock).mockImplementation( + () => + createReactQueryResponse({ + status: 'loading', + }) as unknown as UseQueryResult + ); + + const children = chance.sentence(); + renderCloudDefendPage({ children }); + + expect(screen.getByTestId(LOADING_STATE_TEST_SUBJECT)).toBeInTheDocument(); + expect(screen.queryByText(children)).not.toBeInTheDocument(); + expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument(); + }); + + it('renders default error state when the integration query has an error', () => { + (useCloudDefendSetupStatusApi as jest.Mock).mockImplementation( + () => + createReactQueryResponse({ + status: 'error', + error: new Error('error'), + }) as unknown as UseQueryResult + ); + + const children = chance.sentence(); + renderCloudDefendPage({ children }); + + expect(screen.getByTestId(ERROR_STATE_TEST_SUBJECT)).toBeInTheDocument(); + expect(screen.queryByTestId(LOADING_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByText(children)).not.toBeInTheDocument(); + expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument(); + }); + + it('renders default loading text when query isLoading', () => { + const query = createReactQueryResponse({ + status: 'loading', + }) as unknown as UseQueryResult; + + const children = chance.sentence(); + renderCloudDefendPage({ children, query }); + + expect(screen.getByTestId(LOADING_STATE_TEST_SUBJECT)).toBeInTheDocument(); + expect(screen.queryByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByText(children)).not.toBeInTheDocument(); + expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument(); + }); + + it('renders default loading text when query is idle', () => { + const query = createReactQueryResponse({ + status: 'idle', + }) as unknown as UseQueryResult; + + const children = chance.sentence(); + renderCloudDefendPage({ children, query }); + + expect(screen.getByTestId(LOADING_STATE_TEST_SUBJECT)).toBeInTheDocument(); + expect(screen.queryByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByText(children)).not.toBeInTheDocument(); + expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument(); + }); + + it('renders default error texts when query isError', () => { + const error = chance.sentence(); + const message = chance.sentence(); + const statusCode = chance.integer(); + + const query = createReactQueryResponse({ + status: 'error', + error: { + body: { + error, + message, + statusCode, + }, + }, + }) as unknown as UseQueryResult; + + const children = chance.sentence(); + renderCloudDefendPage({ children, query }); + + [error, message, statusCode].forEach((text) => + expect(screen.getByText(text, { exact: false })).toBeInTheDocument() + ); + expect(screen.getByTestId(ERROR_STATE_TEST_SUBJECT)).toBeInTheDocument(); + expect(screen.queryByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(LOADING_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByText(children)).not.toBeInTheDocument(); + expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument(); + }); + + it('prefers custom error render', () => { + const error = chance.sentence(); + const message = chance.sentence(); + const statusCode = chance.integer(); + + const query = createReactQueryResponse({ + status: 'error', + error: { + body: { + error, + message, + statusCode, + }, + }, + }) as unknown as UseQueryResult; + + const children = chance.sentence(); + renderCloudDefendPage({ + children, + query, + errorRender: (err) =>
      {isCommonError(err) && err.body.message}
      , + }); + + expect(screen.getByText(message)).toBeInTheDocument(); + [error, statusCode].forEach((text) => expect(screen.queryByText(text)).not.toBeInTheDocument()); + expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(LOADING_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByText(children)).not.toBeInTheDocument(); + expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument(); + }); + + it('prefers custom loading render', () => { + const loading = chance.sentence(); + + const query = createReactQueryResponse({ + status: 'loading', + }) as unknown as UseQueryResult; + + const children = chance.sentence(); + renderCloudDefendPage({ + children, + query, + loadingRender: () =>
      {loading}
      , + }); + + expect(screen.getByText(loading)).toBeInTheDocument(); + expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(LOADING_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByText(children)).not.toBeInTheDocument(); + expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument(); + }); + + it('renders no data prompt when query data is undefined', () => { + const query = createReactQueryResponse({ + status: 'success', + data: undefined, + }) as unknown as UseQueryResult; + + const children = chance.sentence(); + renderCloudDefendPage({ children, query }); + + expect(screen.getByTestId(DEFAULT_NO_DATA_TEST_SUBJECT)).toBeInTheDocument(); + expect(screen.queryByTestId(LOADING_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByText(children)).not.toBeInTheDocument(); + expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument(); + }); + + it('prefers custom no data prompt', () => { + const pageTitle = chance.sentence(); + const solution = chance.sentence(); + const docsLink = chance.sentence(); + const noDataRenderer = () => ( + + ); + + const query = createReactQueryResponse({ + status: 'success', + data: undefined, + }) as unknown as UseQueryResult; + + const children = chance.sentence(); + renderCloudDefendPage({ + children, + query, + noDataRenderer, + }); + + expect(screen.getByText(pageTitle)).toBeInTheDocument(); + expect(screen.getAllByText(solution, { exact: false })[0]).toBeInTheDocument(); + expect(screen.queryByTestId(LOADING_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByText(children)).not.toBeInTheDocument(); + expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/cloud_defend/public/components/cloud_defend_page/index.tsx b/x-pack/plugins/cloud_defend/public/components/cloud_defend_page/index.tsx new file mode 100644 index 0000000000000..bc10db7ace74b --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/components/cloud_defend_page/index.tsx @@ -0,0 +1,294 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import type { UseQueryResult } from '@tanstack/react-query'; +import { + EuiButton, + EuiEmptyPrompt, + EuiImage, + EuiFlexGroup, + EuiFlexItem, + EuiLink, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { NoDataPage, NoDataPageProps } from '@kbn/kibana-react-plugin/public'; +import { css } from '@emotion/react'; +import { SubscriptionNotAllowed } from '../subscription_not_allowed'; +import { useSubscriptionStatus } from '../../common/hooks/use_subscription_status'; +import { FullSizeCenteredPage } from '../full_size_page'; +import { useCloudDefendSetupStatusApi } from '../../common/api/use_setup_status_api'; +import { LoadingState } from '../loading_state'; +import { useCloudDefendIntegrationLinks } from '../../common/navigation/use_cloud_defend_integration_links'; + +import noDataIllustration from '../../assets/icons/logo.svg'; + +export const LOADING_STATE_TEST_SUBJECT = 'cloud_defend_page_loading'; +export const ERROR_STATE_TEST_SUBJECT = 'cloud_defend_page_error'; +export const PACKAGE_NOT_INSTALLED_TEST_SUBJECT = 'cloud_defend_page_package_not_installed'; +export const DEFAULT_NO_DATA_TEST_SUBJECT = 'cloud_defend_page_no_data'; +export const SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT = 'cloud_defend_page_subscription_not_allowed'; + +interface CommonError { + body: { + error: string; + message: string; + statusCode: number; + }; +} + +export const isCommonError = (error: unknown): error is CommonError => { + if ( + !(error as any)?.body || + !(error as any)?.body?.error || + !(error as any)?.body?.message || + !(error as any)?.body?.statusCode + ) { + return false; + } + + return true; +}; + +export interface CloudDefendNoDataPageProps { + pageTitle: NoDataPageProps['pageTitle']; + docsLink: NoDataPageProps['docsLink']; + actionHref: NoDataPageProps['actions']['elasticAgent']['href']; + actionTitle: NoDataPageProps['actions']['elasticAgent']['title']; + actionDescription: NoDataPageProps['actions']['elasticAgent']['description']; + testId: string; +} + +export const CloudDefendNoDataPage = ({ + pageTitle, + docsLink, + actionHref, + actionTitle, + actionDescription, + testId, +}: CloudDefendNoDataPageProps) => { + return ( + :nth-child(3) { + display: block; + margin: auto; + width: 450px; + } + `} + pageTitle={pageTitle} + solution={i18n.translate( + 'xpack.cloudDefend.cloudDefendPage.packageNotInstalled.solutionNameLabel', + { + defaultMessage: 'Defend for containers (D4C)', + } + )} + docsLink={docsLink} + logo="logoSecurity" + actions={{ + elasticAgent: { + href: actionHref, + isDisabled: !actionHref, + title: actionTitle, + description: actionDescription, + }, + }} + /> + ); +}; + +const packageNotInstalledRenderer = ({ + addIntegrationLink, + docsLink, +}: { + addIntegrationLink?: string; + docsLink?: string; +}) => { + return ( + + } + title={ +

      + +

      + } + layout="horizontal" + color="plain" + body={ +

      + + + + ), + }} + /> +

      + } + actions={ + + + + + + + + } + /> +
      + ); +}; + +const defaultLoadingRenderer = () => ( + + + +); + +const defaultErrorRenderer = (error: unknown) => ( + + + + + } + body={ + isCommonError(error) ? ( +

      + +

      + ) : undefined + } + /> +
      +); + +const defaultNoDataRenderer = (docsLink: string) => ( + + + +); + +const subscriptionNotAllowedRenderer = () => ( + + + +); + +interface CloudDefendPageProps { + children: React.ReactNode; + query?: UseQueryResult; + loadingRender?: () => React.ReactNode; + errorRender?: (error: TError) => React.ReactNode; + noDataRenderer?: (docsLink: string) => React.ReactNode; +} + +export const CloudDefendPage = ({ + children, + query, + loadingRender = defaultLoadingRenderer, + errorRender = defaultErrorRenderer, + noDataRenderer = defaultNoDataRenderer, +}: CloudDefendPageProps) => { + const subscriptionStatus = useSubscriptionStatus(); + const getSetupStatus = useCloudDefendSetupStatusApi(); + const { addIntegrationLink, docsLink } = useCloudDefendIntegrationLinks(); + + const render = () => { + if (subscriptionStatus.isError) { + return defaultErrorRenderer(subscriptionStatus.error); + } + + if (subscriptionStatus.isLoading) { + return defaultLoadingRenderer(); + } + + if (!subscriptionStatus.data) { + return subscriptionNotAllowedRenderer(); + } + + if (getSetupStatus.isError) { + return defaultErrorRenderer(getSetupStatus.error); + } + + if (getSetupStatus.isLoading) { + return defaultLoadingRenderer(); + } + + if (getSetupStatus.data.status === 'not-installed') { + return packageNotInstalledRenderer({ addIntegrationLink, docsLink }); + } + + if (!query) { + return children; + } + + if (query.isError) { + return errorRender(query.error); + } + + if (query.isLoading) { + return loadingRender(); + } + + if (!query.data) { + return noDataRenderer(docsLink); + } + + return children; + }; + + return <>{render()}; +}; diff --git a/x-pack/plugins/cloud_defend/public/components/cloud_defend_page_title/index.tsx b/x-pack/plugins/cloud_defend/public/components/cloud_defend_page_title/index.tsx new file mode 100644 index 0000000000000..43422ff2db1e6 --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/components/cloud_defend_page_title/index.tsx @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; + +export const CloudDefendPageTitle = ({ title }: { title: string }) => ( + + + +

      {title}

      +
      +
      +
      +); diff --git a/x-pack/plugins/cloud_defend/public/components/full_size_page/index.tsx b/x-pack/plugins/cloud_defend/public/components/full_size_page/index.tsx new file mode 100644 index 0000000000000..4e68797c8c21f --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/components/full_size_page/index.tsx @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { EuiFlexGroup, type CommonProps } from '@elastic/eui'; +import { css } from '@emotion/react'; +import React from 'react'; + +// Keep this component lean as it is part of the main app bundle +export const FullSizeCenteredPage = ({ + children, + ...rest +}: { children: React.ReactNode } & CommonProps) => ( + + {children} + +); diff --git a/x-pack/plugins/cloud_defend/public/components/loading_state/index.tsx b/x-pack/plugins/cloud_defend/public/components/loading_state/index.tsx new file mode 100644 index 0000000000000..608eabf0e30a8 --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/components/loading_state/index.tsx @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiLoadingSpinner, EuiSpacer } from '@elastic/eui'; +import React from 'react'; +import { FullSizeCenteredPage } from '../full_size_page'; + +// Keep this component lean as it is part of the main app bundle +export const LoadingState: React.FunctionComponent<{ ['data-test-subj']?: string }> = ({ + children, + ...rest +}) => { + return ( + + + + {children} + + ); +}; diff --git a/x-pack/plugins/cloud_defend/public/components/policies_table/index.test.tsx b/x-pack/plugins/cloud_defend/public/components/policies_table/index.test.tsx new file mode 100644 index 0000000000000..e473fd9f990b1 --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/components/policies_table/index.test.tsx @@ -0,0 +1,126 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import Chance from 'chance'; +import { render, screen } from '@testing-library/react'; +import moment from 'moment'; +import { createCloudDefendIntegrationFixture } from '../../test/fixtures/cloud_defend_integration'; +import { PoliciesTable } from '.'; +import { TestProvider } from '../../test/test_provider'; + +describe('', () => { + const chance = new Chance(); + + const tableProps = { + pageIndex: 1, + pageSize: 10, + error: undefined, + loading: false, + setQuery: jest.fn(), + }; + + it('renders integration name', () => { + const item = createCloudDefendIntegrationFixture(); + const policies = [item]; + + render( + + + + ); + + expect(screen.getByText(item.package_policy.name)).toBeInTheDocument(); + }); + + it('renders agent policy name', () => { + const agentPolicy = { + id: chance.guid(), + name: chance.sentence(), + agents: chance.integer({ min: 1 }), + }; + + const policies = [createCloudDefendIntegrationFixture({ agent_policy: agentPolicy })]; + + render( + + + + ); + + expect(screen.getByText(agentPolicy.name)).toBeInTheDocument(); + }); + + it('renders number of agents', () => { + const item = createCloudDefendIntegrationFixture(); + const policies = [item]; + + render( + + + + ); + + // TODO too loose + expect(screen.getByText(item.agent_policy.agents as number)).toBeInTheDocument(); + }); + + it('renders created by', () => { + const item = createCloudDefendIntegrationFixture(); + const policies = [item]; + + render( + + + + ); + + expect(screen.getByText(item.package_policy.created_by)).toBeInTheDocument(); + }); + + it('renders created at', () => { + const item = createCloudDefendIntegrationFixture(); + const policies = [item]; + + render( + + + + ); + + expect(screen.getByText(moment(item.package_policy.created_at).fromNow())).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/cloud_defend/public/components/policies_table/index.tsx b/x-pack/plugins/cloud_defend/public/components/policies_table/index.tsx new file mode 100644 index 0000000000000..bb4134b083d9f --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/components/policies_table/index.tsx @@ -0,0 +1,157 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + EuiBasicTable, + type EuiBasicTableColumn, + type EuiBasicTableProps, + type Pagination, + type CriteriaWithPagination, + EuiLink, +} from '@elastic/eui'; +import React from 'react'; +import { pagePathGetters } from '@kbn/fleet-plugin/public'; +import { i18n } from '@kbn/i18n'; +import { TimestampTableCell } from '../timestamp_table_cell'; +import type { CloudDefendPolicy } from '../../../common/types'; +import { useKibana } from '../../common/hooks/use_kibana'; +import * as TEST_SUBJ from '../../pages/policies/test_subjects'; + +interface PoliciesTableProps + extends Pick< + EuiBasicTableProps, + 'loading' | 'error' | 'noItemsMessage' | 'sorting' + >, + Pagination { + policies: CloudDefendPolicy[]; + setQuery(pagination: CriteriaWithPagination): void; + 'data-test-subj'?: string; +} + +const AgentPolicyButtonLink = ({ name, id: policyId }: { name: string; id: string }) => { + const { http } = useKibana().services; + const [fleetBase, path] = pagePathGetters.policy_details({ policyId }); + + return {name}; +}; + +const IntegrationButtonLink = ({ + packageName, + policyId, + packagePolicyId, +}: { + packageName: string; + packagePolicyId: string; + policyId: string; +}) => { + const editIntegrationLink = pagePathGetters + .edit_integration({ + packagePolicyId, + policyId, + }) + .join(''); + + return {packageName}; +}; + +const POLICIES_TABLE_COLUMNS: Array> = [ + { + field: 'package_policy.name', + name: i18n.translate('xpack.cloudDefend.policies.policiesTable.integrationNameColumnTitle', { + defaultMessage: 'Integration Name', + }), + render: (packageName, policy) => ( + + ), + truncateText: true, + sortable: true, + 'data-test-subj': TEST_SUBJ.POLICIES_TABLE_COLUMNS.INTEGRATION_NAME, + }, + { + field: 'agent_policy.name', + name: i18n.translate('xpack.cloudDefend.policies.policiesTable.agentPolicyColumnTitle', { + defaultMessage: 'Agent Policy', + }), + render: (name, policy) => , + truncateText: true, + 'data-test-subj': TEST_SUBJ.POLICIES_TABLE_COLUMNS.AGENT_POLICY, + }, + { + field: 'agent_policy.agents', + name: i18n.translate('xpack.cloudDefend.policies.policiesTable.numberOfAgentsColumnTitle', { + defaultMessage: 'Number of Agents', + }), + truncateText: true, + 'data-test-subj': TEST_SUBJ.POLICIES_TABLE_COLUMNS.NUMBER_OF_AGENTS, + }, + { + field: 'package_policy.created_by', + name: i18n.translate('xpack.cloudDefend.policies.policiesTable.createdByColumnTitle', { + defaultMessage: 'Created by', + }), + dataType: 'string', + truncateText: true, + sortable: true, + 'data-test-subj': TEST_SUBJ.POLICIES_TABLE_COLUMNS.CREATED_BY, + }, + { + field: 'package_policy.created_at', + name: i18n.translate('xpack.cloudDefend.policies.policiesTable.createdAtColumnTitle', { + defaultMessage: 'Created at', + }), + dataType: 'date', + truncateText: true, + render: (timestamp: CloudDefendPolicy['package_policy']['created_at']) => ( + + ), + sortable: true, + 'data-test-subj': TEST_SUBJ.POLICIES_TABLE_COLUMNS.CREATED_AT, + }, +]; + +export const PoliciesTable = ({ + policies, + pageIndex, + pageSize, + totalItemCount, + loading, + error, + setQuery, + noItemsMessage, + sorting, + ...rest +}: PoliciesTableProps) => { + const pagination: Pagination = { + pageIndex: Math.max(pageIndex - 1, 0), + pageSize, + totalItemCount, + }; + + const onChange = ({ page, sort }: CriteriaWithPagination) => { + setQuery({ page: { ...page, index: page.index + 1 }, sort }); + }; + + return ( + [item.agent_policy.id, item.package_policy.id].join('/')} + pagination={pagination} + onChange={onChange} + tableLayout="fixed" + loading={loading} + noItemsMessage={noItemsMessage} + error={error} + sorting={sorting} + /> + ); +}; diff --git a/x-pack/plugins/cloud_defend/public/components/subscription_not_allowed/index.tsx b/x-pack/plugins/cloud_defend/public/components/subscription_not_allowed/index.tsx new file mode 100644 index 0000000000000..7ab4afa3fb06e --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/components/subscription_not_allowed/index.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 { EuiEmptyPrompt, EuiPageSection, EuiLink } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import React from 'react'; +import { useKibana } from '../../common/hooks/use_kibana'; + +export const SubscriptionNotAllowed = () => { + const { application } = useKibana().services; + return ( + + + + + } + body={ +

      + + + + ), + }} + /> +

      + } + /> +
      + ); +}; diff --git a/x-pack/plugins/cloud_defend/public/components/timestamp_table_cell/index.tsx b/x-pack/plugins/cloud_defend/public/components/timestamp_table_cell/index.tsx new file mode 100644 index 0000000000000..b6b6934b92cde --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/components/timestamp_table_cell/index.tsx @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import moment, { type MomentInput } from 'moment'; +import { EuiToolTip, formatDate } from '@elastic/eui'; +import { useUiSetting } from '@kbn/kibana-react-plugin/public'; + +const DEFAULT_DATE_FORMAT = 'dateFormat'; + +export const TimestampTableCell = ({ timestamp }: { timestamp: MomentInput }) => { + const dateFormat = useUiSetting(DEFAULT_DATE_FORMAT); + const formatted = formatDate(timestamp, dateFormat); + + return ( + + {moment(timestamp).fromNow()} + + ); +}; diff --git a/x-pack/plugins/cloud_defend/public/index.ts b/x-pack/plugins/cloud_defend/public/index.ts index fd8099aa2ed11..b74c04111c91b 100755 --- a/x-pack/plugins/cloud_defend/public/index.ts +++ b/x-pack/plugins/cloud_defend/public/index.ts @@ -6,6 +6,14 @@ */ import { CloudDefendPlugin } from './plugin'; +export type { CloudDefendSecuritySolutionContext } from './types'; +export { + getSecuritySolutionLink, + getSecuritySolutionNavTab, +} from './common/navigation/security_solution_links'; +export { CLOUD_DEFEND_BASE_PATH } from './common/navigation/constants'; +export type { CloudDefendPageId } from './common/navigation/types'; + // This exports static code and TypeScript types, // as well as, Kibana Platform `plugin()` initializer. export function plugin() { diff --git a/x-pack/plugins/maps/server/maps_telemetry/index_pattern_stats/index.ts b/x-pack/plugins/cloud_defend/public/pages/index.ts similarity index 66% rename from x-pack/plugins/maps/server/maps_telemetry/index_pattern_stats/index.ts rename to x-pack/plugins/cloud_defend/public/pages/index.ts index 9e4c0ef21fdea..ec35f87e70811 100644 --- a/x-pack/plugins/maps/server/maps_telemetry/index_pattern_stats/index.ts +++ b/x-pack/plugins/cloud_defend/public/pages/index.ts @@ -5,5 +5,4 @@ * 2.0. */ -export { IndexPatternStatsCollector } from './index_pattern_stats_collector'; -export type { IndexPatternStats } from './types'; +export { Policies } from './policies'; diff --git a/x-pack/plugins/cloud_defend/public/pages/policies/index.test.tsx b/x-pack/plugins/cloud_defend/public/pages/policies/index.test.tsx new file mode 100644 index 0000000000000..61fcf6f0a844a --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/pages/policies/index.test.tsx @@ -0,0 +1,93 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import Chance from 'chance'; +import { render, screen } from '@testing-library/react'; +import type { UseQueryResult } from '@tanstack/react-query'; +import { createCloudDefendIntegrationFixture } from '../../test/fixtures/cloud_defend_integration'; +import { createReactQueryResponse } from '../../test/fixtures/react_query'; +import { TestProvider } from '../../test/test_provider'; +import { Policies } from '.'; +import * as TEST_SUBJ from './test_subjects'; +import { useCloudDefendPolicies } from './use_cloud_defend_policies'; +import { useCloudDefendSetupStatusApi } from '../../common/api/use_setup_status_api'; +import { useSubscriptionStatus } from '../../common/hooks/use_subscription_status'; +import { useCloudDefendIntegrationLinks } from '../../common/navigation/use_cloud_defend_integration_links'; + +jest.mock('./use_cloud_defend_policies'); +jest.mock('../../common/api/use_setup_status_api'); +jest.mock('../../common/hooks/use_subscription_status'); +jest.mock('../../common/navigation/use_cloud_defend_integration_links'); + +const chance = new Chance(); + +describe('', () => { + beforeEach(() => { + jest.resetAllMocks(); + (useCloudDefendSetupStatusApi as jest.Mock).mockImplementation(() => + createReactQueryResponse({ + status: 'success', + data: { status: 'indexed' }, + }) + ); + + (useSubscriptionStatus as jest.Mock).mockImplementation(() => + createReactQueryResponse({ + status: 'success', + data: true, + }) + ); + + (useCloudDefendIntegrationLinks as jest.Mock).mockImplementation(() => ({ + addIntegrationLink: chance.url(), + docsLink: chance.url(), + })); + }); + + const renderPolicies = (queryResponse: Partial = createReactQueryResponse()) => { + (useCloudDefendPolicies as jest.Mock).mockImplementation(() => queryResponse); + + return render( + + + + ); + }; + + it('renders the page header', () => { + renderPolicies(); + + expect(screen.getByTestId(TEST_SUBJ.POLICIES_PAGE_HEADER)).toBeInTheDocument(); + }); + + it('renders the "add integration" button', () => { + renderPolicies(); + + expect(screen.getByTestId(TEST_SUBJ.ADD_INTEGRATION_TEST_SUBJ)).toBeInTheDocument(); + }); + + it('renders error state while there is an error', () => { + const error = new Error('message'); + renderPolicies(createReactQueryResponse({ status: 'error', error })); + + expect(screen.getByText(error.message)).toBeInTheDocument(); + }); + + it('renders the benchmarks table', () => { + renderPolicies( + createReactQueryResponse({ + status: 'success', + data: { total: 1, items: [createCloudDefendIntegrationFixture()] }, + }) + ); + + expect(screen.getByTestId(TEST_SUBJ.POLICIES_TABLE_DATA_TEST_SUBJ)).toBeInTheDocument(); + Object.values(TEST_SUBJ.POLICIES_TABLE_COLUMNS).forEach((testId) => + expect(screen.getAllByTestId(testId)[0]).toBeInTheDocument() + ); + }); +}); diff --git a/x-pack/plugins/cloud_defend/public/pages/policies/index.tsx b/x-pack/plugins/cloud_defend/public/pages/policies/index.tsx new file mode 100644 index 0000000000000..c732be5421a17 --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/pages/policies/index.tsx @@ -0,0 +1,197 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState } from 'react'; +import { + EuiButton, + EuiFieldSearch, + EuiFieldSearchProps, + EuiFlexGroup, + EuiFlexItem, + EuiPageHeader, + EuiSpacer, + EuiText, + EuiTextColor, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import useDebounce from 'react-use/lib/useDebounce'; +import { i18n } from '@kbn/i18n'; +import { CloudDefendPageTitle } from '../../components/cloud_defend_page_title'; +import { CloudDefendPage } from '../../components/cloud_defend_page'; +import { PoliciesTable } from '../../components/policies_table'; +import { useCloudDefendPolicies, UseCloudDefendPoliciesProps } from './use_cloud_defend_policies'; +import { extractErrorMessage } from '../../../common/utils/helpers'; +import * as TEST_SUBJ from './test_subjects'; +import { LOCAL_STORAGE_PAGE_SIZE } from '../../common/constants'; +import { usePageSize } from '../../common/hooks/use_page_size'; +import { useCloudDefendIntegrationLinks } from '../../common/navigation/use_cloud_defend_integration_links'; + +const SEARCH_DEBOUNCE_MS = 300; + +const AddIntegrationButton = () => { + const { addIntegrationLink } = useCloudDefendIntegrationLinks(); + + return ( + + + + ); +}; + +const EmptyState = ({ name }: { name: string }) => ( +
      + + { + + + + {name && ( + + )} + + + } + + + + + + + +
      +); + +const TotalIntegrationsCount = ({ + pageCount, + totalCount, +}: Record<'pageCount' | 'totalCount', number>) => ( + + + + + +); + +const SearchField = ({ + onSearch, + isLoading, +}: Required>) => { + const [localValue, setLocalValue] = useState(''); + + useDebounce(() => onSearch(localValue), SEARCH_DEBOUNCE_MS, [localValue]); + + return ( + + + + + + ); +}; + +export const Policies = () => { + const { pageSize, setPageSize } = usePageSize(LOCAL_STORAGE_PAGE_SIZE); + const [query, setQuery] = useState({ + name: '', + page: 1, + perPage: pageSize, + sortField: 'package_policy.name', + sortOrder: 'asc', + }); + + const queryResult = useCloudDefendPolicies(query); + const totalItemCount = queryResult.data?.total || 0; + + return ( + + + } + rightSideItems={[]} + bottomBorder + /> + + setQuery((current) => ({ ...current, name }))} + /> + + + + { + setPageSize(page.size); + setQuery((current) => ({ + ...current, + page: page.index, + perPage: page.size, + sortField: + (sort?.field as UseCloudDefendPoliciesProps['sortField']) || current.sortField, + sortOrder: sort?.direction || current.sortOrder, + })); + }} + noItemsMessage={ + queryResult.isSuccess && !queryResult.data.total ? ( + + ) : undefined + } + /> + + ); +}; diff --git a/x-pack/plugins/cloud_defend/public/pages/policies/test_subjects.ts b/x-pack/plugins/cloud_defend/public/pages/policies/test_subjects.ts new file mode 100644 index 0000000000000..9709360512727 --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/pages/policies/test_subjects.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const POLICIES_PAGE_HEADER = 'policies-page-header'; +export const POLICIES_TABLE_DATA_TEST_SUBJ = 'cloud_defend_policies_table'; +export const ADD_INTEGRATION_TEST_SUBJ = 'cloud_defend_add_integration'; +export const POLICIES_TABLE_COLUMNS = { + INTEGRATION_NAME: 'policies-table-column-integration-name', + AGENT_POLICY: 'policies-table-column-agent-policy', + NUMBER_OF_AGENTS: 'policies-table-column-number-of-agents', + CREATED_BY: 'policies-table-column-created-by', + CREATED_AT: 'policies-table-column-created-at', +}; diff --git a/x-pack/plugins/cloud_defend/public/pages/policies/use_cloud_defend_policies.ts b/x-pack/plugins/cloud_defend/public/pages/policies/use_cloud_defend_policies.ts new file mode 100644 index 0000000000000..e0766026e12b8 --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/pages/policies/use_cloud_defend_policies.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useQuery } from '@tanstack/react-query'; +import type { ListResult } from '@kbn/fleet-plugin/common'; +import { POLICIES_ROUTE_PATH } from '../../../common/constants'; +import type { PoliciesQueryParams } from '../../../common/schemas/policy'; +import { useKibana } from '../../common/hooks/use_kibana'; +import type { CloudDefendPolicy } from '../../../common/types'; + +const QUERY_KEY = 'cloud_defend_policies'; + +export interface UseCloudDefendPoliciesProps { + name: string; + page: number; + perPage: number; + sortField: PoliciesQueryParams['sort_field']; + sortOrder: PoliciesQueryParams['sort_order']; +} + +export const useCloudDefendPolicies = ({ + name, + perPage, + page, + sortField, + sortOrder, +}: UseCloudDefendPoliciesProps) => { + const { http } = useKibana().services; + const query: PoliciesQueryParams = { + policy_name: name, + per_page: perPage, + page, + sort_field: sortField, + sort_order: sortOrder, + }; + + return useQuery( + [QUERY_KEY, query], + () => http.get>(POLICIES_ROUTE_PATH, { query }), + { keepPreviousData: true } + ); +}; diff --git a/x-pack/plugins/cloud_defend/public/plugin.ts b/x-pack/plugins/cloud_defend/public/plugin.ts deleted file mode 100755 index 5bbb1215e2270..0000000000000 --- a/x-pack/plugins/cloud_defend/public/plugin.ts +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import { lazy } from 'react'; -import { CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; -import { - CloudDefendPluginSetup, - CloudDefendPluginStart, - CloudDefendPluginStartDeps, -} from './types'; -import { INTEGRATION_PACKAGE_NAME } from '../common/constants'; - -const LazyEditPolicy = lazy(() => import('./components/fleet_extensions/policy_extension_edit')); -const LazyCreatePolicy = lazy( - () => import('./components/fleet_extensions/policy_extension_create') -); - -export class CloudDefendPlugin implements Plugin { - public setup(core: CoreSetup): CloudDefendPluginSetup { - // Return methods that should be available to other plugins - return {}; - } - - public start(core: CoreStart, plugins: CloudDefendPluginStartDeps): CloudDefendPluginStart { - plugins.fleet.registerExtension({ - package: INTEGRATION_PACKAGE_NAME, - view: 'package-policy-create', - Component: LazyCreatePolicy, - }); - - plugins.fleet.registerExtension({ - package: INTEGRATION_PACKAGE_NAME, - view: 'package-policy-edit', - Component: LazyEditPolicy, - }); - - return {}; - } - - public stop() {} -} diff --git a/x-pack/plugins/cloud_defend/public/plugin.tsx b/x-pack/plugins/cloud_defend/public/plugin.tsx new file mode 100755 index 0000000000000..b9272993e6b55 --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/plugin.tsx @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app'; +import React, { lazy, Suspense } from 'react'; +import type { CloudDefendRouterProps } from './application/router'; +import { + CloudDefendPluginSetup, + CloudDefendPluginStart, + CloudDefendPluginStartDeps, + CloudDefendPluginSetupDeps, +} from './types'; +import { INTEGRATION_PACKAGE_NAME } from '../common/constants'; +import { LoadingState } from './components/loading_state'; +import { SetupContext } from './application/setup_context'; + +const LazyEditPolicy = lazy(() => import('./components/fleet_extensions/policy_extension_edit')); +const LazyCreatePolicy = lazy( + () => import('./components/fleet_extensions/policy_extension_create') +); + +const RouterLazy = lazy(() => import('./application/router')); +const Router = (props: CloudDefendRouterProps) => ( + }> + + +); + +export class CloudDefendPlugin + implements + Plugin< + CloudDefendPluginSetup, + CloudDefendPluginStart, + CloudDefendPluginSetupDeps, + CloudDefendPluginStartDeps + > +{ + private isCloudEnabled?: boolean; + + public setup( + core: CoreSetup, + plugins: CloudDefendPluginSetupDeps + ): CloudDefendPluginSetup { + this.isCloudEnabled = plugins.cloud.isCloudEnabled; + + // Return methods that should be available to other plugins + return {}; + } + + public start(core: CoreStart, plugins: CloudDefendPluginStartDeps): CloudDefendPluginStart { + plugins.fleet.registerExtension({ + package: INTEGRATION_PACKAGE_NAME, + view: 'package-policy-create', + Component: LazyCreatePolicy, + }); + + plugins.fleet.registerExtension({ + package: INTEGRATION_PACKAGE_NAME, + view: 'package-policy-edit', + Component: LazyEditPolicy, + }); + + const CloudDefendRouter = (props: CloudDefendRouterProps) => ( + + +
      + + + +
      +
      +
      + ); + + return { + getCloudDefendRouter: () => CloudDefendRouter, + }; + } + + public stop() {} +} diff --git a/x-pack/plugins/cloud_defend/public/test/fixtures/cloud_defend_integration.ts b/x-pack/plugins/cloud_defend/public/test/fixtures/cloud_defend_integration.ts new file mode 100644 index 0000000000000..830977947673c --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/test/fixtures/cloud_defend_integration.ts @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/* eslint-disable @typescript-eslint/naming-convention */ + +import Chance from 'chance'; +import type { CloudDefendPolicy } from '../../../common/types'; + +type CreateCloudDefendIntegrationFixtureInput = { + chance?: Chance.Chance; +} & Partial; + +export const createCloudDefendIntegrationFixture = ({ + chance = new Chance(), + package_policy = { + revision: chance?.integer(), + enabled: true, + id: chance.guid(), + name: chance.string(), + policy_id: chance.guid(), + namespace: chance.string(), + updated_at: chance.date().toISOString(), + updated_by: chance.word(), + created_at: chance.date().toISOString(), + created_by: chance.word(), + inputs: [ + { + type: 'cloud_defend/control', + policy_template: 'cloud_defend', + enabled: true, + streams: [ + { + id: chance?.guid(), + enabled: true, + data_stream: { + type: 'logs', + dataset: 'cloud_defend.alerts', + }, + }, + ], + }, + ], + package: { + name: chance.string(), + title: chance.string(), + version: chance.string(), + }, + }, + agent_policy = { + id: chance.guid(), + name: chance.sentence(), + agents: chance.integer({ min: 0 }), + }, +}: CreateCloudDefendIntegrationFixtureInput = {}): CloudDefendPolicy => ({ + package_policy, + agent_policy, +}); diff --git a/x-pack/plugins/cloud_defend/public/test/fixtures/navigation_item.ts b/x-pack/plugins/cloud_defend/public/test/fixtures/navigation_item.ts new file mode 100644 index 0000000000000..74fdbce35f95b --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/test/fixtures/navigation_item.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 Chance from 'chance'; +import type { CloudDefendPageNavigationItem } from '../../common/navigation/types'; + +type CreateNavigationItemFixtureInput = { + chance?: Chance.Chance; +} & Partial; +export const createPageNavigationItemFixture = ({ + chance = new Chance(), + name = chance.word(), + path = `/${chance.word()}`, + disabled = undefined, + id = 'cloud_defend-policies', +}: CreateNavigationItemFixtureInput = {}): CloudDefendPageNavigationItem => ({ + name, + path, + disabled, + id, +}); diff --git a/x-pack/plugins/cloud_defend/public/test/fixtures/react_query.ts b/x-pack/plugins/cloud_defend/public/test/fixtures/react_query.ts new file mode 100644 index 0000000000000..9605515618882 --- /dev/null +++ b/x-pack/plugins/cloud_defend/public/test/fixtures/react_query.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { UseQueryResult } from '@tanstack/react-query'; + +interface CreateReactQueryResponseInput { + status?: UseQueryResult['status'] | 'idle'; + data?: TData; + error?: TError; +} + +// TODO: Consider alternatives to using `Partial` over `UseQueryResult` for the return type: +// 1. Fully mock `UseQueryResult` +// 2. Mock the network layer instead of `useQuery` - see: https://tkdodo.eu/blog/testing-react-query +export const createReactQueryResponse = ({ + status = 'loading', + error = undefined, + data = undefined, +}: CreateReactQueryResponseInput = {}): Partial> => { + if (status === 'success') { + return { status, data, isSuccess: true, isLoading: false, isError: false }; + } + + if (status === 'error') { + return { status, error, isSuccess: false, isLoading: false, isError: true }; + } + + if (status === 'loading') { + return { status, data: undefined, isSuccess: false, isLoading: true, isError: false }; + } + + if (status === 'idle') { + return { + status: 'loading', + data: undefined, + isSuccess: false, + isLoading: true, + isError: false, + fetchStatus: 'idle', + }; + } + + return { status }; +}; diff --git a/x-pack/plugins/cloud_defend/public/test/mocks.ts b/x-pack/plugins/cloud_defend/public/test/mocks.ts index 7dcf8d99d9116..4921d3f6d0f98 100644 --- a/x-pack/plugins/cloud_defend/public/test/mocks.ts +++ b/x-pack/plugins/cloud_defend/public/test/mocks.ts @@ -71,8 +71,8 @@ export const getCloudDefendNewPolicyMock = (yaml = MOCK_YAML_CONFIGURATION): New ], package: { name: 'cloud_defend', - title: 'Kubernetes Security Posture Management', - version: '0.0.21', + title: 'Container drift prevention', + version: '1.0.0', }, }); @@ -114,7 +114,7 @@ export const getCloudDefendPolicyMock = (yaml = MOCK_YAML_CONFIGURATION): Packag ], package: { name: 'cloud_defend', - title: 'Kubernetes Security Posture Management', - version: '0.0.21', + title: 'Container drift prevention', + version: '1.0.0', }, }); diff --git a/x-pack/plugins/cloud_defend/public/test/test_provider.tsx b/x-pack/plugins/cloud_defend/public/test/test_provider.tsx index 472f0ea04ecd1..c5deff816241c 100755 --- a/x-pack/plugins/cloud_defend/public/test/test_provider.tsx +++ b/x-pack/plugins/cloud_defend/public/test/test_provider.tsx @@ -37,13 +37,13 @@ Object.defineProperty(window, 'matchMedia', { })), }); -interface CspAppDeps { +interface CloudDefendAppDeps { core: CoreStart; deps: CloudDefendPluginStartDeps; params: AppMountParameters; } -export const TestProvider: React.FC> = ({ +export const TestProvider: React.FC> = ({ core = coreMock.createStart(), deps = { data: dataPluginMock.createStartContract(), diff --git a/x-pack/plugins/cloud_defend/public/types.ts b/x-pack/plugins/cloud_defend/public/types.ts index 801f5ee67891e..3b242b9fc2add 100755 --- a/x-pack/plugins/cloud_defend/public/types.ts +++ b/x-pack/plugins/cloud_defend/public/types.ts @@ -4,22 +4,53 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import type { CloudSetup } from '@kbn/cloud-plugin/public'; +import type { LicensingPluginStart } from '@kbn/licensing-plugin/public'; import type { FleetSetup, FleetStart } from '@kbn/fleet-plugin/public'; import { NewPackagePolicy } from '@kbn/fleet-plugin/public'; +import type { ComponentType, ReactNode } from 'react'; +import type { + UsageCollectionSetup, + UsageCollectionStart, +} from '@kbn/usage-collection-plugin/public'; +import type { CloudDefendRouterProps } from './application/router'; +import type { CloudDefendPageId } from './common/navigation/types'; + +/** + * cloud_defend plugin types + */ // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface CloudDefendPluginSetup {} -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface CloudDefendPluginStart {} +export interface CloudDefendPluginStart { + /** Gets the cloud defend router component for embedding in the security solution. */ + getCloudDefendRouter(): ComponentType; +} export interface CloudDefendPluginSetupDeps { fleet: FleetSetup; + cloud: CloudSetup; + usageCollection?: UsageCollectionSetup; } export interface CloudDefendPluginStartDeps { fleet: FleetStart; + licensing: LicensingPluginStart; + usageCollection?: UsageCollectionStart; } +export interface CloudDefendSecuritySolutionContext { + /** Gets the `FiltersGlobal` component for embedding a filter bar in the security solution application. */ + getFiltersGlobalComponent: () => ComponentType<{ children: ReactNode }>; + /** Gets the `SpyRoute` component for navigation highlighting and breadcrumbs. */ + getSpyRouteComponent: () => ComponentType<{ + pageName: CloudDefendPageId; + state?: Record; + }>; +} + +/** + * cloud_defend/control types + */ export enum ControlResponseAction { alert = 'alert', block = 'block', diff --git a/x-pack/plugins/cloud_defend/server/index.ts b/x-pack/plugins/cloud_defend/server/index.ts new file mode 100644 index 0000000000000..2cb2e1c2b55e5 --- /dev/null +++ b/x-pack/plugins/cloud_defend/server/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { PluginInitializerContext } from '@kbn/core/server'; +import { CloudDefendPlugin } from './plugin'; + +// This exports static code and TypeScript types, +// as well as, Kibana Platform `plugin()` initializer. + +export function plugin(initializerContext: PluginInitializerContext) { + return new CloudDefendPlugin(initializerContext); +} + +export type { CloudDefendPluginSetup, CloudDefendPluginStart } from './types'; diff --git a/x-pack/plugins/cloud_defend/server/lib/check_index_status.ts b/x-pack/plugins/cloud_defend/server/lib/check_index_status.ts new file mode 100644 index 0000000000000..984c68a76a6b6 --- /dev/null +++ b/x-pack/plugins/cloud_defend/server/lib/check_index_status.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 { ElasticsearchClient, type Logger } from '@kbn/core/server'; +import { IndexStatus } from '../../common/types'; + +export const checkIndexStatus = async ( + esClient: ElasticsearchClient, + index: string, + logger: Logger +): Promise => { + try { + const queryResult = await esClient.search({ + index, + query: { + match_all: {}, + }, + size: 1, + }); + + return queryResult.hits.hits.length ? 'not-empty' : 'empty'; + } catch (e) { + logger.debug(e); + if (e?.meta?.body?.error?.type === 'security_exception') { + return 'unprivileged'; + } + + // Assuming index doesn't exist + return 'empty'; + } +}; diff --git a/x-pack/plugins/cloud_defend/server/lib/fleet_util.ts b/x-pack/plugins/cloud_defend/server/lib/fleet_util.ts new file mode 100644 index 0000000000000..ae9866b4f03ca --- /dev/null +++ b/x-pack/plugins/cloud_defend/server/lib/fleet_util.ts @@ -0,0 +1,119 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { map, uniq } from 'lodash'; +import type { SavedObjectsClientContract, Logger } from '@kbn/core/server'; +import type { + AgentPolicyServiceInterface, + AgentService, + PackagePolicyClient, +} from '@kbn/fleet-plugin/server'; +import type { + AgentPolicy, + GetAgentStatusResponse, + ListResult, + PackagePolicy, +} from '@kbn/fleet-plugin/common'; +import { errors } from '@elastic/elasticsearch'; +import { INPUT_CONTROL, CLOUD_DEFEND_FLEET_PACKAGE_KUERY } from '../../common/constants'; +import { POLICIES_PACKAGE_POLICY_PREFIX, PoliciesQueryParams } from '../../common/schemas/policy'; + +export const PACKAGE_POLICY_SAVED_OBJECT_TYPE = 'ingest-package-policies'; + +const isFleetMissingAgentHttpError = (error: unknown) => + error instanceof errors.ResponseError && error.statusCode === 404; + +const isPolicyTemplate = (input: any) => input === INPUT_CONTROL; + +const getPackageNameQuery = (packageName: string, benchmarkFilter?: string): string => { + const integrationNameQuery = `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${packageName}`; + const kquery = benchmarkFilter + ? `${integrationNameQuery} AND ${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.name: *${benchmarkFilter}*` + : integrationNameQuery; + + return kquery; +}; + +export type AgentStatusByAgentPolicyMap = Record; + +export const getAgentStatusesByAgentPolicies = async ( + agentService: AgentService, + agentPolicies: AgentPolicy[] | undefined, + logger: Logger +): Promise => { + if (!agentPolicies?.length) return {}; + + const internalAgentService = agentService.asInternalUser; + const result: AgentStatusByAgentPolicyMap = {}; + + try { + for (const agentPolicy of agentPolicies) { + result[agentPolicy.id] = await internalAgentService.getAgentStatusForAgentPolicy( + agentPolicy.id + ); + } + } catch (error) { + if (isFleetMissingAgentHttpError(error)) { + logger.debug('failed to get agent status for agent policy'); + } else { + throw error; + } + } + + return result; +}; + +export const getCloudDefendAgentPolicies = async ( + soClient: SavedObjectsClientContract, + packagePolicies: PackagePolicy[], + agentPolicyService: AgentPolicyServiceInterface +): Promise => + agentPolicyService.getByIds(soClient, uniq(map(packagePolicies, 'policy_id')), { + withPackagePolicies: true, + ignoreMissing: true, + }); + +export const getCloudDefendPackagePolicies = ( + soClient: SavedObjectsClientContract, + packagePolicyService: PackagePolicyClient, + packageName: string, + queryParams: Partial +): Promise> => { + const sortField = queryParams.sort_field?.replaceAll(POLICIES_PACKAGE_POLICY_PREFIX, ''); + + return packagePolicyService.list(soClient, { + kuery: getPackageNameQuery(packageName, queryParams.policy_name), + page: queryParams.page, + perPage: queryParams.per_page, + sortField, + sortOrder: queryParams.sort_order, + }); +}; + +export const getInstalledPolicyTemplates = async ( + packagePolicyClient: PackagePolicyClient, + soClient: SavedObjectsClientContract +) => { + try { + // getting all installed cloud_defend package policies + const queryResult = await packagePolicyClient.list(soClient, { + kuery: CLOUD_DEFEND_FLEET_PACKAGE_KUERY, + perPage: 1000, + }); + + // getting installed policy templates + const enabledPolicyTemplates = queryResult.items + .map((policy) => { + return policy.inputs.find((input) => input.enabled)?.policy_template; + }) + .filter(isPolicyTemplate); + + // removing duplicates + return [...new Set(enabledPolicyTemplates)]; + } catch (e) { + return []; + } +}; diff --git a/x-pack/plugins/cloud_defend/server/mocks.ts b/x-pack/plugins/cloud_defend/server/mocks.ts new file mode 100644 index 0000000000000..a3a1fb895e3c8 --- /dev/null +++ b/x-pack/plugins/cloud_defend/server/mocks.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; +import { coreMock } from '@kbn/core/server/mocks'; +import { + createFleetRequestHandlerContextMock, + createMockAgentService, + createMockAgentPolicyService, + createPackagePolicyServiceMock, + createMockPackageService, +} from '@kbn/fleet-plugin/server/mocks'; +import { mockAuthenticatedUser } from '@kbn/security-plugin/common/model/authenticated_user.mock'; + +export const createCloudDefendRequestHandlerContextMock = () => { + const coreMockRequestContext = coreMock.createRequestHandlerContext(); + + return { + core: coreMockRequestContext, + fleet: createFleetRequestHandlerContextMock(), + cloudDefend: { + user: mockAuthenticatedUser(), + logger: loggingSystemMock.createLogger(), + esClient: coreMockRequestContext.elasticsearch.client, + soClient: coreMockRequestContext.savedObjects.client, + agentPolicyService: createMockAgentPolicyService(), + agentService: createMockAgentService(), + packagePolicyService: createPackagePolicyServiceMock(), + packageService: createMockPackageService(), + }, + }; +}; diff --git a/x-pack/plugins/cloud_defend/server/plugin.ts b/x-pack/plugins/cloud_defend/server/plugin.ts new file mode 100644 index 0000000000000..05926e82cf796 --- /dev/null +++ b/x-pack/plugins/cloud_defend/server/plugin.ts @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { PluginInitializerContext, CoreSetup, CoreStart, Plugin, Logger } from '@kbn/core/server'; +import type { NewPackagePolicy } from '@kbn/fleet-plugin/common'; +import { + CloudDefendPluginSetup, + CloudDefendPluginStart, + CloudDefendPluginStartDeps, + CloudDefendPluginSetupDeps, +} from './types'; +import { setupRoutes } from './routes/setup_routes'; +import { isCloudDefendPackage } from '../common/utils/helpers'; +import { isSubscriptionAllowed } from '../common/utils/subscription'; + +export class CloudDefendPlugin implements Plugin { + private readonly logger: Logger; + private isCloudEnabled?: boolean; + + constructor(initializerContext: PluginInitializerContext) { + this.logger = initializerContext.logger.get(); + } + + public setup( + core: CoreSetup, + plugins: CloudDefendPluginSetupDeps + ) { + this.logger.debug('cloudDefend: Setup'); + + setupRoutes({ + core, + logger: this.logger, + }); + + this.isCloudEnabled = plugins.cloud.isCloudEnabled; + + return {}; + } + + public start(core: CoreStart, plugins: CloudDefendPluginStartDeps): CloudDefendPluginStart { + this.logger.debug('cloudDefend: Started'); + + plugins.fleet.fleetSetupCompleted().then(async () => { + plugins.fleet.registerExternalCallback( + 'packagePolicyCreate', + async (packagePolicy: NewPackagePolicy): Promise => { + const license = await plugins.licensing.refresh(); + if (isCloudDefendPackage(packagePolicy.package?.name)) { + if (!isSubscriptionAllowed(this.isCloudEnabled, license)) { + throw new Error( + 'To use this feature you must upgrade your subscription or start a trial' + ); + } + } + + return packagePolicy; + } + ); + }); + + return {}; + } + + public stop() {} +} diff --git a/x-pack/plugins/cloud_defend/server/routes/policies/policies.test.ts b/x-pack/plugins/cloud_defend/server/routes/policies/policies.test.ts new file mode 100644 index 0000000000000..51dbabe1d936f --- /dev/null +++ b/x-pack/plugins/cloud_defend/server/routes/policies/policies.test.ts @@ -0,0 +1,261 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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, httpServiceMock, savedObjectsClientMock } from '@kbn/core/server/mocks'; +import { + policiesQueryParamsSchema, + DEFAULT_POLICIES_PER_PAGE, +} from '../../../common/schemas/policy'; +import { + PACKAGE_POLICY_SAVED_OBJECT_TYPE, + getCloudDefendPackagePolicies, + getCloudDefendAgentPolicies, +} from '../../lib/fleet_util'; +import { defineGetPoliciesRoute } from './policies'; + +import { SavedObjectsClientContract } from '@kbn/core/server'; +import { + createMockAgentPolicyService, + createPackagePolicyServiceMock, +} from '@kbn/fleet-plugin/server/mocks'; +import { createPackagePolicyMock } from '@kbn/fleet-plugin/common/mocks'; +import { createCloudDefendRequestHandlerContextMock } from '../../mocks'; + +describe('policies API', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('validate the API route path', async () => { + const router = httpServiceMock.createRouter(); + + defineGetPoliciesRoute(router); + + const [config] = router.get.mock.calls[0]; + + expect(config.path).toEqual('/internal/cloud_defend/policies'); + }); + + it('should accept to a user with fleet.all privilege', async () => { + const router = httpServiceMock.createRouter(); + + defineGetPoliciesRoute(router); + + const [_, handler] = router.get.mock.calls[0]; + + const mockContext = createCloudDefendRequestHandlerContextMock(); + const mockResponse = httpServerMock.createResponseFactory(); + const mockRequest = httpServerMock.createKibanaRequest(); + const [context, req, res] = [mockContext, mockRequest, mockResponse]; + + await handler(context, req, res); + + expect(res.forbidden).toHaveBeenCalledTimes(0); + }); + + it('should reject to a user without fleet.all privilege', async () => { + const router = httpServiceMock.createRouter(); + + defineGetPoliciesRoute(router); + + const [_, handler] = router.get.mock.calls[0]; + + const mockContext = createCloudDefendRequestHandlerContextMock(); + mockContext.fleet.authz.fleet.all = false; + + const mockResponse = httpServerMock.createResponseFactory(); + const mockRequest = httpServerMock.createKibanaRequest(); + const [context, req, res] = [mockContext, mockRequest, mockResponse]; + + await handler(context, req, res); + + expect(res.forbidden).toHaveBeenCalledTimes(1); + }); + + describe('test input schema', () => { + it('expect to find default values', async () => { + const validatedQuery = policiesQueryParamsSchema.validate({}); + + expect(validatedQuery).toMatchObject({ + page: 1, + per_page: DEFAULT_POLICIES_PER_PAGE, + }); + }); + + it('expect to find policy_name', async () => { + const validatedQuery = policiesQueryParamsSchema.validate({ + policy_name: 'my_cis_policy', + }); + + expect(validatedQuery).toMatchObject({ + page: 1, + per_page: DEFAULT_POLICIES_PER_PAGE, + policy_name: 'my_cis_policy', + }); + }); + + it('should throw when page field is not a positive integer', async () => { + expect(() => { + policiesQueryParamsSchema.validate({ page: -2 }); + }).toThrow(); + }); + + it('should throw when per_page field is not a positive integer', async () => { + expect(() => { + policiesQueryParamsSchema.validate({ per_page: -2 }); + }).toThrow(); + }); + }); + + it('should throw when sort_field is not string', async () => { + expect(() => { + policiesQueryParamsSchema.validate({ sort_field: true }); + }).toThrow(); + }); + + it('should not throw when sort_field is a string', async () => { + expect(() => { + policiesQueryParamsSchema.validate({ sort_field: 'package_policy.name' }); + }).not.toThrow(); + }); + + it('should throw when sort_order is not `asc` or `desc`', async () => { + expect(() => { + policiesQueryParamsSchema.validate({ sort_order: 'Other Direction' }); + }).toThrow(); + }); + + it('should not throw when `asc` is input for sort_order field', async () => { + expect(() => { + policiesQueryParamsSchema.validate({ sort_order: 'asc' }); + }).not.toThrow(); + }); + + it('should not throw when `desc` is input for sort_order field', async () => { + expect(() => { + policiesQueryParamsSchema.validate({ sort_order: 'desc' }); + }).not.toThrow(); + }); + + it('should not throw when fields is a known string literal', async () => { + expect(() => { + policiesQueryParamsSchema.validate({ sort_field: 'package_policy.name' }); + }).not.toThrow(); + }); + + describe('test policies utils', () => { + let mockSoClient: jest.Mocked; + + beforeEach(() => { + mockSoClient = savedObjectsClientMock.create(); + }); + + describe('test getPackagePolicies', () => { + it('should format request by package name', async () => { + const mockPackagePolicyService = createPackagePolicyServiceMock(); + + await getCloudDefendPackagePolicies(mockSoClient, mockPackagePolicyService, 'myPackage', { + page: 1, + per_page: 100, + sort_order: 'desc', + }); + + expect(mockPackagePolicyService.list.mock.calls[0][1]).toMatchObject( + expect.objectContaining({ + kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:myPackage`, + page: 1, + perPage: 100, + }) + ); + }); + + it('should build sort request by `sort_field` and default `sort_order`', async () => { + const mockAgentPolicyService = createPackagePolicyServiceMock(); + + await getCloudDefendPackagePolicies(mockSoClient, mockAgentPolicyService, 'myPackage', { + page: 1, + per_page: 100, + sort_field: 'package_policy.name', + sort_order: 'desc', + }); + + expect(mockAgentPolicyService.list.mock.calls[0][1]).toMatchObject( + expect.objectContaining({ + kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:myPackage`, + page: 1, + perPage: 100, + sortField: 'name', + sortOrder: 'desc', + }) + ); + }); + + it('should build sort request by `sort_field` and asc `sort_order`', async () => { + const mockAgentPolicyService = createPackagePolicyServiceMock(); + + await getCloudDefendPackagePolicies(mockSoClient, mockAgentPolicyService, 'myPackage', { + page: 1, + per_page: 100, + sort_field: 'package_policy.name', + sort_order: 'asc', + }); + + expect(mockAgentPolicyService.list.mock.calls[0][1]).toMatchObject( + expect.objectContaining({ + kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:myPackage`, + page: 1, + perPage: 100, + sortField: 'name', + sortOrder: 'asc', + }) + ); + }); + }); + + it('should format request by policy_name', async () => { + const mockAgentPolicyService = createPackagePolicyServiceMock(); + + await getCloudDefendPackagePolicies(mockSoClient, mockAgentPolicyService, 'myPackage', { + page: 1, + per_page: 100, + sort_order: 'desc', + policy_name: 'cloud_defend_1', + }); + + expect(mockAgentPolicyService.list.mock.calls[0][1]).toMatchObject( + expect.objectContaining({ + kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:myPackage AND ${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.name: *cloud_defend_1*`, + page: 1, + perPage: 100, + }) + ); + }); + + describe('test getAgentPolicies', () => { + it('should return one agent policy id when there is duplication', async () => { + const agentPolicyService = createMockAgentPolicyService(); + const packagePolicies = [createPackagePolicyMock(), createPackagePolicyMock()]; + + await getCloudDefendAgentPolicies(mockSoClient, packagePolicies, agentPolicyService); + + expect(agentPolicyService.getByIds.mock.calls[0][1]).toHaveLength(1); + }); + + it('should return full policy ids list when there is no id duplication', async () => { + const agentPolicyService = createMockAgentPolicyService(); + + const packagePolicy1 = createPackagePolicyMock(); + const packagePolicy2 = createPackagePolicyMock(); + packagePolicy2.policy_id = 'AnotherId'; + const packagePolicies = [packagePolicy1, packagePolicy2]; + + await getCloudDefendAgentPolicies(mockSoClient, packagePolicies, agentPolicyService); + + expect(agentPolicyService.getByIds.mock.calls[0][1]).toHaveLength(2); + }); + }); + }); +}); diff --git a/x-pack/plugins/cloud_defend/server/routes/policies/policies.ts b/x-pack/plugins/cloud_defend/server/routes/policies/policies.ts new file mode 100644 index 0000000000000..a0dd3827ff618 --- /dev/null +++ b/x-pack/plugins/cloud_defend/server/routes/policies/policies.ts @@ -0,0 +1,115 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { transformError } from '@kbn/securitysolution-es-utils'; +import type { AgentPolicy, PackagePolicy } from '@kbn/fleet-plugin/common'; +import { POLICIES_ROUTE_PATH, INTEGRATION_PACKAGE_NAME } from '../../../common/constants'; +import { policiesQueryParamsSchema } from '../../../common/schemas/policy'; +import type { CloudDefendPolicy } from '../../../common/types'; +import { isNonNullable } from '../../../common/utils/helpers'; +import { CloudDefendRouter } from '../../types'; +import { + getAgentStatusesByAgentPolicies, + type AgentStatusByAgentPolicyMap, + getCloudDefendAgentPolicies, + getCloudDefendPackagePolicies, +} from '../../lib/fleet_util'; + +export const PACKAGE_POLICY_SAVED_OBJECT_TYPE = 'ingest-package-policies'; + +const createPolicies = ( + agentPolicies: AgentPolicy[], + agentStatusByAgentPolicyId: AgentStatusByAgentPolicyMap, + cloudDefendPackagePolicies: PackagePolicy[] +): Promise => { + const cloudDefendPackagePoliciesMap = new Map( + cloudDefendPackagePolicies.map((packagePolicy) => [packagePolicy.id, packagePolicy]) + ); + + return Promise.all( + agentPolicies.flatMap((agentPolicy) => { + const cloudDefendPackagesOnAgent = + agentPolicy.package_policies + ?.map(({ id: pckPolicyId }) => { + return cloudDefendPackagePoliciesMap.get(pckPolicyId); + }) + .filter(isNonNullable) ?? []; + + const policies = cloudDefendPackagesOnAgent.map(async (cloudDefendPackage) => { + const agentPolicyStatus = { + id: agentPolicy.id, + name: agentPolicy.name, + agents: agentStatusByAgentPolicyId[agentPolicy.id]?.total, + }; + return { + package_policy: cloudDefendPackage, + agent_policy: agentPolicyStatus, + }; + }); + + return policies; + }) + ); +}; + +export const defineGetPoliciesRoute = (router: CloudDefendRouter): void => + router.get( + { + path: POLICIES_ROUTE_PATH, + validate: { query: policiesQueryParamsSchema }, + options: { + tags: ['access:cloud-defend-read'], + }, + }, + async (context, request, response) => { + if (!(await context.fleet).authz.fleet.all) { + return response.forbidden(); + } + + const cloudDefendContext = await context.cloudDefend; + + try { + const cloudDefendPackagePolicies = await getCloudDefendPackagePolicies( + cloudDefendContext.soClient, + cloudDefendContext.packagePolicyService, + INTEGRATION_PACKAGE_NAME, + request.query + ); + + const agentPolicies = await getCloudDefendAgentPolicies( + cloudDefendContext.soClient, + cloudDefendPackagePolicies.items, + cloudDefendContext.agentPolicyService + ); + + const agentStatusesByAgentPolicyId = await getAgentStatusesByAgentPolicies( + cloudDefendContext.agentService, + agentPolicies, + cloudDefendContext.logger + ); + + const policies = await createPolicies( + agentPolicies, + agentStatusesByAgentPolicyId, + cloudDefendPackagePolicies.items + ); + + return response.ok({ + body: { + ...cloudDefendPackagePolicies, + items: policies, + }, + }); + } catch (err) { + const error = transformError(err); + cloudDefendContext.logger.error(`Failed to fetch policies ${err}`); + return response.customError({ + body: { message: error.message }, + statusCode: error.statusCode, + }); + } + } + ); diff --git a/x-pack/plugins/cloud_defend/server/routes/setup_routes.ts b/x-pack/plugins/cloud_defend/server/routes/setup_routes.ts new file mode 100644 index 0000000000000..1a9ef57ba83c7 --- /dev/null +++ b/x-pack/plugins/cloud_defend/server/routes/setup_routes.ts @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { CoreSetup, Logger } from '@kbn/core/server'; +import type { AuthenticatedUser } from '@kbn/security-plugin/common'; +import type { + CloudDefendRequestHandlerContext, + CloudDefendPluginStart, + CloudDefendPluginStartDeps, +} from '../types'; +import { PLUGIN_ID } from '../../common/constants'; +import { defineGetPoliciesRoute } from './policies/policies'; +import { defineGetCloudDefendStatusRoute } from './status/status'; + +/** + * 1. Registers routes + * 2. Registers routes handler context + */ +export function setupRoutes({ + core, + logger, +}: { + core: CoreSetup; + logger: Logger; +}) { + const router = core.http.createRouter(); + defineGetPoliciesRoute(router); + defineGetCloudDefendStatusRoute(router); + + core.http.registerRouteHandlerContext( + PLUGIN_ID, + async (context, request) => { + const [, { security, fleet }] = await core.getStartServices(); + const coreContext = await context.core; + await fleet.fleetSetupCompleted(); + + let user: AuthenticatedUser | null = null; + + return { + get user() { + // We want to call getCurrentUser only when needed and only once + if (!user) { + user = security.authc.getCurrentUser(request); + } + return user; + }, + logger, + esClient: coreContext.elasticsearch.client, + soClient: coreContext.savedObjects.client, + agentPolicyService: fleet.agentPolicyService, + agentService: fleet.agentService, + packagePolicyService: fleet.packagePolicyService, + packageService: fleet.packageService, + }; + } + ); +} diff --git a/x-pack/plugins/cloud_defend/server/routes/status/status.test.ts b/x-pack/plugins/cloud_defend/server/routes/status/status.test.ts new file mode 100644 index 0000000000000..124d8f4738cb2 --- /dev/null +++ b/x-pack/plugins/cloud_defend/server/routes/status/status.test.ts @@ -0,0 +1,493 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { defineGetCloudDefendStatusRoute, INDEX_TIMEOUT_IN_MINUTES } from './status'; +import { httpServerMock, httpServiceMock } from '@kbn/core/server/mocks'; +import type { ESSearchResponse } from '@kbn/es-types'; +import { + AgentClient, + AgentPolicyServiceInterface, + AgentService, + PackageClient, + PackagePolicyClient, + PackageService, +} from '@kbn/fleet-plugin/server'; +import { + AgentPolicy, + GetAgentStatusResponse, + Installation, + RegistryPackage, +} from '@kbn/fleet-plugin/common'; +import { createPackagePolicyMock } from '@kbn/fleet-plugin/common/mocks'; +import { createCloudDefendRequestHandlerContextMock } from '../../mocks'; +import { errors } from '@elastic/elasticsearch'; + +const mockCloudDefendPackageInfo: Installation = { + verification_status: 'verified', + installed_kibana: [], + installed_kibana_space_id: 'default', + installed_es: [], + package_assets: [], + es_index_patterns: { alerts: 'logs-cloud_defend.alerts-*' }, + name: 'cloud_defend', + version: '1.0.0', + install_version: '1.0.0', + install_status: 'installed', + install_started_at: '2022-06-16T15:24:58.281Z', + install_source: 'registry', +}; + +const mockLatestCloudDefendPackageInfo: RegistryPackage = { + format_version: 'mock', + name: 'cloud_defend', + title: 'Defend for containers (D4C)', + version: '1.0.0', + release: 'experimental', + description: 'Container drift prevention', + type: 'integration', + download: '/epr/cloud_defend/cloud_defend-1.0.0.zip', + path: '/package/cloud_defend/1.0.0', + policy_templates: [], + owner: { github: 'elastic/sec-cloudnative-integrations' }, + categories: ['containers', 'kubernetes'], +}; + +describe('CloudDefendSetupStatus route', () => { + const router = httpServiceMock.createRouter(); + let mockContext: ReturnType; + let mockPackagePolicyService: jest.Mocked; + let mockAgentPolicyService: jest.Mocked; + let mockAgentService: jest.Mocked; + let mockAgentClient: jest.Mocked; + let mockPackageService: PackageService; + let mockPackageClient: jest.Mocked; + + beforeEach(() => { + jest.clearAllMocks(); + + mockContext = createCloudDefendRequestHandlerContextMock(); + mockPackagePolicyService = mockContext.cloudDefend.packagePolicyService; + mockAgentPolicyService = mockContext.cloudDefend.agentPolicyService; + mockAgentService = mockContext.cloudDefend.agentService; + mockPackageService = mockContext.cloudDefend.packageService; + + mockAgentClient = mockAgentService.asInternalUser as jest.Mocked; + mockPackageClient = mockPackageService.asInternalUser as jest.Mocked; + }); + + it('validate the API route path', async () => { + defineGetCloudDefendStatusRoute(router); + const [config, _] = router.get.mock.calls[0]; + + expect(config.path).toEqual('/internal/cloud_defend/status'); + }); + + const indices = [ + { + index: 'logs-cloud_defend.alerts-default*', + expected_status: 'not-installed', + }, + ]; + + indices.forEach((idxTestCase) => { + it( + 'Verify the API result when there are no permissions to index: ' + idxTestCase.index, + async () => { + mockContext.core.elasticsearch.client.asCurrentUser.search.mockResponseImplementation( + (req) => { + if (req?.index === idxTestCase.index) { + throw new errors.ResponseError({ + body: { + error: { + type: 'security_exception', + }, + }, + statusCode: 503, + headers: {}, + warnings: [], + meta: {} as any, + }); + } + + return { + hits: { + hits: [{}], + }, + } as any; + } + ); + mockPackageClient.fetchFindLatestPackage.mockResolvedValueOnce( + mockLatestCloudDefendPackageInfo + ); + + mockPackagePolicyService.list.mockResolvedValueOnce({ + items: [], + total: 0, + page: 1, + perPage: 100, + }); + + // Act + defineGetCloudDefendStatusRoute(router); + const [_, handler] = router.get.mock.calls[0]; + + const mockResponse = httpServerMock.createResponseFactory(); + const mockRequest = httpServerMock.createKibanaRequest(); + await handler(mockContext, mockRequest, mockResponse); + + // Assert + const [call] = mockResponse.ok.mock.calls; + const body = call[0]?.body; + expect(mockResponse.ok).toHaveBeenCalledTimes(1); + + expect(body).toMatchObject({ + status: idxTestCase.expected_status, + }); + } + ); + }); + + it('Verify the API result when there are alerts and no installed policies', async () => { + mockContext.core.elasticsearch.client.asCurrentUser.search.mockResponseOnce({ + hits: { + hits: [{ Alerts: 'foo' }], + }, + } as unknown as ESSearchResponse); + mockPackageClient.fetchFindLatestPackage.mockResolvedValueOnce( + mockLatestCloudDefendPackageInfo + ); + + mockPackagePolicyService.list.mockResolvedValueOnce({ + items: [], + total: 0, + page: 1, + perPage: 100, + }); + + // Act + defineGetCloudDefendStatusRoute(router); + const [_, handler] = router.get.mock.calls[0]; + + const mockResponse = httpServerMock.createResponseFactory(); + const mockRequest = httpServerMock.createKibanaRequest(); + await handler(mockContext, mockRequest, mockResponse); + + // Assert + const [call] = mockResponse.ok.mock.calls; + const body = call[0]?.body; + expect(mockResponse.ok).toHaveBeenCalledTimes(1); + + await expect(body).toMatchObject({ + status: 'indexed', + latestPackageVersion: '1.0.0', + installedPackagePolicies: 0, + healthyAgents: 0, + installedPackageVersion: undefined, + }); + }); + + it('Verify the API result when there are alerts, installed policies, no running agents', async () => { + mockContext.core.elasticsearch.client.asCurrentUser.search.mockResponseOnce({ + hits: { + hits: [{ Alerts: 'foo' }], + }, + } as unknown as ESSearchResponse); + + mockPackageClient.fetchFindLatestPackage.mockResolvedValueOnce( + mockLatestCloudDefendPackageInfo + ); + mockPackageClient.getInstallation.mockResolvedValueOnce(mockCloudDefendPackageInfo); + + mockPackagePolicyService.list.mockResolvedValueOnce({ + items: [], + total: 3, + page: 1, + perPage: 100, + }); + + // Act + defineGetCloudDefendStatusRoute(router); + const [_, handler] = router.get.mock.calls[0]; + + const mockResponse = httpServerMock.createResponseFactory(); + const mockRequest = httpServerMock.createKibanaRequest(); + await handler(mockContext, mockRequest, mockResponse); + + // Assert + const [call] = mockResponse.ok.mock.calls; + const body = call[0]?.body; + + expect(mockResponse.ok).toHaveBeenCalledTimes(1); + + await expect(body).toMatchObject({ + status: 'indexed', + latestPackageVersion: '1.0.0', + installedPackagePolicies: 3, + healthyAgents: 0, + installedPackageVersion: '1.0.0', + }); + }); + + it('Verify the API result when there are alerts, installed policies, running agents', async () => { + mockContext.core.elasticsearch.client.asCurrentUser.search.mockResponseOnce({ + hits: { + hits: [{ Alerts: 'foo' }], + }, + } as unknown as ESSearchResponse); + + mockPackageClient.fetchFindLatestPackage.mockResolvedValueOnce( + mockLatestCloudDefendPackageInfo + ); + mockPackageClient.getInstallation.mockResolvedValueOnce(mockCloudDefendPackageInfo); + + mockPackagePolicyService.list.mockResolvedValueOnce({ + items: [], + total: 3, + page: 1, + perPage: 100, + }); + + mockAgentPolicyService.getByIds.mockResolvedValue([ + { package_policies: createPackagePolicyMock() }, + ] as unknown as AgentPolicy[]); + + mockAgentClient.getAgentStatusForAgentPolicy.mockResolvedValue({ + online: 1, + updating: 0, + } as unknown as GetAgentStatusResponse['results']); + + // Act + defineGetCloudDefendStatusRoute(router); + const [_, handler] = router.get.mock.calls[0]; + + const mockResponse = httpServerMock.createResponseFactory(); + const mockRequest = httpServerMock.createKibanaRequest(); + await handler(mockContext, mockRequest, mockResponse); + + // Assert + const [call] = mockResponse.ok.mock.calls; + const body = call[0]!.body; + + expect(mockResponse.ok).toHaveBeenCalledTimes(1); + + expect(body).toMatchObject({ + status: 'indexed', + latestPackageVersion: '1.0.0', + installedPackagePolicies: 3, + healthyAgents: 1, + installedPackageVersion: '1.0.0', + }); + }); + + it('Verify the API result when there are no alerts and no installed policies', async () => { + mockContext.core.elasticsearch.client.asCurrentUser.search.mockResponseOnce({ + hits: { + hits: [], + }, + } as unknown as ESSearchResponse); + mockPackageClient.fetchFindLatestPackage.mockResolvedValueOnce( + mockLatestCloudDefendPackageInfo + ); + + mockPackagePolicyService.list.mockResolvedValueOnce({ + items: [], + total: 0, + page: 1, + perPage: 100, + }); + defineGetCloudDefendStatusRoute(router); + const [_, handler] = router.get.mock.calls[0]; + + const mockResponse = httpServerMock.createResponseFactory(); + const mockRequest = httpServerMock.createKibanaRequest(); + + // Act + await handler(mockContext, mockRequest, mockResponse); + + // Assert + const [call] = mockResponse.ok.mock.calls; + const body = call[0]!.body; + + expect(mockResponse.ok).toHaveBeenCalledTimes(1); + + expect(body).toMatchObject({ + status: 'not-installed', + latestPackageVersion: '1.0.0', + installedPackagePolicies: 0, + healthyAgents: 0, + }); + }); + + it('Verify the API result when there are no alerts, installed agent but no deployed agent', async () => { + mockContext.core.elasticsearch.client.asCurrentUser.search.mockResponseOnce({ + hits: { + hits: [], + }, + } as unknown as ESSearchResponse); + + mockPackageClient.fetchFindLatestPackage.mockResolvedValueOnce( + mockLatestCloudDefendPackageInfo + ); + mockPackageClient.getInstallation.mockResolvedValueOnce(mockCloudDefendPackageInfo); + + mockPackagePolicyService.list.mockResolvedValueOnce({ + items: [], + total: 1, + page: 1, + perPage: 100, + }); + + mockAgentPolicyService.getByIds.mockResolvedValue([ + { package_policies: createPackagePolicyMock() }, + ] as unknown as AgentPolicy[]); + + mockAgentClient.getAgentStatusForAgentPolicy.mockResolvedValue({ + online: 0, + updating: 0, + } as unknown as GetAgentStatusResponse['results']); + + // Act + defineGetCloudDefendStatusRoute(router); + + const [_, handler] = router.get.mock.calls[0]; + + const mockResponse = httpServerMock.createResponseFactory(); + const mockRequest = httpServerMock.createKibanaRequest(); + await handler(mockContext, mockRequest, mockResponse); + + // Assert + const [call] = mockResponse.ok.mock.calls; + const body = call[0]!.body; + + expect(mockResponse.ok).toHaveBeenCalledTimes(1); + + expect(body).toMatchObject({ + status: 'not-deployed', + latestPackageVersion: '1.0.0', + installedPackagePolicies: 1, + healthyAgents: 0, + installedPackageVersion: '1.0.0', + }); + }); + + it('Verify the API result when there are no alerts, installed agent, deployed agent, before index timeout', async () => { + mockContext.core.elasticsearch.client.asCurrentUser.search.mockResponseOnce({ + hits: { + hits: [], + }, + } as unknown as ESSearchResponse); + mockPackageClient.fetchFindLatestPackage.mockResolvedValueOnce( + mockLatestCloudDefendPackageInfo + ); + + const currentTime = new Date(); + mockCloudDefendPackageInfo.install_started_at = new Date( + currentTime.setMinutes(currentTime.getMinutes() - INDEX_TIMEOUT_IN_MINUTES + 1) + ).toUTCString(); + + mockPackageClient.getInstallation.mockResolvedValueOnce(mockCloudDefendPackageInfo); + + mockPackagePolicyService.list.mockResolvedValueOnce({ + items: [], + total: 1, + page: 1, + perPage: 100, + }); + + mockAgentPolicyService.getByIds.mockResolvedValue([ + { package_policies: createPackagePolicyMock() }, + ] as unknown as AgentPolicy[]); + + mockAgentClient.getAgentStatusForAgentPolicy.mockResolvedValue({ + online: 1, + updating: 0, + } as unknown as GetAgentStatusResponse['results']); + + // Act + defineGetCloudDefendStatusRoute(router); + + const [_, handler] = router.get.mock.calls[0]; + + const mockResponse = httpServerMock.createResponseFactory(); + const mockRequest = httpServerMock.createKibanaRequest(); + const [context, req, res] = [mockContext, mockRequest, mockResponse]; + + await handler(context, req, res); + + // Assert + const [call] = mockResponse.ok.mock.calls; + const body = call[0]!.body; + + expect(mockResponse.ok).toHaveBeenCalledTimes(1); + + expect(body).toMatchObject({ + status: 'indexing', + latestPackageVersion: '1.0.0', + installedPackagePolicies: 1, + healthyAgents: 1, + installedPackageVersion: '1.0.0', + }); + }); + + it('Verify the API result when there are no alerts, installed agent, deployed agent, after index timeout', async () => { + mockContext.core.elasticsearch.client.asCurrentUser.search.mockResponseOnce({ + hits: { + hits: [], + }, + } as unknown as ESSearchResponse); + mockPackageClient.fetchFindLatestPackage.mockResolvedValueOnce( + mockLatestCloudDefendPackageInfo + ); + + const currentTime = new Date(); + mockCloudDefendPackageInfo.install_started_at = new Date( + currentTime.setMinutes(currentTime.getMinutes() - INDEX_TIMEOUT_IN_MINUTES - 1) + ).toUTCString(); + + mockPackageClient.getInstallation.mockResolvedValueOnce(mockCloudDefendPackageInfo); + + mockPackagePolicyService.list.mockResolvedValueOnce({ + items: [], + total: 1, + page: 1, + perPage: 100, + }); + + mockAgentPolicyService.getByIds.mockResolvedValue([ + { package_policies: createPackagePolicyMock() }, + ] as unknown as AgentPolicy[]); + + mockAgentClient.getAgentStatusForAgentPolicy.mockResolvedValue({ + online: 1, + updating: 0, + } as unknown as GetAgentStatusResponse['results']); + + // Act + defineGetCloudDefendStatusRoute(router); + + const [_, handler] = router.get.mock.calls[0]; + + const mockResponse = httpServerMock.createResponseFactory(); + const mockRequest = httpServerMock.createKibanaRequest(); + + await handler(mockContext, mockRequest, mockResponse); + + // Assert + const [call] = mockResponse.ok.mock.calls; + const body = call[0]!.body; + + expect(mockResponse.ok).toHaveBeenCalledTimes(1); + + expect(body).toMatchObject({ + status: 'index-timeout', + latestPackageVersion: '1.0.0', + installedPackagePolicies: 1, + healthyAgents: 1, + installedPackageVersion: '1.0.0', + }); + }); +}); diff --git a/x-pack/plugins/cloud_defend/server/routes/status/status.ts b/x-pack/plugins/cloud_defend/server/routes/status/status.ts new file mode 100644 index 0000000000000..0283843254978 --- /dev/null +++ b/x-pack/plugins/cloud_defend/server/routes/status/status.ts @@ -0,0 +1,198 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { transformError } from '@kbn/securitysolution-es-utils'; +import type { SavedObjectsClientContract, Logger } from '@kbn/core/server'; +import type { AgentPolicyServiceInterface, AgentService } from '@kbn/fleet-plugin/server'; +import moment from 'moment'; +import { PackagePolicy } from '@kbn/fleet-plugin/common'; +import { + ALERTS_INDEX_PATTERN, + INTEGRATION_PACKAGE_NAME, + STATUS_ROUTE_PATH, +} from '../../../common/constants'; +import type { CloudDefendApiRequestHandlerContext, CloudDefendRouter } from '../../types'; +import type { + CloudDefendSetupStatus, + CloudDefendStatusCode, + IndexStatus, +} from '../../../common/types'; +import { + getAgentStatusesByAgentPolicies, + getCloudDefendAgentPolicies, + getCloudDefendPackagePolicies, + getInstalledPolicyTemplates, +} from '../../lib/fleet_util'; +import { checkIndexStatus } from '../../lib/check_index_status'; + +export const INDEX_TIMEOUT_IN_MINUTES = 10; + +const calculateDiffFromNowInMinutes = (date: string | number): number => + moment().diff(moment(date), 'minutes'); + +const getHealthyAgents = async ( + soClient: SavedObjectsClientContract, + installedCloudDefendPackagePolicies: PackagePolicy[], + agentPolicyService: AgentPolicyServiceInterface, + agentService: AgentService, + logger: Logger +): Promise => { + // Get agent policies of package policies (from installed package policies) + const agentPolicies = await getCloudDefendAgentPolicies( + soClient, + installedCloudDefendPackagePolicies, + agentPolicyService + ); + + // Get agents statuses of the following agent policies + const agentStatusesByAgentPolicyId = await getAgentStatusesByAgentPolicies( + agentService, + agentPolicies, + logger + ); + + return Object.values(agentStatusesByAgentPolicyId).reduce( + (sum, status) => sum + status.online + status.updating, + 0 + ); +}; + +const calculateCloudDefendStatusCode = ( + indicesStatus: { + alerts: IndexStatus; + }, + installedCloudDefendPackagePolicies: number, + healthyAgents: number, + timeSinceInstallationInMinutes: number +): CloudDefendStatusCode => { + // We check privileges only for the relevant indices for our pages to appear + if (indicesStatus.alerts === 'unprivileged') return 'unprivileged'; + if (indicesStatus.alerts === 'not-empty') return 'indexed'; + if (installedCloudDefendPackagePolicies === 0) return 'not-installed'; + if (healthyAgents === 0) return 'not-deployed'; + if (timeSinceInstallationInMinutes <= INDEX_TIMEOUT_IN_MINUTES) return 'indexing'; + if (timeSinceInstallationInMinutes > INDEX_TIMEOUT_IN_MINUTES) return 'index-timeout'; + + throw new Error('Could not determine cloud defend status'); +}; + +const assertResponse = ( + resp: CloudDefendSetupStatus, + logger: CloudDefendApiRequestHandlerContext['logger'] +) => { + if ( + resp.status === 'unprivileged' && + !resp.indicesDetails.some((idxDetails) => idxDetails.status === 'unprivileged') + ) { + logger.warn('Returned status in `unprivileged` but response is missing the unprivileged index'); + } +}; + +const getCloudDefendStatus = async ({ + logger, + esClient, + soClient, + packageService, + packagePolicyService, + agentPolicyService, + agentService, +}: CloudDefendApiRequestHandlerContext): Promise => { + const [ + alertsIndexStatus, + installation, + latestCloudDefendPackage, + installedPackagePolicies, + installedPolicyTemplates, + ] = await Promise.all([ + checkIndexStatus(esClient.asCurrentUser, ALERTS_INDEX_PATTERN, logger), + packageService.asInternalUser.getInstallation(INTEGRATION_PACKAGE_NAME), + packageService.asInternalUser.fetchFindLatestPackage(INTEGRATION_PACKAGE_NAME), + getCloudDefendPackagePolicies(soClient, packagePolicyService, INTEGRATION_PACKAGE_NAME, { + per_page: 10000, + }), + getInstalledPolicyTemplates(packagePolicyService, soClient), + ]); + + const healthyAgents = await getHealthyAgents( + soClient, + installedPackagePolicies.items, + agentPolicyService, + agentService, + logger + ); + + const installedPackagePoliciesTotal = installedPackagePolicies.total; + const latestCloudDefendPackageVersion = latestCloudDefendPackage.version; + + const MIN_DATE = 0; + const indicesDetails = [ + { + index: ALERTS_INDEX_PATTERN, + status: alertsIndexStatus, + }, + ]; + + const status = calculateCloudDefendStatusCode( + { + alerts: alertsIndexStatus, + }, + installedPackagePoliciesTotal, + healthyAgents, + calculateDiffFromNowInMinutes(installation?.install_started_at || MIN_DATE) + ); + + if (status === 'not-installed') + return { + status, + indicesDetails, + latestPackageVersion: latestCloudDefendPackageVersion, + healthyAgents, + installedPackagePolicies: installedPackagePoliciesTotal, + }; + + const response = { + status, + indicesDetails, + latestPackageVersion: latestCloudDefendPackageVersion, + healthyAgents, + installedPolicyTemplates, + installedPackagePolicies: installedPackagePoliciesTotal, + installedPackageVersion: installation?.install_version, + }; + + assertResponse(response, logger); + return response; +}; + +export const defineGetCloudDefendStatusRoute = (router: CloudDefendRouter): void => + router.get( + { + path: STATUS_ROUTE_PATH, + validate: {}, + options: { + tags: ['access:cloud-defend-read'], + }, + }, + async (context, request, response) => { + const cloudDefendContext = await context.cloudDefend; + try { + const status = await getCloudDefendStatus(cloudDefendContext); + return response.ok({ + body: status, + }); + } catch (err) { + cloudDefendContext.logger.error(`Error getting cloud_defend status`); + cloudDefendContext.logger.error(err); + + const error = transformError(err); + return response.customError({ + body: { message: error.message }, + statusCode: error.statusCode, + }); + } + } + ); diff --git a/x-pack/plugins/cloud_defend/server/types.ts b/x-pack/plugins/cloud_defend/server/types.ts new file mode 100644 index 0000000000000..121a97ee926be --- /dev/null +++ b/x-pack/plugins/cloud_defend/server/types.ts @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { CloudSetup } from '@kbn/cloud-plugin/server'; +import { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/server'; +import type { + IRouter, + CustomRequestHandlerContext, + Logger, + SavedObjectsClientContract, + IScopedClusterClient, +} from '@kbn/core/server'; +import type { LicensingPluginStart } from '@kbn/licensing-plugin/server'; +import type { + FleetStartContract, + FleetRequestHandlerContext, + AgentService, + PackageService, + AgentPolicyServiceInterface, + PackagePolicyClient, +} from '@kbn/fleet-plugin/server'; +import type { + PluginSetup as DataPluginSetup, + PluginStart as DataPluginStart, +} from '@kbn/data-plugin/server'; + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface CloudDefendPluginSetup {} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface CloudDefendPluginStart {} + +export interface CloudDefendPluginSetupDeps { + data: DataPluginSetup; + security: SecurityPluginSetup; + cloud: CloudSetup; +} +export interface CloudDefendPluginStartDeps { + data: DataPluginStart; + fleet: FleetStartContract; + security: SecurityPluginStart; + licensing: LicensingPluginStart; +} + +export interface CloudDefendApiRequestHandlerContext { + user: ReturnType; + logger: Logger; + esClient: IScopedClusterClient; + soClient: SavedObjectsClientContract; + agentPolicyService: AgentPolicyServiceInterface; + agentService: AgentService; + packagePolicyService: PackagePolicyClient; + packageService: PackageService; +} + +export type CloudDefendRequestHandlerContext = CustomRequestHandlerContext<{ + cloudDefend: CloudDefendApiRequestHandlerContext; + fleet: FleetRequestHandlerContext['fleet']; +}>; + +/** + * Convenience type for routers in cloud_defend that includes the CloudDefendRequestHandlerContext type + * @internal + */ +export type CloudDefendRouter = IRouter; diff --git a/x-pack/plugins/cloud_defend/tsconfig.json b/x-pack/plugins/cloud_defend/tsconfig.json index f96b98d8c44dd..d12ee82da6fce 100755 --- a/x-pack/plugins/cloud_defend/tsconfig.json +++ b/x-pack/plugins/cloud_defend/tsconfig.json @@ -6,23 +6,30 @@ "include": [ "common/**/*", "public/**/*", + "server/**/*", "../../../typings/**/*", - "server/**/*.json", - "public/**/*.json" + "public/**/*.json", + "server/**/*.json" ], "kbn_references": [ "@kbn/core", + "@kbn/data-plugin", + "@kbn/security-plugin", "@kbn/fleet-plugin", - "@kbn/fleet-plugin", - "@kbn/core", "@kbn/i18n-react", + "@kbn/config-schema", + "@kbn/licensing-plugin", "@kbn/data-plugin", "@kbn/kibana-react-plugin", "@kbn/monaco", "@kbn/i18n", - "@kbn/shared-ux-router" + "@kbn/usage-collection-plugin", + "@kbn/cloud-plugin", + "@kbn/shared-ux-router", + "@kbn/shared-ux-link-redirect-app", + "@kbn/core-logging-server-mocks", + "@kbn/securitysolution-es-utils", + "@kbn/es-types" ], - "exclude": [ - "target/**/*" - ] + "exclude": ["target/**/*"] } diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/findings_by_resource_table.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/findings_by_resource_table.tsx index f8ac3dc3ad53a..b25149208ce1a 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/findings_by_resource_table.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/findings_by_resource_table.tsx @@ -16,7 +16,7 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import numeral from '@elastic/numeral'; -import { Link, generatePath } from 'react-router-dom'; +import { generatePath, Link } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; import { ColumnNameWithTooltip } from '../../../components/column_name_with_tooltip'; import { ComplianceScoreBar } from '../../../components/compliance_score_bar'; @@ -126,7 +126,9 @@ const baseColumns: Array> = width: '15%', render: (resourceId: FindingsByResourcePage['resource_id']) => ( diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/resource_findings/resource_findings_container.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/resource_findings/resource_findings_container.tsx index 268ea6f393a99..a9598b228f3ae 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/resource_findings/resource_findings_container.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/resource_findings/resource_findings_container.tsx @@ -69,6 +69,7 @@ const getResourceFindingSharedValues = (sharedValues: { resourceSubType: string; resourceName: string; clusterId: string; + cloudAccountName: string; }): EuiDescriptionListProps['listItems'] => [ { title: i18n.translate('xpack.csp.findings.resourceFindingsSharedValues.resourceTypeTitle', { @@ -88,10 +89,18 @@ const getResourceFindingSharedValues = (sharedValues: { }), description: sharedValues.clusterId, }, + { + title: i18n.translate('xpack.csp.findings.resourceFindingsSharedValues.cloudAccountName', { + defaultMessage: 'Cloud Account Name', + }), + description: sharedValues.cloudAccountName, + }, ]; export const ResourceFindings = ({ dataView }: FindingsBaseProps) => { const params = useParams<{ resourceId: string }>(); + const decodedResourceId = decodeURIComponent(params.resourceId); + const getPersistedDefaultQuery = usePersistedQuery(getDefaultQuery); const { urlQuery, setUrlQuery } = useUrlQuery(getPersistedDefaultQuery); const { pageSize, setPageSize } = usePageSize(LOCAL_STORAGE_PAGE_SIZE_FINDINGS_KEY); @@ -111,7 +120,7 @@ export const ResourceFindings = ({ dataView }: FindingsBaseProps) => { const resourceFindings = useResourceFindings({ sort: urlQuery.sort, query: baseEsQuery.query, - resourceId: params.resourceId, + resourceId: decodedResourceId, enabled: !baseEsQuery.error, }); @@ -213,10 +222,11 @@ export const ResourceFindings = ({ dataView }: FindingsBaseProps) => { resourceFindings.data && ( ) diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/resource_findings/use_resource_findings.ts b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/resource_findings/use_resource_findings.ts index a2314dc50b50d..17520e68bceb9 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/resource_findings/use_resource_findings.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/resource_findings/use_resource_findings.ts @@ -35,7 +35,7 @@ type ResourceFindingsResponse = IKibanaSearchResponse< >; export type ResourceFindingsResponseAggs = Record< - 'count' | 'clusterId' | 'resourceSubType' | 'resourceName', + 'count' | 'clusterId' | 'resourceSubType' | 'resourceName' | 'cloudAccountName', estypes.AggregationsMultiBucketAggregateBase< estypes.AggregationsStringRareTermsBucketKeys | undefined > @@ -59,6 +59,9 @@ const getResourceFindingsQuery = ({ sort: [{ [sort.field]: sort.direction }], aggs: { ...getFindingsCountAggQuery(), + cloudAccountName: { + terms: { field: 'cloud.account.name' }, + }, clusterId: { terms: { field: 'cluster_id' }, }, @@ -98,6 +101,7 @@ export const useResourceFindings = (options: UseResourceFindingsOptions) => { assertNonBucketsArray(aggregations.clusterId?.buckets); assertNonBucketsArray(aggregations.resourceSubType?.buckets); assertNonBucketsArray(aggregations.resourceName?.buckets); + assertNonBucketsArray(aggregations.cloudAccountName?.buckets); return { page: hits.hits.map((hit) => hit._source!), @@ -106,6 +110,7 @@ export const useResourceFindings = (options: UseResourceFindingsOptions) => { clusterId: getFirstBucketKey(aggregations.clusterId?.buckets), resourceSubType: getFirstBucketKey(aggregations.resourceSubType?.buckets), resourceName: getFirstBucketKey(aggregations.resourceName?.buckets), + cloudAccountName: getFirstBucketKey(aggregations.cloudAccountName?.buckets), }; }, onError: (err: Error) => showErrorToast(toasts, err), diff --git a/x-pack/plugins/cloud_security_posture/server/create_transforms/create_transforms.ts b/x-pack/plugins/cloud_security_posture/server/create_transforms/create_transforms.ts index eecc50da0ba4c..2f902d03f62dc 100644 --- a/x-pack/plugins/cloud_security_posture/server/create_transforms/create_transforms.ts +++ b/x-pack/plugins/cloud_security_posture/server/create_transforms/create_transforms.ts @@ -10,14 +10,18 @@ import type { ElasticsearchClient, Logger } from '@kbn/core/server'; import { errors } from '@elastic/elasticsearch'; import { latestFindingsTransform } from './latest_findings_transform'; +const LATEST_TRANSFORM_V830 = 'cloud_security_posture.findings_latest-default-0.0.1'; +const LATEST_TRANSFORM_V840 = 'cloud_security_posture.findings_latest-default-8.4.0'; + +const PREVIOUS_TRANSFORMS = [LATEST_TRANSFORM_V830, LATEST_TRANSFORM_V840]; + // TODO: Move transforms to integration package export const initializeCspTransforms = async ( esClient: ElasticsearchClient, logger: Logger ): Promise => { // Deletes old assets from previous versions as part of upgrade process - const LATEST_TRANSFORM_V830 = 'cloud_security_posture.findings_latest-default-0.0.1'; - await deleteTransformSafe(esClient, logger, LATEST_TRANSFORM_V830); + await deletePreviousTransformsVersions(esClient, logger); await initializeTransform(esClient, latestFindingsTransform, logger); }; @@ -107,16 +111,30 @@ export const startTransformIfNotStarted = async ( } }; -const deleteTransformSafe = async (esClient: ElasticsearchClient, logger: Logger, name: string) => { +const deletePreviousTransformsVersions = async (esClient: ElasticsearchClient, logger: Logger) => { + for (const transform of PREVIOUS_TRANSFORMS) { + const response = await deleteTransformSafe(esClient, logger, transform); + if (response) return; + } +}; + +const deleteTransformSafe = async ( + esClient: ElasticsearchClient, + logger: Logger, + name: string +): Promise => { try { await esClient.transform.deleteTransform({ transform_id: name, force: true }); logger.info(`Deleted transform successfully [Name: ${name}]`); + return true; } catch (e) { if (e instanceof errors.ResponseError && e.statusCode === 404) { - logger.trace(`Transform no longer exists [Name: ${name}]`); + logger.trace(`Transform not exists [Name: ${name}]`); + return false; } else { logger.error(`Failed to delete transform [Name: ${name}]`); logger.error(e); + return false; } } }; diff --git a/x-pack/plugins/cloud_security_posture/server/create_transforms/latest_findings_transform.ts b/x-pack/plugins/cloud_security_posture/server/create_transforms/latest_findings_transform.ts index 9775b260c6949..9e9192e7690e8 100644 --- a/x-pack/plugins/cloud_security_posture/server/create_transforms/latest_findings_transform.ts +++ b/x-pack/plugins/cloud_security_posture/server/create_transforms/latest_findings_transform.ts @@ -12,7 +12,7 @@ import { } from '../../common/constants'; export const latestFindingsTransform: TransformPutTransformRequest = { - transform_id: 'cloud_security_posture.findings_latest-default-8.4.0', + transform_id: 'cloud_security_posture.findings_latest-default-8.8.0', description: 'Defines findings transformation to view only the latest finding per resource', source: { index: FINDINGS_INDEX_PATTERN, @@ -30,7 +30,7 @@ export const latestFindingsTransform: TransformPutTransformRequest = { retention_policy: { time: { field: '@timestamp', - max_age: '5h', + max_age: '26h', }, }, latest: { diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/services/time_field_range.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/services/time_field_range.ts deleted file mode 100644 index bcf32a7f62bd7..0000000000000 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/services/time_field_range.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; -import { lazyLoadModules } from '../../../lazy_load_bundle'; -import { GetTimeFieldRangeResponse } from '../../../../common/types/time_field_request'; - -export async function getTimeFieldRange({ - index, - timeFieldName, - query, - runtimeMappings, -}: { - index: string; - timeFieldName?: string; - query?: QueryDslQueryContainer; - runtimeMappings?: estypes.MappingRuntimeFields; -}) { - const body = JSON.stringify({ index, timeFieldName, query, runtimeMappings }); - const fileUploadModules = await lazyLoadModules(); - - return await fileUploadModules.getHttp().fetch({ - path: `/internal/file_upload/time_field_range`, - method: 'POST', - body, - }); -} diff --git a/x-pack/plugins/enterprise_search/common/types/error_codes.ts b/x-pack/plugins/enterprise_search/common/types/error_codes.ts index 134fb7ebf395e..a2c37a2ba7264 100644 --- a/x-pack/plugins/enterprise_search/common/types/error_codes.ts +++ b/x-pack/plugins/enterprise_search/common/types/error_codes.ts @@ -19,4 +19,5 @@ export enum ErrorCode { RESOURCE_NOT_FOUND = 'resource_not_found', UNAUTHORIZED = 'unauthorized', UNCAUGHT_EXCEPTION = 'uncaught_exception', + ENGINE_NOT_FOUND = 'engine_not_found', } diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/recreate_crawler_connector_api_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/recreate_crawler_connector_api_logic.test.ts new file mode 100644 index 0000000000000..340a089bf3c10 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/recreate_crawler_connector_api_logic.test.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 { mockHttpValues } from '../../../__mocks__/kea_logic'; + +import { nextTick } from '@kbn/test-jest-helpers'; + +import { recreateCrawlerConnector } from './recreate_crawler_connector_api_logic'; + +describe('CreateCrawlerIndexApiLogic', () => { + const { http } = mockHttpValues; + beforeEach(() => { + jest.clearAllMocks(); + }); + describe('createCrawlerIndex', () => { + it('calls correct api', async () => { + const indexName = 'elastic-co-crawler'; + http.post.mockReturnValue(Promise.resolve({ connector_id: 'connectorId' })); + + const result = recreateCrawlerConnector({ indexName }); + await nextTick(); + + expect(http.post).toHaveBeenCalledWith( + '/internal/enterprise_search/indices/elastic-co-crawler/crawler/connector' + ); + await expect(result).resolves.toEqual({ connector_id: 'connectorId' }); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/recreate_crawler_connector_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/recreate_crawler_connector_api_logic.ts new file mode 100644 index 0000000000000..6982850b00661 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/recreate_crawler_connector_api_logic.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 { Actions, createApiLogic } from '../../../shared/api_logic/create_api_logic'; +import { HttpLogic } from '../../../shared/http'; + +export interface RecreateCrawlerConnectorArgs { + indexName: string; +} + +export interface RecreateCrawlerConnectorResponse { + created: string; // the name of the newly created index +} + +export const recreateCrawlerConnector = async ({ indexName }: RecreateCrawlerConnectorArgs) => { + const route = `/internal/enterprise_search/indices/${indexName}/crawler/connector`; + + return await HttpLogic.values.http.post(route); +}; + +export const RecreateCrawlerConnectorApiLogic = createApiLogic( + ['recreate_crawler_connector_api_logic'], + recreateCrawlerConnector +); + +export type RecreateCrawlerConnectorActions = Actions< + RecreateCrawlerConnectorArgs, + RecreateCrawlerConnectorResponse +>; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/index/create_custom_pipeline_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/index/create_custom_pipeline_api_logic.ts index 7d9a7ede6ad98..ba3b551b23602 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/index/create_custom_pipeline_api_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/index/create_custom_pipeline_api_logic.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { IngestPipeline } from '@elastic/elasticsearch/lib/api/types'; import { i18n } from '@kbn/i18n'; import { createApiLogic } from '../../../shared/api_logic/create_api_logic'; @@ -14,9 +15,7 @@ export interface CreateCustomPipelineApiLogicArgs { indexName: string; } -export interface CreateCustomPipelineApiLogicResponse { - created: string[]; -} +export type CreateCustomPipelineApiLogicResponse = Record; export const createCustomPipeline = async ({ indexName, diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/index/delete_index_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/index/delete_index_api_logic.ts index 391e3e102383a..7c3f80b05e7a1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/index/delete_index_api_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/index/delete_index_api_logic.ts @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; -import { createApiLogic } from '../../../shared/api_logic/create_api_logic'; +import { Actions, createApiLogic } from '../../../shared/api_logic/create_api_logic'; import { HttpLogic } from '../../../shared/http'; export interface DeleteIndexApiLogicArgs { @@ -36,3 +36,5 @@ export const DeleteIndexApiLogic = createApiLogic(['delete_index_api_logic'], de }, }), }); + +export type DeleteIndexApiActions = Actions; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/pipelines/revert_connector_pipeline_api_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/pipelines/revert_connector_pipeline_api_logic.test.ts new file mode 100644 index 0000000000000..2753fadb77aea --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/pipelines/revert_connector_pipeline_api_logic.test.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 { mockHttpValues } from '../../../__mocks__/kea_logic'; + +import { revertConnectorPipeline } from './revert_connector_pipeline_api_logic'; + +describe('RevertConnectorPipelineApiLogic', () => { + it('should call delete pipeline endpoint', () => { + const { http } = mockHttpValues; + revertConnectorPipeline({ indexName: 'indexName' }); + expect(http.delete).toHaveBeenCalledWith( + '/internal/enterprise_search/indices/indexName/pipelines' + ); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/pipelines/revert_connector_pipeline_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/pipelines/revert_connector_pipeline_api_logic.ts new file mode 100644 index 0000000000000..a97cafdcd1881 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/pipelines/revert_connector_pipeline_api_logic.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Actions, createApiLogic } from '../../../shared/api_logic/create_api_logic'; +import { HttpLogic } from '../../../shared/http'; + +export interface RevertConnectorPipelineArgs { + indexName: string; +} + +export const revertConnectorPipeline = async ({ indexName }: RevertConnectorPipelineArgs) => { + const route = `/internal/enterprise_search/indices/${indexName}/pipelines`; + + return await HttpLogic.values.http.delete(route); +}; + +export const RevertConnectorPipelineApilogic = createApiLogic( + ['revert_connector_pipeline_api'], + revertConnectorPipeline +); + +export type RevertConnectorPipelineActions = Actions; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_search_preview/engine_search_preview.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_search_preview/engine_search_preview.tsx index f4491baf6a7a6..f31fb2dd911a7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_search_preview/engine_search_preview.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_search_preview/engine_search_preview.tsx @@ -9,7 +9,7 @@ import React, { useState, useMemo } from 'react'; import { useValues } from 'kea'; -import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; +import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiLink, EuiSpacer } from '@elastic/eui'; import { PagingInfo, Results, @@ -25,7 +25,9 @@ import EnginesAPIConnector, { } from '@elastic/search-ui-engines-connector'; import { HttpSetup } from '@kbn/core-http-browser'; import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { docLinks } from '../../../../shared/doc_links'; import { HttpLogic } from '../../../../shared/http'; import { EngineViewTabs } from '../../../routes'; import { EnterpriseSearchEnginesPageTemplate } from '../../layout/engines_page_template'; @@ -138,6 +140,13 @@ export const EngineSearchPreview: React.FC = () => { + + + + diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_search_preview/search_ui_components.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_search_preview/search_ui_components.tsx index 97b1a1608ab60..c685cf5898e3d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_search_preview/search_ui_components.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_search_preview/search_ui_components.tsx @@ -194,26 +194,33 @@ export const ResultsPerPageView: React.FC = ({ options, value, }) => ( - - - - - ({ - text: i18n.translate( - 'xpack.enterpriseSearch.content.engine.searchPreview.resultsPerPage.label', - { - defaultMessage: '{value} {value, plural, one {Result} other {Results}}', - values: { value: option }, - } - ), - value: option, - })) ?? [] - } - value={value} - onChange={(evt) => onChange(parseInt(evt.target.value, 10))} - /> - + + + + + + ({ + text: i18n.translate( + 'xpack.enterpriseSearch.content.engine.searchPreview.resultsPerPage.option.label', + { + defaultMessage: '{value} {value, plural, one {Result} other {Results}}', + values: { value: option }, + } + ), + value: option, + })) ?? [] + } + value={value} + onChange={(evt) => onChange(parseInt(evt.target.value, 10))} + /> + + ); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/components/header_actions/header_actions.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/components/header_actions/header_actions.tsx index 0bf12fa6e200d..f6cb956478fde 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/components/header_actions/header_actions.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/components/header_actions/header_actions.tsx @@ -18,7 +18,7 @@ import { SyncsContextMenu } from './syncs_context_menu'; export const getHeaderActions = (indexData?: ElasticsearchIndexWithIngestion) => { const ingestionMethod = getIngestionMethod(indexData); return [ - ...(isCrawlerIndex(indexData) ? [] : []), + ...(isCrawlerIndex(indexData) && indexData.connector ? [] : []), ...(isConnectorIndex(indexData) ? [] : []), { return ( <>
      - - + ]} + description={ +

      + +

      + } + title={

      {i18n.translate('xpack.enterpriseSearch.crawler.authenticationPanel.title', { @@ -36,19 +47,9 @@ export const AuthenticationPanel: React.FC = () => { })}

      -
      - - - -
      - -

      - -

      -
      + } + /> + {isEditing ? : }
      {isModalVisible && } diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/crawl_rules_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/crawl_rules_table.test.tsx index 7eb4c04db4480..e2c3074f41750 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/crawl_rules_table.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/crawl_rules_table.test.tsx @@ -30,6 +30,7 @@ describe('CrawlRulesTable', () => { crawlRules, domainId: '6113e1407a2f2e6f42489794', indexName, + title: 'Crawl rules', }; beforeEach(() => { jest.clearAllMocks(); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/crawl_rules_table.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/crawl_rules_table.tsx index db98f1fb987f7..5c96617e1fb2d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/crawl_rules_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/crawl_rules_table.tsx @@ -18,6 +18,7 @@ import { EuiLink, EuiSelect, EuiText, + EuiSpacer, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -39,6 +40,7 @@ export interface CrawlRulesTableProps { description?: React.ReactNode; domainId: string; indexName: string; + title?: React.ReactNode; } export const getReadableCrawlerRule = (rule: CrawlerRules) => { @@ -99,17 +101,14 @@ const DEFAULT_DESCRIPTION = (

      - {i18n.translate('xpack.enterpriseSearch.crawler.crawlRulesTable.descriptionLinkText', { - defaultMessage: 'Learn more about crawl rules', - })} - - ), - }} + defaultMessage="Create a crawl rule to include or exclude pages whose URL matches the rule. Rules run in sequential order, and each URL is evaluated according to the first match." /> + + + {i18n.translate('xpack.enterpriseSearch.crawler.crawlRulesTable.descriptionLinkText', { + defaultMessage: 'Learn more about crawl rules', + })} +

      ); @@ -119,6 +118,7 @@ export const CrawlRulesTable: React.FC = ({ indexName, crawlRules, defaultCrawlRule, + title, }) => { const { updateCrawlRules } = useActions(CrawlerDomainDetailLogic); @@ -251,7 +251,7 @@ export const CrawlRulesTable: React.FC = ({ updateCrawlRules(newCrawlRules as CrawlRule[]); clearFlashMessages(); }} - title="" + title={title || ''} uneditableItems={defaultCrawlRule ? [defaultCrawlRule] : undefined} canRemoveLastItem /> diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/crawler_domain_detail_tabs.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/crawler_domain_detail_tabs.tsx index 9ca36ba97f7b4..a00c353eaab0b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/crawler_domain_detail_tabs.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/crawler_domain_detail_tabs.tsx @@ -43,14 +43,20 @@ export const CrawlerDomainDetailTabs: React.FC = ( content: ( <> - -

      - {i18n.translate('xpack.enterpriseSearch.crawler.entryPointsTable.title', { - defaultMessage: 'Entry points', - })} -

      -
      - + +

      + {i18n.translate('xpack.enterpriseSearch.crawler.entryPointsTable.title', { + defaultMessage: 'Entry points', + })} +

      + + } + /> ), id: CrawlerDomainTabId.ENTRY_POINTS, @@ -74,14 +80,20 @@ export const CrawlerDomainDetailTabs: React.FC = ( content: ( <> - -

      - {i18n.translate('xpack.enterpriseSearch.crawler.sitemapsTable.title', { - defaultMessage: 'Sitemaps', - })} -

      -
      - + +

      + {i18n.translate('xpack.enterpriseSearch.crawler.sitemapsTable.title', { + defaultMessage: 'Sitemaps', + })} +

      + + } + /> ), id: CrawlerDomainTabId.SITE_MAPS, @@ -93,18 +105,20 @@ export const CrawlerDomainDetailTabs: React.FC = ( content: ( <> - -

      - {i18n.translate('xpack.enterpriseSearch.crawler.crawlRulesTable.title', { - defaultMessage: 'Crawl rules', - })} -

      -
      +

      + {i18n.translate('xpack.enterpriseSearch.crawler.crawlRulesTable.title', { + defaultMessage: 'Crawl rules', + })} +

      + + } /> ), diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/deduplication_panel/deduplication_panel.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/deduplication_panel/deduplication_panel.test.tsx index 54c93ff744ede..a7ffe1bc81bd3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/deduplication_panel/deduplication_panel.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/deduplication_panel/deduplication_panel.test.tsx @@ -14,7 +14,6 @@ import { shallow } from 'enzyme'; import { act } from 'react-dom/test-utils'; import { - EuiButton, EuiButtonEmpty, EuiContextMenuItem, EuiPopover, @@ -26,6 +25,8 @@ import { import { mountWithIntl } from '@kbn/test-jest-helpers'; +import { PageIntroduction } from '../../../../../../shared/page_introduction/page_introduction'; + import { rerender } from '../../../../../../test_helpers'; import { DeduplicationPanel } from './deduplication_panel'; @@ -61,8 +62,11 @@ describe('DeduplicationPanel', () => { it('contains a button to reset to defaults', () => { const wrapper = shallow(); + const dedupeButton = shallow( +
      {wrapper.find(PageIntroduction).prop('actions')}
      + ).children(); - wrapper.find('EuiFlexGroup').first().dive().find(EuiButton).simulate('click'); + dedupeButton.simulate('click'); expect(MOCK_ACTIONS.submitDeduplicationUpdate).toHaveBeenCalledWith({ fields: [], diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/deduplication_panel/deduplication_panel.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/deduplication_panel/deduplication_panel.tsx index e17815765169e..c4bedce7a43c4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/deduplication_panel/deduplication_panel.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/deduplication_panel/deduplication_panel.tsx @@ -21,7 +21,6 @@ import { EuiSelectable, EuiSpacer, EuiSwitch, - EuiText, EuiTitle, } from '@elastic/eui'; @@ -31,6 +30,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { docLinks } from '../../../../../../shared/doc_links'; +import { PageIntroduction } from '../../../../../../shared/page_introduction/page_introduction'; import { CrawlerDomainDetailLogic } from '../crawler_domain_detail_logic'; import { getCheckedOptionLabels, getSelectableOptions } from './utils'; @@ -55,8 +55,8 @@ export const DeduplicationPanel: React.FC = () => { return (
      - - +

      {i18n.translate('xpack.enterpriseSearch.crawler.deduplicationPanel.title', { @@ -64,8 +64,8 @@ export const DeduplicationPanel: React.FC = () => { })}

      -
      - + } + actions={ { } )} - -
      - - -

      - - {i18n.translate( - 'xpack.enterpriseSearch.crawler.deduplicationPanel.learnMoreMessage', - { - defaultMessage: 'Learn more about content hashing', - } - )} - - ), - }} - /> -

      -
      + documents on this domain." + /> +

      + } + links={ + + {i18n.translate('xpack.enterpriseSearch.crawler.deduplicationPanel.learnMoreMessage', { + defaultMessage: 'Learn more about content hashing', + })} + + } + /> = ({ domain, indexName, items }) => { +export const EntryPointsTable: React.FC = ({ + domain, + indexName, + items, + title, +}) => { const { onAdd, onDelete, onUpdate } = useActions(EntryPointsTableLogic); const field = 'value'; @@ -76,7 +82,8 @@ export const EntryPointsTable: React.FC = ({ domain, inde {i18n.translate('xpack.enterpriseSearch.crawler.entryPointsTable.description', { defaultMessage: 'Include the most important URLs for your website here. Entry point URLs will be the first pages to be indexed and processed for links to other pages.', - })}{' '} + })} + {i18n.translate('xpack.enterpriseSearch.crawler.entryPointsTable.learnMoreLinkText', { defaultMessage: 'Learn more about entry points.', @@ -130,7 +137,7 @@ export const EntryPointsTable: React.FC = ({ domain, inde onAdd={onAdd} onDelete={onDelete} onUpdate={onUpdate} - title="" + title={title} disableReordering /> ); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/extraction_rules/extraction_rules.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/extraction_rules/extraction_rules.tsx index b8fdab12acbc6..0887db23c7789 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/extraction_rules/extraction_rules.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/extraction_rules/extraction_rules.tsx @@ -13,9 +13,8 @@ import { EuiButton, EuiConfirmModal, EuiEmptyPrompt, - EuiFlexGroup, - EuiFlexItem, EuiLink, + EuiFlexGroup, EuiSpacer, EuiText, EuiTitle, @@ -26,6 +25,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { CANCEL_BUTTON_LABEL } from '../../../../../../shared/constants'; import { docLinks } from '../../../../../../shared/doc_links'; +import { PageIntroduction } from '../../../../../../shared/page_introduction/page_introduction'; import { EditExtractionRule } from './edit_extraction_rule'; import { ExtractionRulesLogic } from './extraction_rules_logic'; @@ -81,56 +81,56 @@ export const ExtractionRules: React.FC = () => { )} - - -

      - {i18n.translate('xpack.enterpriseSearch.content.crawler.extractionRules.title', { - defaultMessage: 'Extraction rules', - })} -

      -
      -
      - {extractionRules.length === 0 ? ( - <> - ) : ( - - + +

      + {i18n.translate('xpack.enterpriseSearch.content.crawler.extractionRules.title', { + defaultMessage: 'Extraction rules', + })} +

      + + } + description={ +

      + +

      + } + links={ + {i18n.translate( - 'xpack.enterpriseSearch.content.crawler.extractionRulesTable.addRuleLabel', + 'xpack.enterpriseSearch.content.crawler.extractionRules.learnMoreLink', { - defaultMessage: 'Add extraction rule', + defaultMessage: 'Learn more about content extraction rules.', } )} -
      -
      - )} +
      + } + actions={ + extractionRules.length === 0 + ? [] + : [ + + {i18n.translate( + 'xpack.enterpriseSearch.content.crawler.extractionRulesTable.addRuleLabel', + { + defaultMessage: 'Add extraction rule', + } + )} + , + ] + } + /> - - -

      - - {i18n.translate( - 'xpack.enterpriseSearch.content.crawler.extractionRules.learnMoreLink', - { - defaultMessage: 'Learn more about content extraction rules.', - } - )} - - ), - }} - /> -

      -
      - + {editingExtractionRule ? ( = ({ domain, indexName, items }) => { +export const SitemapsTable: React.FC = ({ + domain, + indexName, + items, + title, +}) => { const { updateSitemaps } = useActions(CrawlerDomainDetailLogic); const field = 'url'; @@ -113,7 +119,7 @@ export const SitemapsTable: React.FC = ({ domain, indexName, updateSitemaps(newSitemaps as Sitemap[]); clearFlashMessages(); }} - title="" + title={title || ''} disableReordering /> ); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/no_connector_record.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/no_connector_record.tsx new file mode 100644 index 0000000000000..ea6dc5d7d2b62 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/no_connector_record.tsx @@ -0,0 +1,94 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { useActions, useValues } from 'kea'; + +import { EuiButton, EuiPageTemplate } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { Status } from '../../../../../../common/types/api'; + +import { RecreateCrawlerConnectorApiLogic } from '../../../api/crawler/recreate_crawler_connector_api_logic'; +import { DeleteIndexModal } from '../../search_indices/delete_index_modal'; +import { IndicesLogic } from '../../search_indices/indices_logic'; +import { IndexViewLogic } from '../index_view_logic'; + +import { NoConnectorRecordLogic } from './no_connector_record_logic'; + +export const NoConnectorRecord: React.FC = () => { + const { indexName } = useValues(IndexViewLogic); + const { isDeleteLoading } = useValues(IndicesLogic); + const { openDeleteModal } = useActions(IndicesLogic); + const { makeRequest } = useActions(RecreateCrawlerConnectorApiLogic); + const { status } = useValues(RecreateCrawlerConnectorApiLogic); + NoConnectorRecordLogic.mount(); + const buttonsDisabled = status === Status.LOADING || isDeleteLoading; + + return ( + <> + + + + {i18n.translate( + 'xpack.enterpriseSearch.content.searchIndex.noCrawlerConnectorFound.title', + { + defaultMessage: "This index's connector configuration has been removed", + } + )} + + } + body={ +

      + {i18n.translate( + 'xpack.enterpriseSearch.content.searchIndex.noCrawlerConnectorFound.description', + { + defaultMessage: + 'We could not find a connector configuration for this crawler index. The record should be recreated, or the index should be deleted.', + } + )} +

      + } + actions={[ + makeRequest({ indexName })} + > + {i18n.translate( + 'xpack.enterpriseSearch.content.searchIndex.noCrawlerConnectorFound.recreateConnectorRecord', + { + defaultMessage: 'Recreate connector record', + } + )} + , + openDeleteModal(indexName)} + > + {i18n.translate( + 'xpack.enterpriseSearch.content.searchIndex.noCrawlerConnectorFound.deleteIndex', + { + defaultMessage: 'Delete index', + } + )} + , + ]} + /> + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/no_connector_record_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/no_connector_record_logic.test.ts new file mode 100644 index 0000000000000..3d9ce1c235fb4 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/no_connector_record_logic.test.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { LogicMounter } from '../../../../__mocks__/kea_logic'; + +import { KibanaLogic } from '../../../../shared/kibana'; + +import { RecreateCrawlerConnectorApiLogic } from '../../../api/crawler/recreate_crawler_connector_api_logic'; +import { DeleteIndexApiLogic } from '../../../api/index/delete_index_api_logic'; +import { SEARCH_INDICES_PATH } from '../../../routes'; + +import { NoConnectorRecordLogic } from './no_connector_record_logic'; + +describe('NoConnectorRecordLogic', () => { + const { mount: deleteMount } = new LogicMounter(DeleteIndexApiLogic); + const { mount: recreateMount } = new LogicMounter(RecreateCrawlerConnectorApiLogic); + const { mount } = new LogicMounter(NoConnectorRecordLogic); + beforeEach(() => { + deleteMount(); + recreateMount(); + mount(); + }); + it('should redirect to search indices on delete', () => { + KibanaLogic.values.navigateToUrl = jest.fn(); + DeleteIndexApiLogic.actions.apiSuccess({} as any); + expect(KibanaLogic.values.navigateToUrl).toHaveBeenCalledWith(SEARCH_INDICES_PATH); + }); + it('should fetch index on recreate', () => { + NoConnectorRecordLogic.actions.fetchIndex = jest.fn(); + RecreateCrawlerConnectorApiLogic.actions.apiSuccess({} as any); + expect(NoConnectorRecordLogic.actions.fetchIndex).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/no_connector_record_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/no_connector_record_logic.ts new file mode 100644 index 0000000000000..a3d1d1a653ec0 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/no_connector_record_logic.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { kea, MakeLogicType } from 'kea'; + +import { KibanaLogic } from '../../../../shared/kibana'; + +import { + RecreateCrawlerConnectorActions, + RecreateCrawlerConnectorApiLogic, +} from '../../../api/crawler/recreate_crawler_connector_api_logic'; +import { + DeleteIndexApiActions, + DeleteIndexApiLogic, +} from '../../../api/index/delete_index_api_logic'; +import { SEARCH_INDICES_PATH } from '../../../routes'; +import { IndexViewActions, IndexViewLogic } from '../index_view_logic'; + +type NoConnectorRecordActions = RecreateCrawlerConnectorActions['apiSuccess'] & { + deleteSuccess: DeleteIndexApiActions['apiSuccess']; + fetchIndex: IndexViewActions['fetchIndex']; +}; + +export const NoConnectorRecordLogic = kea>({ + connect: { + actions: [ + RecreateCrawlerConnectorApiLogic, + ['apiSuccess'], + IndexViewLogic, + ['fetchIndex'], + DeleteIndexApiLogic, + ['apiSuccess as deleteSuccess'], + ], + }, + listeners: ({ actions }) => ({ + apiSuccess: () => { + actions.fetchIndex(); + }, + deleteSuccess: () => { + KibanaLogic.values.navigateToUrl(SEARCH_INDICES_PATH); + }, + }), + path: ['enterprise_search', 'content', 'no_connector_record'], +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ingest_pipelines/customize_pipeline_item.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ingest_pipelines/customize_pipeline_item.tsx index 96d213ced0f2d..e95fb524f29ab 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ingest_pipelines/customize_pipeline_item.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ingest_pipelines/customize_pipeline_item.tsx @@ -9,20 +9,32 @@ import React from 'react'; import { useActions, useValues } from 'kea'; -import { EuiButtonEmpty, EuiFlexGroup, EuiSpacer, EuiText } from '@elastic/eui'; +import { EuiButtonEmpty, EuiConfirmModal, EuiFlexGroup, EuiSpacer, EuiText } from '@elastic/eui'; + import { i18n } from '@kbn/i18n'; +import { Status } from '../../../../../../../common/types/api'; + +import { CANCEL_BUTTON_LABEL } from '../../../../../shared/constants'; + import { KibanaLogic } from '../../../../../shared/kibana'; import { LicensingLogic } from '../../../../../shared/licensing'; import { CreateCustomPipelineApiLogic } from '../../../../api/index/create_custom_pipeline_api_logic'; +import { RevertConnectorPipelineApilogic } from '../../../../api/pipelines/revert_connector_pipeline_api_logic'; import { IndexViewLogic } from '../../index_view_logic'; +import { PipelinesLogic } from '../pipelines_logic'; export const CustomizeIngestPipelineItem: React.FC = () => { const { indexName, ingestionMethod } = useValues(IndexViewLogic); const { isCloud } = useValues(KibanaLogic); const { hasPlatinumLicense } = useValues(LicensingLogic); const { makeRequest: createCustomPipeline } = useActions(CreateCustomPipelineApiLogic); + const { status: createStatus } = useValues(CreateCustomPipelineApiLogic); + const { isDeleteModalOpen, hasIndexIngestionPipeline } = useValues(PipelinesLogic); + const { closeDeleteModal, openDeleteModal } = useActions(PipelinesLogic); + const { makeRequest: revertPipeline } = useActions(RevertConnectorPipelineApilogic); + const { status: revertStatus } = useValues(RevertConnectorPipelineApilogic); const isGated = !isCloud && !hasPlatinumLicense; return ( @@ -49,17 +61,61 @@ export const CustomizeIngestPipelineItem: React.FC = () => { )} - createCustomPipeline({ indexName })} - > - {i18n.translate( - 'xpack.enterpriseSearch.content.index.pipelines.ingestFlyout.copyButtonLabel', - { defaultMessage: 'Copy and customize' } - )} - + {isDeleteModalOpen && ( + revertPipeline({ indexName })} + cancelButtonText={CANCEL_BUTTON_LABEL} + confirmButtonText={i18n.translate( + 'xpack.enterpriseSearch.content.index.pipelines.deleteModal.confirmButton', + { + defaultMessage: 'Delete pipeline', + } + )} + buttonColor="danger" + > +

      + {i18n.translate( + 'xpack.enterpriseSearch.content.index.pipelines.deleteModal.description', + { + defaultMessage: + 'This will delete any custom pipelines associated with this index, including machine learning inference pipelines. The index will revert to using the default ingest pipeline.', + } + )} +

      +
      + )} + {hasIndexIngestionPipeline ? ( + openDeleteModal()} + > + {i18n.translate( + 'xpack.enterpriseSearch.content.index.pipelines.ingestFlyout.revertPipelineLabel', + { defaultMessage: 'Delete custom pipeline' } + )} + + ) : ( + createCustomPipeline({ indexName })} + > + {i18n.translate( + 'xpack.enterpriseSearch.content.index.pipelines.ingestFlyout.copyButtonLabel', + { defaultMessage: 'Copy and customize' } + )} + + )}
      diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ingest_pipelines/ingest_pipelines_card.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ingest_pipelines/ingest_pipelines_card.test.tsx index 0ca347da0712e..a6c68b47c7b5a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ingest_pipelines/ingest_pipelines_card.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ingest_pipelines/ingest_pipelines_card.test.tsx @@ -12,8 +12,6 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { IngestPipeline } from '@elastic/elasticsearch/lib/api/types'; - import { DEFAULT_PIPELINE_NAME } from '../../../../../../../common/constants'; import { CustomPipelineItem } from './custom_pipeline_item'; @@ -52,28 +50,4 @@ describe('IngestPipelinesCard', () => { expect(wrapper.find(CustomizeIngestPipelineItem)).toHaveLength(1); expect(wrapper.find(CustomPipelineItem)).toHaveLength(0); }); - it('does not render customize cta with index ingest pipeline', () => { - const pipelineName = crawlerIndex.name; - const pipelines: Record = { - [pipelineName]: {}, - [`${pipelineName}@custom`]: { - processors: [], - }, - }; - setMockValues({ - ...DEFAULT_VALUES, - data: pipelines, - hasIndexIngestionPipeline: true, - pipelineName, - pipelineState: { - ...DEFAULT_VALUES.pipelineState, - name: pipelineName, - }, - }); - - const wrapper = shallow(); - expect(wrapper.find(CustomizeIngestPipelineItem)).toHaveLength(0); - expect(wrapper.find(DefaultPipelineItem)).toHaveLength(1); - expect(wrapper.find(CustomPipelineItem)).toHaveLength(1); - }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ingest_pipelines/ingest_pipelines_card.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ingest_pipelines/ingest_pipelines_card.tsx index d57b4142d2905..64f195e217891 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ingest_pipelines/ingest_pipelines_card.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ingest_pipelines/ingest_pipelines_card.tsx @@ -24,14 +24,8 @@ import { IngestPipelineFlyout } from './ingest_pipeline_flyout'; export const IngestPipelinesCard: React.FC = () => { const { indexName, ingestionMethod } = useValues(IndexViewLogic); - const { - canSetPipeline, - hasIndexIngestionPipeline, - index, - pipelineName, - pipelineState, - showPipelineSettings, - } = useValues(PipelinesLogic); + const { canSetPipeline, index, pipelineName, pipelineState, showPipelineSettings } = + useValues(PipelinesLogic); const { closePipelineSettings, openPipelineSettings, setPipelineState, savePipeline } = useActions(PipelinesLogic); const { makeRequest: fetchCustomPipeline } = useActions(FetchCustomPipelineApiLogic); @@ -45,7 +39,7 @@ export const IngestPipelinesCard: React.FC = () => { return ( <> - {!hasIndexIngestionPipeline && } + {showPipelineSettings && ( { - const { showAddMlInferencePipelineModal, hasIndexIngestionPipeline, index, pipelineName } = - useValues(PipelinesLogic); + const { + showMissingPipelineCallout, + showAddMlInferencePipelineModal, + hasIndexIngestionPipeline, + index, + pipelineName, + } = useValues(PipelinesLogic); const { closeAddMlInferencePipelineModal, openAddMlInferencePipelineModal } = useActions(PipelinesLogic); + const { indexName } = useValues(IndexNameLogic); + const { makeRequest: revertPipeline } = useActions(RevertConnectorPipelineApilogic); const apiIndex = isApiIndex(index); const pipelinesTabs: EuiTabbedContentTab[] = [ @@ -66,6 +78,36 @@ export const SearchIndexPipelines: React.FC = () => { return ( <> + {showMissingPipelineCallout && ( + +

      + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.missingPipeline.description', + { + defaultMessage: + 'The custom pipeline for this index has been deleted. This may affect connector data ingestion. Its configuration will need to be reverted to the default pipeline settings.', + } + )} +

      + revertPipeline({ indexName })}> + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.missingPipeline.buttonLabel', + { + defaultMessage: 'Revert pipeline to default', + } + )} + +
      + )} diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.test.ts index a16bd36ee54ba..69e01d45750fe 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.test.ts @@ -35,10 +35,12 @@ const DEFAULT_VALUES = { hasIndexIngestionPipeline: false, index: undefined, indexName: '', + isDeleteModalOpen: false, mlInferencePipelineProcessors: undefined, pipelineName: DEFAULT_PIPELINE_VALUES.name, pipelineState: DEFAULT_PIPELINE_VALUES, showAddMlInferencePipelineModal: false, + showMissingPipelineCallout: false, showPipelineSettings: false, }; @@ -145,16 +147,16 @@ describe('PipelinesLogic', () => { }); }); describe('createCustomPipelineSuccess', () => { - it('should call flashSuccessToast', () => { + it('should call flashSuccessToast and update pipelines', () => { PipelinesLogic.actions.setPipelineState = jest.fn(); PipelinesLogic.actions.savePipeline = jest.fn(); PipelinesLogic.actions.fetchCustomPipeline = jest.fn(); PipelinesLogic.actions.fetchIndexApiSuccess(connectorIndex); - PipelinesLogic.actions.createCustomPipelineSuccess({ created: ['a', 'b'] }); + PipelinesLogic.actions.createCustomPipelineSuccess({ [connectorIndex.name]: {} }); expect(flashSuccessToast).toHaveBeenCalledWith('Custom pipeline created'); expect(PipelinesLogic.actions.setPipelineState).toHaveBeenCalledWith({ ...PipelinesLogic.values.pipelineState, - name: 'a', + name: connectorIndex.name, }); expect(PipelinesLogic.actions.savePipeline).toHaveBeenCalled(); expect(PipelinesLogic.actions.fetchCustomPipeline).toHaveBeenCalled(); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.ts index dbe0239028ad5..bf71dc215243f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.ts @@ -66,6 +66,10 @@ import { } from '../../../api/pipelines/detach_ml_inference_pipeline'; import { FetchMlInferencePipelineProcessorsApiLogic } from '../../../api/pipelines/fetch_ml_inference_pipeline_processors'; +import { + RevertConnectorPipelineActions, + RevertConnectorPipelineApilogic, +} from '../../../api/pipelines/revert_connector_pipeline_api_logic'; import { isApiIndex, isConnectorIndex, isCrawlerIndex } from '../../../utils/indices'; type PipelinesActions = Pick< @@ -77,6 +81,7 @@ type PipelinesActions = Pick< AttachMlInferencePipelineResponse >['apiSuccess']; closeAddMlInferencePipelineModal: () => void; + closeDeleteModal: () => void; closePipelineSettings: () => void; createCustomPipeline: Actions< CreateCustomPipelineApiLogicArgs, @@ -132,7 +137,9 @@ type PipelinesActions = Pick< fetchMlInferenceProcessors: typeof FetchMlInferencePipelineProcessorsApiLogic.actions.makeRequest; fetchMlInferenceProcessorsApiError: (error: HttpError) => HttpError; openAddMlInferencePipelineModal: () => void; + openDeleteModal: () => void; openPipelineSettings: () => void; + revertPipelineSuccess: RevertConnectorPipelineActions['apiSuccess']; savePipeline: () => void; setPipelineState(pipeline: IngestPipelineParams): { pipeline: IngestPipelineParams; @@ -148,18 +155,22 @@ interface PipelinesValues { hasIndexIngestionPipeline: boolean; index: CachedFetchIndexApiLogicValues['fetchIndexApiData']; indexName: string; + isDeleteModalOpen: boolean; mlInferencePipelineProcessors: InferencePipeline[]; pipelineName: string; pipelineState: IngestPipelineParams; showAddMlInferencePipelineModal: boolean; + showMissingPipelineCallout: boolean; showPipelineSettings: boolean; } export const PipelinesLogic = kea>({ actions: { closeAddMlInferencePipelineModal: true, + closeDeleteModal: true, closePipelineSettings: true, openAddMlInferencePipelineModal: true, + openDeleteModal: true, openPipelineSettings: true, savePipeline: true, setPipelineState: (pipeline: IngestPipelineParams) => ({ pipeline }), @@ -201,6 +212,8 @@ export const PipelinesLogic = kea { - actions.setPipelineState({ ...values.pipelineState, name: created[0] }); + createCustomPipelineSuccess: (created) => { + actions.fetchCustomPipelineSuccess(created); + actions.setPipelineState({ ...values.pipelineState, name: values.indexName }); actions.savePipeline(); actions.fetchCustomPipeline({ indexName: values.index.name }); }, @@ -313,6 +327,19 @@ export const PipelinesLogic = kea { + if (isConnectorIndex(values.index) || isCrawlerIndex(values.index)) { + if (values.index.connector) { + // had to split up these if checks rather than nest them or typescript wouldn't recognize connector as defined + actions.fetchIndexApiSuccess({ + ...values.index, + connector: { ...values.index.connector, pipeline: values.defaultPipelineValues }, + }); + actions.fetchCustomPipelineSuccess({}); + } + } + actions.fetchCustomPipeline({ indexName: values.indexName }); + }, savePipeline: () => { if (isConnectorIndex(values.index) || isCrawlerIndex(values.index)) { if (values.index.connector) { @@ -326,6 +353,14 @@ export const PipelinesLogic = kea ({ + isDeleteModalOpen: [ + false, + { + closeDeleteModal: () => false, + openDeleteModal: () => true, + revertPipelineSuccess: () => false, + }, + ], pipelineState: [ DEFAULT_PIPELINE_VALUES, { @@ -381,5 +416,25 @@ export const PipelinesLogic = kea customPipelineData && customPipelineData[indexName] ? indexName : pipelineState.name, ], + showMissingPipelineCallout: [ + () => [ + selectors.hasIndexIngestionPipeline, + selectors.pipelineName, + selectors.customPipelineData, + selectors.index, + ], + ( + hasCustomPipeline: boolean, + pipelineName: string, + customPipelineData: Record | undefined, + index: ElasticsearchIndexWithIngestion + ) => + Boolean( + hasCustomPipeline && + customPipelineData && + !customPipelineData[pipelineName] && + isConnectorIndex(index) + ), + ], }), }); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/search_index.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/search_index.tsx index 0643cce0f8173..52ae6628f8081 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/search_index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/search_index.tsx @@ -38,6 +38,7 @@ import { AutomaticCrawlScheduler } from './crawler/automatic_crawl_scheduler/aut import { CrawlCustomSettingsFlyout } from './crawler/crawl_custom_settings_flyout/crawl_custom_settings_flyout'; import { CrawlerConfiguration } from './crawler/crawler_configuration/crawler_configuration'; import { SearchIndexDomainManagement } from './crawler/domain_management/domain_management'; +import { NoConnectorRecord } from './crawler/no_connector_record'; import { SearchIndexDocuments } from './documents'; import { SearchIndexIndexMappings } from './index_mappings'; import { IndexNameLogic } from './index_name_logic'; @@ -224,12 +225,16 @@ export const SearchIndex: React.FC = () => { rightSideItems: getHeaderActions(index), }} > - <> - {indexName === index?.name && ( - - )} - {isCrawlerIndex(index) && } - + {isCrawlerIndex(index) && !index.connector ? ( + + ) : ( + <> + {indexName === index?.name && ( + + )} + {isCrawlerIndex(index) && } + + )} ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/indices_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/indices_logic.test.ts index 16ee5faff914a..89043f1d7c0c8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/indices_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/indices_logic.test.ts @@ -85,7 +85,7 @@ describe('IndicesLogic', () => { describe('openDeleteModal', () => { it('should set deleteIndexName and set isDeleteModalVisible to true', () => { IndicesLogic.actions.fetchIndexDetails = jest.fn(); - IndicesLogic.actions.openDeleteModal(connectorIndex); + IndicesLogic.actions.openDeleteModal(connectorIndex.name); expect(IndicesLogic.values).toEqual({ ...DEFAULT_VALUES, deleteModalIndexName: 'connector', @@ -98,7 +98,7 @@ describe('IndicesLogic', () => { }); describe('closeDeleteModal', () => { it('should set deleteIndexName to empty and set isDeleteModalVisible to false', () => { - IndicesLogic.actions.openDeleteModal(connectorIndex); + IndicesLogic.actions.openDeleteModal(connectorIndex.name); IndicesLogic.actions.fetchIndexDetails = jest.fn(); IndicesLogic.actions.closeDeleteModal(); expect(IndicesLogic.values).toEqual({ diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/indices_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/indices_logic.ts index 953fce853904b..9489a005d0b15 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/indices_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/indices_logic.ts @@ -69,7 +69,7 @@ export interface IndicesActions { }): { meta: Meta; returnHiddenIndices: boolean; searchQuery?: string }; makeRequest: typeof FetchIndicesAPILogic.actions.makeRequest; onPaginate(newPageIndex: number): { newPageIndex: number }; - openDeleteModal(index: ElasticsearchViewIndex): { index: ElasticsearchViewIndex }; + openDeleteModal(indexName: string): { indexName: string }; setIsFirstRequest(): void; } export interface IndicesValues { @@ -102,7 +102,7 @@ export const IndicesLogic = kea>({ searchQuery, }), onPaginate: (newPageIndex) => ({ newPageIndex }), - openDeleteModal: (index) => ({ index }), + openDeleteModal: (indexName) => ({ indexName }), setIsFirstRequest: true, }, connect: { @@ -137,8 +137,8 @@ export const IndicesLogic = kea>({ await breakpoint(150); actions.makeRequest(input); }, - openDeleteModal: ({ index }) => { - actions.fetchIndexDetails({ indexName: index.name }); + openDeleteModal: ({ indexName }) => { + actions.fetchIndexDetails({ indexName }); }, }), path: ['enterprise_search', 'content', 'indices_logic'], @@ -147,7 +147,7 @@ export const IndicesLogic = kea>({ '', { closeDeleteModal: () => '', - openDeleteModal: (_, { index: { name } }) => name, + openDeleteModal: (_, { indexName }) => indexName, }, ], isDeleteModalVisible: [ diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/indices_table.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/indices_table.tsx index fd0bc55fdc7e3..c73f567f31edb 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/indices_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/indices_table.tsx @@ -38,7 +38,7 @@ interface IndicesTableProps { isLoading?: boolean; meta: Meta; onChange: (criteria: CriteriaWithPagination) => void; - onDelete: (index: ElasticsearchViewIndex) => void; + onDelete: (indexName: string) => void; } export const IndicesTable: React.FC = ({ @@ -175,7 +175,7 @@ export const IndicesTable: React.FC = ({ }, } ), - onClick: (index) => onDelete(index), + onClick: (index) => onDelete(index.name), type: 'icon', }, ], diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/utils/indices.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/utils/indices.ts index fd87b60bd99a8..f88620faf5b16 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/utils/indices.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/utils/indices.ts @@ -81,7 +81,7 @@ export function getIngestionStatus(index?: ElasticsearchIndexWithIngestion): Ing if (!index || isApiIndex(index)) { return IngestionStatus.CONNECTED; } - if (isConnectorIndex(index) || isCrawlerIndex(index)) { + if (isConnectorIndex(index) || (isCrawlerIndex(index) && index.connector)) { if ( index.connector.last_seen && moment(index.connector.last_seen).isBefore(moment().subtract(30, 'minutes')) diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/page_introduction/page_introduction.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/page_introduction/page_introduction.test.tsx new file mode 100644 index 0000000000000..4077dac076839 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/page_introduction/page_introduction.test.tsx @@ -0,0 +1,129 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { mount } from 'enzyme'; + +import { EuiLink } from '@elastic/eui'; + +import { PageIntroduction } from './page_introduction'; + +describe('PageIntroduction component', () => { + it('renders with title as a string', () => { + const wrapper = mount(); + // .hostNodes is required due to Emotion injection causing problems with enzyme + const titleContainer = wrapper + .find('[data-test-subj="pageIntroductionTitleContainer"]') + .hostNodes(); + expect(titleContainer).toHaveLength(1); + + expect(titleContainer.text()).toEqual('string title'); + }); + + it('renders title as React node', () => { + const wrapper = mount( + react node title} + description="some description" + /> + ); + // .hostNodes is required due to Emotion injection causing problems with enzyme + const titleContainer = wrapper.find('[data-test-subj="injected"]').hostNodes(); + expect(titleContainer).toHaveLength(1); + + expect(titleContainer.text()).toEqual('react node title'); + }); + + it('renders with description only', () => { + const wrapper = mount(); + // .hostNodes is required due to Emotion injection causing problems with enzyme + const titleContainer = wrapper + .find('[data-test-subj="pageIntroductionTitleContainer"]') + .hostNodes(); + + const descriptionContainer = wrapper + .find('[data-test-subj="pageIntroductionDescriptionText"]') + .hostNodes(); + expect(titleContainer).toHaveLength(1); + expect(descriptionContainer).toHaveLength(1); + + expect(titleContainer.text()).toEqual(''); + expect(descriptionContainer.text()).toEqual('some description'); + }); + + it('renders with single link', () => { + const wrapper = mount( + + test link to nowhere + + } + /> + ); + const links = wrapper.find(EuiLink); + expect(links).toHaveLength(1); + expect(links.prop('href')).toEqual('testlink'); + // due to accesibility injections text includes screen reader text as well + expect(links.text().startsWith('test link to nowhere')).toBe(true); + }); + + it('renders with multiple links', () => { + const wrapper = mount( + + test link to nowhere + , + + test link to nowhere2 + , + ]} + /> + ); + const links = wrapper.find(EuiLink); + expect(links).toHaveLength(2); + expect(links.at(0).prop('href')).toEqual('testlink'); + // due to accesibility injections text includes screen reader text as well + expect(links.at(0).text().startsWith('test link to nowhere')).toBe(true); + expect(links.at(1).prop('href')).toEqual('testlink2'); + // due to accesibility injections text includes screen reader text as well + expect(links.at(1).text().startsWith('test link to nowhere2')).toBe(true); + }); + + it('renders with single actions', () => { + const wrapper = mount( + some action} + /> + ); + const actions = wrapper.find('button'); + expect(actions).toHaveLength(1); + expect(actions.text()).toEqual('some action'); + }); + + it('renders with multiple action', () => { + const wrapper = mount( + some action, ]} + /> + ); + const actions = wrapper.find('button'); + expect(actions).toHaveLength(2); + expect(actions.at(0).text()).toEqual('some action'); + expect(actions.at(1).text()).toEqual('another action'); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/page_introduction/page_introduction.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/page_introduction/page_introduction.tsx new file mode 100644 index 0000000000000..1f2e5951bb94f --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/page_introduction/page_introduction.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 from 'react'; + +import { EuiText, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle } from '@elastic/eui'; + +export interface PageIntroductionProps { + actions?: React.ReactNode | React.ReactNode[]; + description: React.ReactNode | string; + links?: React.ReactNode | React.ReactNode[]; + title?: string | React.ReactNode; +} + +export const PageIntroduction: React.FC = ({ + actions, + description, + links, + title = '', +}) => { + return ( + + + + + {typeof title === 'string' ? ( + +

      {title}

      +
      + ) : ( + title + )} +
      + + + + {description} + + + {!!links && ( + <> + + + + {Array.isArray(links) ? links.map((link) => link) : links} + + + + )} +
      +
      + + + {Array.isArray(actions) + ? actions?.map((action, index) => ( + + {action} + + )) + : actions} + + +
      + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/inline_editable_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/inline_editable_table.test.tsx index 9725abcd6eba7..3d2a243664edf 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/inline_editable_table.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/inline_editable_table.test.tsx @@ -12,6 +12,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import { BindLogic } from 'kea'; +import { PageIntroduction } from '../../page_introduction/page_introduction'; import { ReorderableTable } from '../reorderable_table'; jest.mock('./get_updated_columns', () => ({ @@ -86,18 +87,19 @@ describe('InlineEditableTable', () => { it('renders a ReorderableTable', () => { const wrapper = shallow(); const reorderableTable = wrapper.find(ReorderableTable); + const addButton = shallow( +
      {wrapper.find(PageIntroduction).prop('actions')}
      + ).children(); expect(reorderableTable.exists()).toBe(true); expect(reorderableTable.prop('items')).toEqual(items); - expect( - wrapper.find('[data-test-subj="inlineEditableTableActionButton"]').children().text() - ).toEqual('New row'); + expect(addButton.children().text()).toEqual('New row'); }); it('renders a title if one is provided', () => { const wrapper = shallow( Some Description

      } /> ); - expect(wrapper.find('[data-test-subj="inlineEditableTableTitle"]').exists()).toBe(true); + expect(wrapper.find(PageIntroduction).prop('title')).toEqual(requiredParams.title); }); it('does not render a title if none is provided', () => { @@ -114,7 +116,7 @@ describe('InlineEditableTable', () => { const wrapper = shallow( Some Description

      } /> ); - expect(wrapper.find('[data-test-subj="inlineEditableTableDescription"]').exists()).toBe(true); + expect(wrapper.find(PageIntroduction).prop('description')).toEqual(

      Some Description

      ); }); it('renders no description if none is provided', () => { @@ -141,9 +143,10 @@ describe('InlineEditableTable', () => { const wrapper = shallow( ); - expect( - wrapper.find('[data-test-subj="inlineEditableTableActionButton"]').children().text() - ).toEqual('Add a new row custom text'); + const addButton = shallow( +
      {wrapper.find(PageIntroduction).prop('actions')}
      + ).children(); + expect(addButton.children().text()).toEqual('Add a new row custom text'); }); describe('when a user is editing an unsaved item', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/inline_editable_table.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/inline_editable_table.tsx index 232ad491b1397..d14acc91ac581 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/inline_editable_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/inline_editable_table.tsx @@ -11,9 +11,11 @@ import classNames from 'classnames'; import { useActions, useValues, BindLogic } from 'kea'; -import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui'; +import { EuiButton, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { PageIntroduction } from '../../page_introduction/page_introduction'; + import { ReorderableTable } from '../reorderable_table'; import { ItemWithAnID } from '../types'; @@ -29,7 +31,7 @@ export interface InlineEditableTableProps { items: Item[]; defaultItem?: Partial; emptyPropertyAllowed?: boolean; - title: string; + title: string | React.ReactNode; addButtonText?: string; canRemoveLastItem?: boolean; className?: string; @@ -129,28 +131,10 @@ export const InlineEditableTableContents = ({ return ( <> - - - {!!title && ( - -

      {title}

      -
      - )} - {!!description && ( - <> - - - {description} - - - )} -
      - + ({ i18n.translate('xpack.enterpriseSearch.inlineEditableTable.newRowButtonLabel', { defaultMessage: 'New row', })} - - -
      - + , + ]} + /> + { + const mockClient = { + asCurrentUser: { + index: jest.fn(), + }, + asInternalUser: {}, + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should recreate connector document', async () => { + mockClient.asCurrentUser.index.mockResolvedValue({ _id: 'connectorId' }); + + await recreateConnectorDocument(mockClient as unknown as IScopedClusterClient, 'indexName'); + expect(mockClient.asCurrentUser.index).toHaveBeenCalledWith({ + document: { + api_key_id: null, + configuration: {}, + custom_scheduling: {}, + description: null, + error: null, + features: null, + filtering: [ + { + active: { + advanced_snippet: { + created_at: expect.any(String), + updated_at: expect.any(String), + value: {}, + }, + rules: [ + { + created_at: expect.any(String), + field: '_', + id: 'DEFAULT', + order: 0, + policy: 'include', + rule: 'regex', + updated_at: expect.any(String), + value: '.*', + }, + ], + validation: { + errors: [], + state: 'valid', + }, + }, + domain: 'DEFAULT', + draft: { + advanced_snippet: { + created_at: expect.any(String), + updated_at: expect.any(String), + value: {}, + }, + rules: [ + { + created_at: expect.any(String), + field: '_', + id: 'DEFAULT', + order: 0, + policy: 'include', + rule: 'regex', + updated_at: expect.any(String), + value: '.*', + }, + ], + validation: { + errors: [], + state: 'valid', + }, + }, + }, + ], + index_name: 'indexName', + is_native: false, + language: '', + last_seen: null, + last_sync_error: null, + last_sync_status: null, + last_synced: null, + name: 'indexName', + pipeline: null, + scheduling: { enabled: false, interval: '0 0 0 * * ?' }, + service_type: 'elastic-crawler', + status: ConnectorStatus.CONFIGURED, + sync_now: false, + }, + index: CONNECTORS_INDEX, + refresh: 'wait_for', + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/server/lib/crawler/post_connector.ts b/x-pack/plugins/enterprise_search/server/lib/crawler/post_connector.ts new file mode 100644 index 0000000000000..17bf6945d0d82 --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/lib/crawler/post_connector.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 { IScopedClusterClient } from '@kbn/core-elasticsearch-server'; + +import { CONNECTORS_INDEX } from '../..'; + +import { ENTERPRISE_SEARCH_CONNECTOR_CRAWLER_SERVICE_TYPE } from '../../../common/constants'; +import { ConnectorStatus } from '../../../common/types/connectors'; + +import { createConnectorDocument } from '../../utils/create_connector_document'; + +export const recreateConnectorDocument = async ( + client: IScopedClusterClient, + indexName: string +) => { + const document = createConnectorDocument({ + indexName, + isNative: false, + // The search index has already been created so we don't need the language, which we can't retrieve anymore anyway + language: '', + pipeline: null, + serviceType: ENTERPRISE_SEARCH_CONNECTOR_CRAWLER_SERVICE_TYPE, + }); + const result = await client.asCurrentUser.index({ + document: { ...document, status: ConnectorStatus.CONFIGURED }, + index: CONNECTORS_INDEX, + refresh: 'wait_for', + }); + return result._id; +}; diff --git a/x-pack/plugins/enterprise_search/server/lib/engines/field_capabilities.test.ts b/x-pack/plugins/enterprise_search/server/lib/engines/field_capabilities.test.ts new file mode 100644 index 0000000000000..3e250e4ec9109 --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/lib/engines/field_capabilities.test.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FieldCapsResponse } from '@elastic/elasticsearch/lib/api/types'; +import { IScopedClusterClient } from '@kbn/core-elasticsearch-server'; + +import { EnterpriseSearchEngineDetails } from '../../../common/types/engines'; + +import { fetchEngineFieldCapabilities } from './field_capabilities'; + +describe('engines field_capabilities', () => { + const mockClient = { + asCurrentUser: { + fieldCaps: jest.fn(), + }, + asInternalUser: {}, + }; + const mockEngine: EnterpriseSearchEngineDetails = { + created: '1999-12-31T23:59:59.999Z', + indices: [], + name: 'unit-test-engine', + updated: '1999-12-31T23:59:59.999Z', + }; + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('gets engine alias field capabilities', async () => { + const fieldCapsResponse = {} as FieldCapsResponse; + + mockClient.asCurrentUser.fieldCaps.mockResolvedValueOnce(fieldCapsResponse); + await expect( + fetchEngineFieldCapabilities(mockClient as unknown as IScopedClusterClient, mockEngine) + ).resolves.toEqual({ + created: mockEngine.created, + field_capabilities: fieldCapsResponse, + name: mockEngine.name, + updated: mockEngine.updated, + }); + + expect(mockClient.asCurrentUser.fieldCaps).toHaveBeenCalledTimes(1); + expect(mockClient.asCurrentUser.fieldCaps).toHaveBeenCalledWith({ + fields: '*', + include_unmapped: true, + index: 'search-engine-unit-test-engine', + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/server/lib/engines/field_capabilities.ts b/x-pack/plugins/enterprise_search/server/lib/engines/field_capabilities.ts new file mode 100644 index 0000000000000..ed42ab744621c --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/lib/engines/field_capabilities.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { IScopedClusterClient } from '@kbn/core-elasticsearch-server'; + +import { + EnterpriseSearchEngineDetails, + EnterpriseSearchEngineFieldCapabilities, +} from '../../../common/types/engines'; + +export const fetchEngineFieldCapabilities = async ( + client: IScopedClusterClient, + engine: EnterpriseSearchEngineDetails +): Promise => { + const { created, name, updated } = engine; + const fieldCapabilities = await client.asCurrentUser.fieldCaps({ + fields: '*', + include_unmapped: true, + index: getEngineIndexAliasName(name), + }); + return { + created, + field_capabilities: fieldCapabilities, + name, + updated, + }; +}; + +// Note: This will likely need to be modified when engines move to es module +const getEngineIndexAliasName = (engineName: string): string => `search-engine-${engineName}`; diff --git a/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.test.ts b/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.test.ts index 23232fda4199d..91859d66f6e55 100644 --- a/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.test.ts @@ -8,8 +8,6 @@ import { merge } from 'lodash'; import { ElasticsearchClient } from '@kbn/core/server'; -import { getInferencePipelineNameFromIndexName } from '../../utils/ml_inference_pipeline_utils'; - import { createIndexPipelineDefinitions } from './create_pipeline_definitions'; import { formatMlPipelineBody } from './create_pipeline_definitions'; @@ -22,19 +20,13 @@ describe('createIndexPipelineDefinitions util function', () => { }, }; - const expectedResult = { - created: [indexName, `${indexName}@custom`, getInferencePipelineNameFromIndexName(indexName)], - }; - beforeEach(() => { jest.clearAllMocks(); }); it('should create the pipelines', async () => { mockClient.ingest.putPipeline.mockImplementation(() => Promise.resolve({ acknowledged: true })); - await expect( - createIndexPipelineDefinitions(indexName, mockClient as unknown as ElasticsearchClient) - ).resolves.toEqual(expectedResult); + await createIndexPipelineDefinitions(indexName, mockClient as unknown as ElasticsearchClient); expect(mockClient.ingest.putPipeline).toHaveBeenCalledTimes(3); }); }); diff --git a/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.ts b/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.ts index 8b511ab22c3e7..209d9d4787ea3 100644 --- a/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.ts +++ b/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { IngestPipeline } from '@elastic/elasticsearch/lib/api/types'; import { ElasticsearchClient } from '@kbn/core/server'; import { generateMlInferencePipelineBody } from '../../../common/ml_inference_pipeline'; @@ -14,10 +15,6 @@ import { } from '../../../common/types/pipelines'; import { getInferencePipelineNameFromIndexName } from '../../utils/ml_inference_pipeline_utils'; -export interface CreatedPipelines { - created: string[]; -} - /** * Used to create index-specific Ingest Pipelines to be used in conjunction with Enterprise Search * ingestion mechanisms. Three pipelines are created: @@ -33,189 +30,202 @@ export interface CreatedPipelines { export const createIndexPipelineDefinitions = async ( indexName: string, esClient: ElasticsearchClient -): Promise => { +): Promise> => { // TODO: add back descriptions (see: https://github.com/elastic/elasticsearch-specification/issues/1827) - await esClient.ingest.putPipeline({ - description: `Enterprise Search Machine Learning Inference pipeline for the '${indexName}' index`, - id: getInferencePipelineNameFromIndexName(indexName), - processors: [], - version: 1, - }); - await esClient.ingest.putPipeline({ - description: `Enterprise Search customizable ingest pipeline for the '${indexName}' index`, - id: `${indexName}@custom`, - processors: [], - version: 1, - }); - await esClient.ingest.putPipeline({ - _meta: { - managed: true, - managed_by: 'Enterprise Search', - }, - description: `Enterprise Search ingest pipeline for the '${indexName}' index`, - id: `${indexName}`, - processors: [ - { - attachment: { - field: '_attachment', - if: 'ctx?._extract_binary_content == true', - ignore_missing: true, - indexed_chars_field: '_attachment_indexed_chars', - on_failure: [ - { - append: { - field: '_ingestion_errors', - value: [ - [ - "Processor 'attachment' in pipeline '{{ _ingest.on_failure_pipeline }}' failed with message '{{ _ingest.on_failure_message }}'", + let result: Record = {}; + try { + const mlPipeline = { + description: `Enterprise Search Machine Learning Inference pipeline for the '${indexName}' index`, + id: getInferencePipelineNameFromIndexName(indexName), + processors: [], + version: 1, + }; + await esClient.ingest.putPipeline(mlPipeline); + result = { ...result, [mlPipeline.id]: mlPipeline }; + const customPipeline = { + description: `Enterprise Search customizable ingest pipeline for the '${indexName}' index`, + id: `${indexName}@custom`, + processors: [], + version: 1, + }; + await esClient.ingest.putPipeline(customPipeline); + result = { ...result, [customPipeline.id]: customPipeline }; + const ingestPipeline = { + _meta: { + managed: true, + managed_by: 'Enterprise Search', + }, + description: `Enterprise Search ingest pipeline for the '${indexName}' index`, + id: `${indexName}`, + processors: [ + { + attachment: { + field: '_attachment', + if: 'ctx?._extract_binary_content == true', + ignore_missing: true, + indexed_chars_field: '_attachment_indexed_chars', + on_failure: [ + { + append: { + field: '_ingestion_errors', + value: [ + [ + "Processor 'attachment' in pipeline '{{ _ingest.on_failure_pipeline }}' failed with message '{{ _ingest.on_failure_message }}'", + ], ], - ], + }, }, - }, - ], - target_field: '_extracted_attachment', + ], + target_field: '_extracted_attachment', + }, }, - }, - { - set: { - field: 'body', - if: 'ctx?._extract_binary_content == true', - on_failure: [ - { - append: { - field: '_ingestion_errors', - value: [ - [ - "Processor 'set' with tag 'set_body' in pipeline '{{ _ingest.on_failure_pipeline }}' failed with message '{{ _ingest.on_failure_message }}'", + { + set: { + field: 'body', + if: 'ctx?._extract_binary_content == true', + on_failure: [ + { + append: { + field: '_ingestion_errors', + value: [ + [ + "Processor 'set' with tag 'set_body' in pipeline '{{ _ingest.on_failure_pipeline }}' failed with message '{{ _ingest.on_failure_message }}'", + ], ], - ], + }, }, - }, - ], - tag: 'set_body', - value: '{{{_extracted_attachment.content}}}', + ], + tag: 'set_body', + value: '{{{_extracted_attachment.content}}}', + }, }, - }, - { - pipeline: { - if: 'ctx?._run_ml_inference == true', - name: getInferencePipelineNameFromIndexName(indexName), - on_failure: [ - { - append: { - field: '_ingestion_errors', - value: [ - "Processor 'pipeline' with tag 'index_ml_inference_pipeline' in pipeline '{{ _ingest.on_failure_pipeline }}' failed with message '{{ _ingest.on_failure_message }}'", - ], + { + pipeline: { + if: 'ctx?._run_ml_inference == true', + name: getInferencePipelineNameFromIndexName(indexName), + on_failure: [ + { + append: { + field: '_ingestion_errors', + value: [ + "Processor 'pipeline' with tag 'index_ml_inference_pipeline' in pipeline '{{ _ingest.on_failure_pipeline }}' failed with message '{{ _ingest.on_failure_message }}'", + ], + }, }, - }, - ], - tag: 'index_ml_inference_pipeline', + ], + tag: 'index_ml_inference_pipeline', + }, }, - }, - { - pipeline: { - name: `${indexName}@custom`, - on_failure: [ - { - append: { - field: '_ingestion_errors', - value: [ - "Processor 'pipeline' with tag 'index_custom_pipeline' in pipeline '{{ _ingest.on_failure_pipeline }}' failed with message '{{ _ingest.on_failure_message }}'", - ], + { + pipeline: { + name: `${indexName}@custom`, + on_failure: [ + { + append: { + field: '_ingestion_errors', + value: [ + "Processor 'pipeline' with tag 'index_custom_pipeline' in pipeline '{{ _ingest.on_failure_pipeline }}' failed with message '{{ _ingest.on_failure_message }}'", + ], + }, }, - }, - ], - tag: 'index_custom_pipeline', + ], + tag: 'index_custom_pipeline', + }, }, - }, - { - gsub: { - field: 'body', - if: 'ctx?._extract_binary_content == true', - ignore_missing: true, - on_failure: [ - { - append: { - field: '_ingestion_errors', - value: [ - "Processor 'gsub' with tag 'remove_replacement_chars' in pipeline '{{ _ingest.on_failure_pipeline }}' failed with message '{{ _ingest.on_failure_message }}'", - ], + { + gsub: { + field: 'body', + if: 'ctx?._extract_binary_content == true', + ignore_missing: true, + on_failure: [ + { + append: { + field: '_ingestion_errors', + value: [ + "Processor 'gsub' with tag 'remove_replacement_chars' in pipeline '{{ _ingest.on_failure_pipeline }}' failed with message '{{ _ingest.on_failure_message }}'", + ], + }, }, - }, - ], - pattern: '�', - replacement: '', - tag: 'remove_replacement_chars', + ], + pattern: '�', + replacement: '', + tag: 'remove_replacement_chars', + }, }, - }, - { - gsub: { - field: 'body', - if: 'ctx?._reduce_whitespace == true', - ignore_missing: true, - on_failure: [ - { - append: { - field: '_ingestion_errors', - value: [ - "Processor 'gsub' with tag 'remove_extra_whitespace' in pipeline '{{ _ingest.on_failure_pipeline }}' failed with message '{{ _ingest.on_failure_message }}'", - ], + { + gsub: { + field: 'body', + if: 'ctx?._reduce_whitespace == true', + ignore_missing: true, + on_failure: [ + { + append: { + field: '_ingestion_errors', + value: [ + "Processor 'gsub' with tag 'remove_extra_whitespace' in pipeline '{{ _ingest.on_failure_pipeline }}' failed with message '{{ _ingest.on_failure_message }}'", + ], + }, }, - }, - ], - pattern: '\\s+', - replacement: ' ', - tag: 'remove_extra_whitespace', + ], + pattern: '\\s+', + replacement: ' ', + tag: 'remove_extra_whitespace', + }, }, - }, - { - trim: { - field: 'body', - if: 'ctx?._reduce_whitespace == true', - ignore_missing: true, - on_failure: [ - { - append: { - field: '_ingestion_errors', - value: [ - "Processor 'trim' in pipeline '{{ _ingest.on_failure_pipeline }}' failed with message '{{ _ingest.on_failure_message }}'", - ], + { + trim: { + field: 'body', + if: 'ctx?._reduce_whitespace == true', + ignore_missing: true, + on_failure: [ + { + append: { + field: '_ingestion_errors', + value: [ + "Processor 'trim' in pipeline '{{ _ingest.on_failure_pipeline }}' failed with message '{{ _ingest.on_failure_message }}'", + ], + }, }, - }, - ], + ], + }, }, - }, - { - remove: { - field: [ - '_attachment', - '_attachment_indexed_chars', - '_extracted_attachment', - '_extract_binary_content', - '_reduce_whitespace', - '_run_ml_inference', - ], - ignore_missing: true, - on_failure: [ - { - append: { - field: '_ingestion_errors', - value: [ - "Processor 'remove' with tag 'remove_meta_fields' in pipeline '{{ _ingest.on_failure_pipeline }}' failed with message '{{ _ingest.on_failure_message }}'", - ], + { + remove: { + field: [ + '_attachment', + '_attachment_indexed_chars', + '_extracted_attachment', + '_extract_binary_content', + '_reduce_whitespace', + '_run_ml_inference', + ], + ignore_missing: true, + on_failure: [ + { + append: { + field: '_ingestion_errors', + value: [ + "Processor 'remove' with tag 'remove_meta_fields' in pipeline '{{ _ingest.on_failure_pipeline }}' failed with message '{{ _ingest.on_failure_message }}'", + ], + }, }, - }, - ], - tag: 'remove_meta_fields', + ], + tag: 'remove_meta_fields', + }, }, - }, - ], - version: 1, - }); - return { - created: [indexName, `${indexName}@custom`, getInferencePipelineNameFromIndexName(indexName)], - }; + ], + version: 1, + }; + await esClient.ingest.putPipeline(ingestPipeline); + result = { ...result, [ingestPipeline.id]: ingestPipeline }; + return result; + } catch (error) { + // clean up pipelines if one failed to create + for (const id of Object.keys(result)) { + await esClient.ingest.deletePipeline({ id }); + } + throw error; + } }; /** diff --git a/x-pack/plugins/enterprise_search/server/lib/pipelines/delete_pipelines.ts b/x-pack/plugins/enterprise_search/server/lib/pipelines/delete_pipelines.ts new file mode 100644 index 0000000000000..3038f48004ea9 --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/lib/pipelines/delete_pipelines.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { IScopedClusterClient } from '@kbn/core/server'; + +import { getInferencePipelineNameFromIndexName } from '../../utils/ml_inference_pipeline_utils'; + +export const deleteIndexPipelines = async ( + client: IScopedClusterClient, + indexName: string +): Promise<{ deleted: string[] }> => { + const deleted: string[] = []; + const promises = [ + client.asCurrentUser.ingest + .deletePipeline({ id: indexName }) + .then(() => deleted.push(indexName)), + client.asCurrentUser.ingest + .deletePipeline({ + id: getInferencePipelineNameFromIndexName(indexName), + }) + .then(() => deleted.push(getInferencePipelineNameFromIndexName(indexName))), + client.asCurrentUser.ingest + .deletePipeline({ id: `${indexName}@custom` }) + .then(() => deleted.push(`${indexName}@custom`)), + ]; + await Promise.allSettled(promises); + return { + deleted, + }; +}; diff --git a/x-pack/plugins/enterprise_search/server/lib/pipelines/revert_custom_pipeline.ts b/x-pack/plugins/enterprise_search/server/lib/pipelines/revert_custom_pipeline.ts new file mode 100644 index 0000000000000..60a9e5cfcf97d --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/lib/pipelines/revert_custom_pipeline.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { IScopedClusterClient } from '@kbn/core/server'; + +import { CONNECTORS_INDEX } from '../..'; + +import { fetchConnectorByIndexName } from '../connectors/fetch_connectors'; + +import { deleteIndexPipelines } from './delete_pipelines'; + +import { getDefaultPipeline } from './get_default_pipeline'; + +export const revertCustomPipeline = async (client: IScopedClusterClient, indexName: string) => { + const connector = await fetchConnectorByIndexName(client, indexName); + if (connector) { + const pipeline = await getDefaultPipeline(client); + await client.asCurrentUser.update({ + doc: { pipeline }, + id: connector?.id, + index: CONNECTORS_INDEX, + }); + } + return await deleteIndexPipelines(client, indexName); +}; diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/crawler/crawler.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/crawler/crawler.ts index 08cea961709fc..8d0c1e73df848 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/crawler/crawler.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/crawler/crawler.ts @@ -16,6 +16,7 @@ import { addConnector } from '../../../lib/connectors/add_connector'; import { deleteConnectorById } from '../../../lib/connectors/delete_connector'; import { fetchConnectorByIndexName } from '../../../lib/connectors/fetch_connectors'; import { fetchCrawlerByIndexName } from '../../../lib/crawler/fetch_crawlers'; +import { recreateConnectorDocument } from '../../../lib/crawler/post_connector'; import { updateHtmlExtraction } from '../../../lib/crawler/put_html_extraction'; import { deleteIndex } from '../../../lib/indices/delete_index'; import { RouteDependencies } from '../../../plugin'; @@ -429,6 +430,37 @@ export function registerCrawlerRoutes(routeDependencies: RouteDependencies) { }) ); + router.post( + { + path: '/internal/enterprise_search/indices/{indexName}/crawler/connector', + validate: { + params: schema.object({ + indexName: schema.string(), + }), + }, + }, + elasticsearchErrorHandler(log, async (context, request, response) => { + const { client } = (await context.core).elasticsearch; + const connector = await fetchConnectorByIndexName(client, request.params.indexName); + if (connector) { + return createError({ + errorCode: ErrorCode.CONNECTOR_DOCUMENT_ALREADY_EXISTS, + message: i18n.translate( + 'xpack.enterpriseSearch.server.routes.recreateConnector.connectorExistsError', + { + defaultMessage: 'A connector for this index already exists', + } + ), + response, + statusCode: 409, + }); + } + + const connectorId = await recreateConnectorDocument(client, request.params.indexName); + return response.ok({ body: { connector_id: connectorId } }); + }) + ); + registerCrawlerCrawlRulesRoutes(routeDependencies); registerCrawlerEntryPointRoutes(routeDependencies); registerCrawlerSitemapRoutes(routeDependencies); diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/engines.test.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/engines.test.ts index 83e31f1ddf03d..019cca3a7acc2 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/engines.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/engines.test.ts @@ -7,6 +7,19 @@ import { mockDependencies, mockRequestHandler, MockRouter } from '../../__mocks__'; +jest.mock('../../utils/fetch_enterprise_search', () => ({ + ...jest.requireActual('../../utils/fetch_enterprise_search'), + fetchEnterpriseSearch: jest.fn(), +})); +jest.mock('../../lib/engines/field_capabilities', () => ({ + fetchEngineFieldCapabilities: jest.fn(), +})); + +import { RequestHandlerContext } from '@kbn/core/server'; + +import { fetchEngineFieldCapabilities } from '../../lib/engines/field_capabilities'; +import { fetchEnterpriseSearch } from '../../utils/fetch_enterprise_search'; + import { registerEnginesRoutes } from './engines'; describe('engines routes', () => { @@ -298,4 +311,118 @@ describe('engines routes', () => { mockRouter.shouldThrow(request); }); }); + + describe('GET /internal/enterprise_search/engines/{engine_name}/field_capabilities', () => { + let mockRouter: MockRouter; + const mockClient = { + asCurrentUser: {}, + }; + const mockCore = { + elasticsearch: { client: mockClient }, + savedObjects: { client: {} }, + }; + + beforeEach(() => { + jest.clearAllMocks(); + + const context = { + core: Promise.resolve(mockCore), + } as unknown as jest.Mocked; + + mockRouter = new MockRouter({ + context, + method: 'get', + path: '/internal/enterprise_search/engines/{engine_name}/field_capabilities', + }); + + registerEnginesRoutes({ + ...mockDependencies, + router: mockRouter.router, + }); + }); + + it('fetches engine fields', async () => { + const engineResult = { + created: '1999-12-31T23:59:59.999Z', + indices: [], + name: 'unit-test', + updated: '1999-12-31T23:59:59.999Z', + }; + const fieldCapabilitiesResult = { + name: 'unit-test', + }; + + (fetchEnterpriseSearch as jest.Mock).mockResolvedValueOnce(engineResult); + (fetchEngineFieldCapabilities as jest.Mock).mockResolvedValueOnce(fieldCapabilitiesResult); + + await mockRouter.callRoute({ + params: { engine_name: 'unit-test' }, + }); + + expect(fetchEnterpriseSearch).toHaveBeenCalledWith( + expect.anything(), + expect.anything(), + '/api/engines/unit-test' + ); + expect(fetchEngineFieldCapabilities).toHaveBeenCalledWith(mockClient, engineResult); + expect(mockRouter.response.ok).toHaveBeenCalledWith({ + body: fieldCapabilitiesResult, + headers: { 'content-type': 'application/json' }, + }); + }); + it('returns 404 when fetch engine is undefined', async () => { + (fetchEnterpriseSearch as jest.Mock).mockResolvedValueOnce(undefined); + await mockRouter.callRoute({ + params: { engine_name: 'unit-test' }, + }); + + expect(mockRouter.response.customError).toHaveBeenCalledWith({ + body: { + attributes: { + error_code: 'engine_not_found', + }, + message: 'Could not find engine', + }, + statusCode: 404, + }); + }); + it('returns 404 when fetch engine is returns 404', async () => { + (fetchEnterpriseSearch as jest.Mock).mockResolvedValueOnce({ + responseStatus: 404, + responseStatusText: 'NOT_FOUND', + }); + await mockRouter.callRoute({ + params: { engine_name: 'unit-test' }, + }); + + expect(mockRouter.response.customError).toHaveBeenCalledWith({ + body: { + attributes: { + error_code: 'engine_not_found', + }, + message: 'Could not find engine', + }, + statusCode: 404, + }); + }); + it('returns error when fetch engine returns an error', async () => { + (fetchEnterpriseSearch as jest.Mock).mockResolvedValueOnce({ + responseStatus: 500, + responseStatusText: 'INTERNAL_SERVER_ERROR', + }); + await mockRouter.callRoute({ + params: { engine_name: 'unit-test' }, + }); + + expect(mockRouter.response.customError).toHaveBeenCalledWith({ + body: { + attributes: { + error_code: 'uncaught_exception', + }, + message: 'Error fetching engine', + }, + statusCode: 500, + }); + }); + }); }); diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/engines.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/engines.ts index 71b0061a7d1ba..111c04e6cd42e 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/engines.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/engines.ts @@ -6,15 +6,22 @@ */ import { schema } from '@kbn/config-schema'; +import { EnterpriseSearchEngineDetails } from '../../../common/types/engines'; +import { ErrorCode } from '../../../common/types/error_codes'; import { createApiKey } from '../../lib/engines/create_api_key'; +import { fetchEngineFieldCapabilities } from '../../lib/engines/field_capabilities'; import { RouteDependencies } from '../../plugin'; + +import { createError } from '../../utils/create_error'; import { elasticsearchErrorHandler } from '../../utils/elasticsearch_error_handler'; +import { fetchEnterpriseSearch, isResponseError } from '../../utils/fetch_enterprise_search'; export function registerEnginesRoutes({ - router, + config, enterpriseSearchRequestHandler, log, + router, }: RouteDependencies) { router.get( { @@ -134,8 +141,37 @@ export function registerEnginesRoutes({ path: '/internal/enterprise_search/engines/{engine_name}/field_capabilities', validate: { params: schema.object({ engine_name: schema.string() }) }, }, - enterpriseSearchRequestHandler.createRequest({ - path: '/api/engines/:engine_name/field_capabilities', + elasticsearchErrorHandler(log, async (context, request, response) => { + const engineName = decodeURIComponent(request.params.engine_name); + const { client } = (await context.core).elasticsearch; + + const engine = await fetchEnterpriseSearch( + config, + request, + `/api/engines/${engineName}` + ); + if (!engine || (isResponseError(engine) && engine.responseStatus === 404)) { + return createError({ + errorCode: ErrorCode.ENGINE_NOT_FOUND, + message: 'Could not find engine', + response, + statusCode: 404, + }); + } + if (isResponseError(engine)) { + return createError({ + errorCode: ErrorCode.UNCAUGHT_EXCEPTION, + message: 'Error fetching engine', + response, + statusCode: engine.responseStatus, + }); + } + + const data = await fetchEngineFieldCapabilities(client, engine); + return response.ok({ + body: data, + headers: { 'content-type': 'application/json' }, + }); }) ); } diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts index 238a882142b7c..dc82b35c31f59 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts @@ -44,6 +44,7 @@ import { createIndexPipelineDefinitions } from '../../lib/pipelines/create_pipel import { getCustomPipelines } from '../../lib/pipelines/get_custom_pipelines'; import { getPipeline } from '../../lib/pipelines/get_pipeline'; import { getMlInferencePipelines } from '../../lib/pipelines/ml_inference/get_ml_inference_pipelines'; +import { revertCustomPipeline } from '../../lib/pipelines/revert_custom_pipeline'; import { RouteDependencies } from '../../plugin'; import { createError } from '../../utils/create_error'; import { elasticsearchErrorHandler } from '../../utils/elasticsearch_error_handler'; @@ -299,6 +300,27 @@ export function registerIndexRoutes({ }) ); + router.delete( + { + path: '/internal/enterprise_search/indices/{indexName}/pipelines', + validate: { + params: schema.object({ + indexName: schema.string(), + }), + }, + }, + elasticsearchErrorHandler(log, async (context, request, response) => { + const indexName = decodeURIComponent(request.params.indexName); + const { client } = (await context.core).elasticsearch; + const body = await revertCustomPipeline(client, indexName); + + return response.ok({ + body, + headers: { 'content-type': 'application/json' }, + }); + }) + ); + router.get( { path: '/internal/enterprise_search/indices/{indexName}/pipelines', diff --git a/x-pack/plugins/enterprise_search/server/utils/create_connector_document.test.ts b/x-pack/plugins/enterprise_search/server/utils/create_connector_document.test.ts new file mode 100644 index 0000000000000..62851572cf798 --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/utils/create_connector_document.test.ts @@ -0,0 +1,195 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ConnectorStatus } from '../../common/types/connectors'; + +import { createConnectorDocument } from './create_connector_document'; + +describe('createConnectorDocument', () => { + it('should create a connector document', () => { + expect( + createConnectorDocument({ + indexName: 'indexName', + isNative: false, + language: 'fr', + pipeline: { + extract_binary_content: true, + name: 'ent-search-generic-ingestion', + reduce_whitespace: true, + run_ml_inference: false, + }, + }) + ).toEqual({ + api_key_id: null, + configuration: {}, + custom_scheduling: {}, + description: null, + error: null, + features: null, + filtering: [ + { + active: { + advanced_snippet: { + created_at: expect.any(String), + updated_at: expect.any(String), + value: {}, + }, + rules: [ + { + created_at: expect.any(String), + field: '_', + id: 'DEFAULT', + order: 0, + policy: 'include', + rule: 'regex', + updated_at: expect.any(String), + value: '.*', + }, + ], + validation: { + errors: [], + state: 'valid', + }, + }, + domain: 'DEFAULT', + draft: { + advanced_snippet: { + created_at: expect.any(String), + updated_at: expect.any(String), + value: {}, + }, + rules: [ + { + created_at: expect.any(String), + field: '_', + id: 'DEFAULT', + order: 0, + policy: 'include', + rule: 'regex', + updated_at: expect.any(String), + value: '.*', + }, + ], + validation: { + errors: [], + state: 'valid', + }, + }, + }, + ], + index_name: 'indexName', + is_native: false, + language: 'fr', + last_seen: null, + last_sync_error: null, + last_sync_status: null, + last_synced: null, + name: 'indexName', + pipeline: { + extract_binary_content: true, + name: 'ent-search-generic-ingestion', + reduce_whitespace: true, + run_ml_inference: false, + }, + scheduling: { enabled: false, interval: '0 0 0 * * ?' }, + service_type: null, + status: ConnectorStatus.CREATED, + sync_now: false, + }); + }); + it('should remove search- from name', () => { + expect( + createConnectorDocument({ + indexName: 'search-indexName', + isNative: false, + language: 'fr', + pipeline: { + extract_binary_content: true, + name: 'ent-search-generic-ingestion', + reduce_whitespace: true, + run_ml_inference: false, + }, + }) + ).toEqual({ + api_key_id: null, + configuration: {}, + custom_scheduling: {}, + description: null, + error: null, + features: null, + filtering: [ + { + active: { + advanced_snippet: { + created_at: expect.any(String), + updated_at: expect.any(String), + value: {}, + }, + rules: [ + { + created_at: expect.any(String), + field: '_', + id: 'DEFAULT', + order: 0, + policy: 'include', + rule: 'regex', + updated_at: expect.any(String), + value: '.*', + }, + ], + validation: { + errors: [], + state: 'valid', + }, + }, + domain: 'DEFAULT', + draft: { + advanced_snippet: { + created_at: expect.any(String), + updated_at: expect.any(String), + value: {}, + }, + rules: [ + { + created_at: expect.any(String), + field: '_', + id: 'DEFAULT', + order: 0, + policy: 'include', + rule: 'regex', + updated_at: expect.any(String), + value: '.*', + }, + ], + validation: { + errors: [], + state: 'valid', + }, + }, + }, + ], + index_name: 'search-indexName', + is_native: false, + language: 'fr', + last_seen: null, + last_sync_error: null, + last_sync_status: null, + last_synced: null, + name: 'indexName', + pipeline: { + extract_binary_content: true, + name: 'ent-search-generic-ingestion', + reduce_whitespace: true, + run_ml_inference: false, + }, + scheduling: { enabled: false, interval: '0 0 0 * * ?' }, + service_type: null, + status: ConnectorStatus.CREATED, + sync_now: false, + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/server/utils/create_connector_document.ts b/x-pack/plugins/enterprise_search/server/utils/create_connector_document.ts new file mode 100644 index 0000000000000..d4776e3941cee --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/utils/create_connector_document.ts @@ -0,0 +1,103 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + ConnectorDocument, + ConnectorStatus, + FilteringPolicy, + FilteringRuleRule, + FilteringValidationState, + IngestPipelineParams, +} from '../../common/types/connectors'; + +export function createConnectorDocument({ + indexName, + isNative, + pipeline, + serviceType, + language, +}: { + indexName: string; + isNative: boolean; + language: string | null; + pipeline?: IngestPipelineParams | null; + serviceType?: string | null; +}): ConnectorDocument { + const currentTimestamp = new Date().toISOString(); + return { + api_key_id: null, + configuration: {}, + custom_scheduling: {}, + description: null, + error: null, + features: null, + filtering: [ + { + active: { + advanced_snippet: { + created_at: currentTimestamp, + updated_at: currentTimestamp, + value: {}, + }, + rules: [ + { + created_at: currentTimestamp, + field: '_', + id: 'DEFAULT', + order: 0, + policy: FilteringPolicy.INCLUDE, + rule: FilteringRuleRule.REGEX, + updated_at: currentTimestamp, + value: '.*', + }, + ], + validation: { + errors: [], + state: FilteringValidationState.VALID, + }, + }, + domain: 'DEFAULT', + draft: { + advanced_snippet: { + created_at: currentTimestamp, + updated_at: currentTimestamp, + value: {}, + }, + rules: [ + { + created_at: currentTimestamp, + field: '_', + id: 'DEFAULT', + order: 0, + policy: FilteringPolicy.INCLUDE, + rule: FilteringRuleRule.REGEX, + updated_at: currentTimestamp, + value: '.*', + }, + ], + validation: { + errors: [], + state: FilteringValidationState.VALID, + }, + }, + }, + ], + index_name: indexName, + is_native: isNative, + language, + last_seen: null, + last_sync_error: null, + last_sync_status: null, + last_synced: null, + name: indexName.startsWith('search-') ? indexName.substring(7) : indexName, + pipeline, + scheduling: { enabled: false, interval: '0 0 0 * * ?' }, + service_type: serviceType || null, + status: ConnectorStatus.CREATED, + sync_now: false, + }; +} diff --git a/x-pack/plugins/enterprise_search/server/utils/fetch_enterprise_search.test.ts b/x-pack/plugins/enterprise_search/server/utils/fetch_enterprise_search.test.ts new file mode 100644 index 0000000000000..20fe7b57350ee --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/utils/fetch_enterprise_search.test.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 '../__mocks__/http_agent.mock'; + +jest.mock('node-fetch'); +import fetch from 'node-fetch'; + +import { KibanaRequest } from '@kbn/core/server'; + +import { ConfigType } from '..'; + +import { fetchEnterpriseSearch, isResponseError } from './fetch_enterprise_search'; + +describe('fetchEnterpriseSearch', () => { + const mockConfig = { + accessCheckTimeout: 200, + accessCheckTimeoutWarning: 100, + host: 'http://localhost:3002', + }; + const mockRequest = { + headers: { authorization: '==someAuth' }, + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('returns json fetch response', async () => { + const response = { foo: 'bar' }; + (fetch as unknown as jest.Mock).mockResolvedValueOnce({ + json: jest.fn().mockResolvedValueOnce(response), + ok: true, + }); + await expect( + fetchEnterpriseSearch(mockConfig as ConfigType, mockRequest as KibanaRequest, '/api/v1/test') + ).resolves.toBe(response); + }); + it('calls expected endpoint', async () => { + (fetch as unknown as jest.Mock).mockResolvedValueOnce({ + json: jest.fn().mockResolvedValueOnce({}), + ok: true, + }); + await fetchEnterpriseSearch( + mockConfig as ConfigType, + mockRequest as KibanaRequest, + '/api/v1/test' + ); + + expect(fetch).toHaveBeenCalledTimes(1); + expect(fetch).toHaveBeenCalledWith('http://localhost:3002/api/v1/test', expect.anything()); + }); + it('uses request auth header & config custom headers', async () => { + (fetch as unknown as jest.Mock).mockResolvedValueOnce({ + json: jest.fn().mockResolvedValueOnce({}), + ok: true, + }); + const config = { + ...mockConfig, + customHeaders: { + foo: 'bar', + }, + }; + await fetchEnterpriseSearch( + config as unknown as ConfigType, + mockRequest as KibanaRequest, + '/api/v1/test' + ); + + expect(fetch).toHaveBeenCalledTimes(1); + expect(fetch).toHaveBeenCalledWith(expect.anything(), { + agent: expect.anything(), + headers: { + Authorization: mockRequest.headers.authorization, + foo: 'bar', + }, + }); + }); + it('returns undefined when config.host is unavailable', async () => { + await expect( + fetchEnterpriseSearch( + { host: '' } as ConfigType, + mockRequest as KibanaRequest, + '/api/v1/test' + ) + ).resolves.toBeUndefined(); + }); +}); + +describe('isResponseError', () => { + it('returns true for ResponseError object', () => { + expect(isResponseError({ responseStatus: 404, responseStatusText: 'NOT_FOUND' })).toBe(true); + }); + it('returns false for null/undefined', () => { + expect(isResponseError(null)).toBe(false); + expect(isResponseError(undefined)).toBe(false); + }); + it('returns false for object without expected keys', () => { + expect(isResponseError({})).toBe(false); + expect(isResponseError({ responseStatusText: 'NOT_FOUND' })).toBe(false); + expect(isResponseError({ responseStatus: 404 })).toBe(false); + expect(isResponseError([])).toBe(false); + }); + it('returns false for non-object', () => { + expect(isResponseError(100)).toBe(false); + expect(isResponseError('test')).toBe(false); + }); +}); diff --git a/x-pack/plugins/enterprise_search/server/utils/fetch_enterprise_search.ts b/x-pack/plugins/enterprise_search/server/utils/fetch_enterprise_search.ts new file mode 100644 index 0000000000000..b1a2146a86ab9 --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/utils/fetch_enterprise_search.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 fetch, { RequestInit } from 'node-fetch'; + +import { KibanaRequest } from '@kbn/core/server'; + +import { ConfigType } from '..'; + +import { entSearchHttpAgent } from '../lib/enterprise_search_http_agent'; + +export interface ResponseError { + responseStatus: number; + responseStatusText: string; +} + +export function isResponseError(resp: unknown): resp is ResponseError { + if (typeof resp !== 'object') return false; + if (resp === null) return false; + if ('responseStatus' in resp && 'responseStatusText' in resp) return true; + return false; +} + +export async function fetchEnterpriseSearch( + config: ConfigType, + request: KibanaRequest, + endpoint: string +): Promise { + if (!config.host) return undefined; + + const enterpriseSearchUrl = encodeURI(`${config.host}${endpoint}`); + const options: RequestInit = { + agent: entSearchHttpAgent.getHttpAgent(), + headers: { + Authorization: request.headers.authorization as string, + ...config.customHeaders, + }, + }; + + const response = await fetch(enterpriseSearchUrl, options); + + if (!response.ok) { + return { + responseStatus: response.status, + responseStatusText: response.statusText, + }; + } + + return await response.json(); +} diff --git a/x-pack/plugins/fleet/README.md b/x-pack/plugins/fleet/README.md index 9d9c6c9720de4..2881076c7f156 100644 --- a/x-pack/plugins/fleet/README.md +++ b/x-pack/plugins/fleet/README.md @@ -13,7 +13,7 @@ ## Fleet Requirements -Fleet needs to have Elasticsearch API keys enabled, and also to have TLS enabled on kibana, (if you want to run Kibana without TLS you can provide the following config flag `--xpack.fleet.agents.tlsCheckDisabled=false`) +Fleet needs to have Elasticsearch API keys enabled. Also you need to configure the hosts your agent is going to use to comunication with Elasticsearch and Kibana (Not needed if you use Elastic cloud). You can use the following flags: @@ -26,27 +26,53 @@ Also you need to configure the hosts your agent is going to use to comunication ### Getting started -See the Kibana docs for [how to set up your dev environment](https://github.com/elastic/kibana/blob/main/CONTRIBUTING.md#setting-up-your-development-environment), [run Elasticsearch](https://github.com/elastic/kibana/blob/main/CONTRIBUTING.md#running-elasticsearch), and [start Kibana](https://github.com/elastic/kibana/blob/main/CONTRIBUTING.md#running-kibana) +See the [Contributing to Kibana documentation](https://github.com/elastic/kibana/blob/main/CONTRIBUTING.md) or head straight to the [Kibana Developer Guide](https://docs.elastic.dev/kibana-dev-docs/getting-started/welcome) for setting up your dev environment, run Elasticsearch and start Kibana. -One common development workflow is: +This plugin follows the `common`, `server`, `public` structure described in the [Kibana Developer Guide](https://docs.elastic.dev/kibana-dev-docs/key-concepts/platform-intro). Refer to [The anatomy of a plugin](https://docs.elastic.dev/kibana-dev-docs/key-concepts/anatomy-of-a-plugin) in the guide for further details. -- Bootstrap Kibana - ``` - yarn kbn bootstrap +We follow the pattern of developing feature branches under your personal fork of Kibana. Refer to [Set up a Development Environment](https://docs.elastic.dev/kibana-dev-docs/getting-started/setup-dev-env) in the guide for further details. Other best practices including developer principles, standards and style guide can be found under the Contributing section of the guide. + +Note: The plugin was previously named Ingest Manager, it's possible that some variables are still named with that old plugin name. + +#### Dev environment setup + +These are some additional recommendations to the steps detailed in the [Kibana Developer Guide](https://docs.elastic.dev/kibana-dev-docs/getting-started/setup-dev-env). + +1. Create a `config/kibana.dev.yml` file by copying the existing `config/kibana.yml` file. +2. It is recommended to explicitly set a base path for Kibana (refer to [Considerations for basepath](https://www.elastic.co/guide/en/kibana/current/development-basepath.html) for details). To do this, add the following to your `kibana.dev.yml`: + ```yml + server.basePath: / ``` -- Start Elasticsearch in one shell + where `yourPath` is a path of your choice (e.g. your name). +3. Bootstrap Kibana: + ``` + yarn kbn bootstrap + ``` + +#### Running Elasticsearch and Kibana +- Start Elasticsearch in one shell (NB: you might want to add other flags to enable data persistency and/or running Fleet Server locally, see below): ``` yarn es snapshot -E xpack.security.authc.api_key.enabled=true -E xpack.security.authc.token.enabled=true ``` -- Start Kibana in another shell +- Start Kibana in another shell: ``` - yarn start --no-base-path + yarn start ``` + If you don't have a base path set up, add `--no-base-path` to `yarn start`. + +#### Useful tips + +If Kibana fails to start, it is possible that your local setup got corrupted. An easy fix is to run: +``` +yarn kbn clean && yarn kbn bootstrap +``` -This plugin follows the `common`, `server`, `public` structure from the [Architecture Style Guide -](https://github.com/elastic/kibana/blob/main/style_guides/architecture_style_guide.md#file-and-folder-structure). We also follow the pattern of developing feature branches under your personal fork of Kibana. +To avoid losing all your data when you restart Elasticsearch, you can provide a path to store the data when running the `yarn es snapshot ` command, e.g.: +``` +-E path.data=/tmp/es-data +``` -Note: The plugin was previously named Ingest Manager it's possible that some variables are still named with that old plugin name. +Refer to the [Running Elasticsearch during development](https://www.elastic.co/guide/en/kibana/current/running-elasticsearch.html) page of the guide for other options. ### Running Fleet Server Locally in a Container @@ -99,7 +125,7 @@ docker run -e KIBANA_HOST=http://{YOUR-IP}:5601/{BASE-PATH} -e KIBANA_USERNAME=e Ensure you provide the `-p 8220:8220` port mapping to map the Fleet Server container's port `8220` to your local machine's port `8220` in order for Fleet to communicate with Fleet Server. -For the latest version, use `8.0.0-SNAPSHOT`. Otherwise, you can explore the available versions at https://www.docker.elastic.co/r/beats/elastic-agent. +Explore the available versions at https://www.docker.elastic.co/r/beats/elastic-agent. Only released versions are shown by default: tick the `Include snapshots` checkbox to see the latest version, e.g. `8.8.0-SNAPSHOT`. Once the Fleet Server container is running, you should be able to treat it as if it were a local process running on `https://localhost:8220` when configuring Fleet via the UI. You can then run `elastic-agent` on your local machine directly for testing purposes, or with Docker (recommended) see next section. @@ -115,6 +141,18 @@ Once the Fleet Server container is running, you should be able to treat it as if ### Tests +#### Unit tests + +Kibana primarily uses Jest for unit testing. Each plugin or package defines a `jest.config.js` that extends a preset provided by the `@kbn/test` package. Unless you intend to run all unit tests within the project, you should provide the Jest configuration for Fleet. The following command runs all Fleet unit tests: +``` +yarn jest --config x-pack/plugins/fleet/jest.config.js +``` + +You can also run a specific test by passing the filepath as an argument, e.g.: +``` +yarn jest --config x-pack/plugins/fleet/jest.config.js x-pack/plugins/fleet/common/services/validate_package_policy.test.ts +``` + #### API integration tests You need to have `docker` to run ingest manager api integration tests diff --git a/x-pack/plugins/fleet/common/openapi/bundled.json b/x-pack/plugins/fleet/common/openapi/bundled.json index acb1360d39f83..4ff0b522325ca 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.json +++ b/x-pack/plugins/fleet/common/openapi/bundled.json @@ -20,10 +20,67 @@ } ], "paths": { + "/health_check": { + "post": { + "summary": "Fleet Server health check", + "tags": [ + "Fleet internals" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "status": { + "type": "string" + }, + "host": { + "type": "string" + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/error" + } + }, + "operationId": "fleet-server-health-check", + "parameters": [ + { + "$ref": "#/components/parameters/kbn_xsrf" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "host": { + "type": "string" + } + } + } + } + } + } + } + }, "/setup": { "post": { - "summary": "Setup", - "tags": [], + "summary": "Initiate Fleet setup", + "tags": [ + "Fleet internals" + ], "responses": { "200": { "description": "OK", @@ -64,8 +121,10 @@ }, "/settings": { "get": { - "summary": "Settings", - "tags": [], + "summary": "Get settings", + "tags": [ + "Fleet internals" + ], "responses": { "200": { "description": "OK", @@ -84,8 +143,10 @@ "operationId": "get-settings" }, "put": { - "summary": "Settings - Update", - "tags": [], + "summary": "Update settings", + "tags": [ + "Fleet internals" + ], "requestBody": { "content": { "application/json": { @@ -128,63 +189,12 @@ "operationId": "update-settings" } }, - "/health_check": { - "post": { - "summary": "Fleet Server Health Check", - "tags": [], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "status": { - "type": "string" - }, - "host": { - "type": "string" - } - } - } - } - } - }, - "400": { - "$ref": "#/components/responses/error" - } - }, - "operationId": "fleet-server-health-check", - "parameters": [ - { - "$ref": "#/components/parameters/kbn_xsrf" - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "host": { - "type": "string" - } - } - } - } - } - } - } - }, "/service-tokens": { "post": { - "summary": "Generate service tokens", - "tags": [], + "summary": "Create service token", + "tags": [ + "Service tokens" + ], "responses": { "200": { "description": "OK", @@ -219,8 +229,10 @@ }, "/service_tokens": { "post": { - "summary": "Generate service tokens", - "tags": [], + "summary": "Create service token", + "tags": [ + "Service tokens" + ], "responses": { "200": { "description": "OK", @@ -255,7 +267,9 @@ "/epm/verification_key_id": { "get": { "summary": "Get package signature verification key ID", - "tags": [], + "tags": [ + "Elastic Package Manager (EPM)" + ], "responses": { "200": { "description": "OK", @@ -291,37 +305,14 @@ }, "operationId": "packages-get-verification-key-id" }, - "parameters": [ - { - "schema": { - "type": "string" - }, - "name": "pkgName", - "in": "path", - "required": true - }, - { - "schema": { - "type": "string" - }, - "name": "pkgVersion", - "in": "path", - "required": true - }, - { - "schema": { - "type": "string" - }, - "name": "filePath", - "in": "path", - "required": true - } - ] + "parameters": [] }, "/epm/categories": { "get": { - "summary": "Package categories", - "tags": [], + "summary": "List package categories", + "tags": [ + "Elastic Package Manager (EPM)" + ], "responses": { "200": { "description": "OK", @@ -347,7 +338,7 @@ "type": "boolean", "default": false }, - "description": "Whether to include prerelease packages in categories count (e.g. beta, rc, preview) " + "description": "Whether to include prerelease packages in categories count (e.g. beta, rc, preview)" }, { "in": "query", @@ -370,8 +361,10 @@ }, "/epm/packages/limited": { "get": { - "summary": "Packages - Get limited list", - "tags": [], + "summary": "Get limited package list", + "tags": [ + "Elastic Package Manager (EPM)" + ], "responses": { "200": { "description": "OK", @@ -401,8 +394,10 @@ }, "/epm/packages": { "get": { - "summary": "Packages - List", - "tags": [], + "summary": "List packages", + "tags": [ + "Elastic Package Manager (EPM)" + ], "responses": { "200": { "description": "OK", @@ -436,7 +431,7 @@ "type": "boolean", "default": false }, - "description": "Whether to return prerelease versions of packages (e.g. beta, rc, preview) " + "description": "Whether to return prerelease versions of packages (e.g. beta, rc, preview)" }, { "in": "query", @@ -457,8 +452,10 @@ ] }, "post": { - "summary": "Packages - Install by upload", - "tags": [], + "summary": "Install by package by direct upload", + "tags": [ + "Elastic Package Manager (EPM)" + ], "responses": { "200": { "description": "OK", @@ -544,8 +541,10 @@ }, "/epm/packages/_bulk": { "post": { - "summary": "Packages - Bulk install", - "tags": [], + "summary": "Bulk install packages", + "tags": [ + "Elastic Package Manager (EPM)" + ], "responses": { "200": { "description": "OK", @@ -570,7 +569,7 @@ "type": "boolean", "default": false }, - "description": "Whether to return prerelease versions of packages (e.g. beta, rc, preview) " + "description": "Whether to return prerelease versions of packages (e.g. beta, rc, preview)" } ], "requestBody": { @@ -602,8 +601,10 @@ }, "/epm/packages/{pkgkey}": { "get": { - "summary": "Packages - Info", - "tags": [], + "summary": "Get package", + "tags": [ + "Elastic Package Manager (EPM)" + ], "responses": { "200": { "description": "OK", @@ -670,14 +671,16 @@ "type": "boolean", "default": false }, - "description": "Whether to return prerelease versions of packages (e.g. beta, rc, preview) " + "description": "Whether to return prerelease versions of packages (e.g. beta, rc, preview)" } ], "deprecated": true }, "post": { - "summary": "Packages - Install", - "tags": [], + "summary": "Install package", + "tags": [ + "Elastic Package Manager (EPM)" + ], "responses": { "200": { "description": "OK", @@ -755,8 +758,10 @@ "deprecated": true }, "delete": { - "summary": "Packages - Delete", - "tags": [], + "summary": "Delete ackage", + "tags": [ + "Elastic Package Manager (EPM)" + ], "responses": { "200": { "description": "OK", @@ -835,8 +840,10 @@ }, "/epm/packages/{pkgName}/{pkgVersion}": { "get": { - "summary": "Packages - Info", - "tags": [], + "summary": "Get package", + "tags": [ + "Elastic Package Manager (EPM)" + ], "responses": { "200": { "description": "OK", @@ -940,12 +947,14 @@ "type": "boolean", "default": false }, - "description": "Whether to return prerelease versions of packages (e.g. beta, rc, preview) " + "description": "Whether to return prerelease versions of packages (e.g. beta, rc, preview)" } ], "post": { - "summary": "Packages - Install", - "tags": [], + "summary": "Install package", + "tags": [ + "Elastic Package Manager (EPM)" + ], "responses": { "200": { "description": "OK", @@ -1030,8 +1039,10 @@ } }, "put": { - "summary": "Packages - Update", - "tags": [], + "summary": "Update package settings", + "tags": [ + "Elastic Package Manager (EPM)" + ], "responses": { "200": { "description": "OK", @@ -1095,8 +1106,10 @@ } }, "delete": { - "summary": "Packages - Delete", - "tags": [], + "summary": "Delete package", + "tags": [ + "Elastic Package Manager (EPM)" + ], "responses": { "200": { "description": "OK", @@ -1166,8 +1179,10 @@ }, "/epm/packages/{pkgName}/{pkgVersion}/{filePath}": { "get": { - "summary": "Packages - Get file from registry", - "tags": [], + "summary": "Get package file", + "tags": [ + "Elastic Package Manager (EPM)" + ], "responses": { "200": { "description": "OK", @@ -1225,8 +1240,10 @@ }, "/epm/packages/{pkgName}/stats": { "get": { - "summary": "Get stats for a package", - "tags": [], + "summary": "Get package stats", + "tags": [ + "Elastic Package Manager (EPM)" + ], "responses": { "200": { "description": "OK", @@ -1270,8 +1287,10 @@ }, "/agents/setup": { "get": { - "summary": "Agents setup - Info", - "tags": [], + "summary": "Get agent setup info", + "tags": [ + "Agents" + ], "responses": { "200": { "description": "OK", @@ -1295,7 +1314,10 @@ ] }, "post": { - "summary": "Agents setup - Create", + "summary": "Initiate agent setup", + "tags": [ + "Agents" + ], "operationId": "setup-agents", "responses": { "200": { @@ -1342,8 +1364,10 @@ }, "/agent-status": { "get": { - "summary": "Agents - Summary stats", - "tags": [], + "summary": "Get agent status summary", + "tags": [ + "Agent status" + ], "responses": { "200": { "description": "OK", @@ -1411,8 +1435,10 @@ }, "/agent_status": { "get": { - "summary": "Agents - Summary stats", - "tags": [], + "summary": "Get agent status summary", + "tags": [ + "Agent status" + ], "responses": { "200": { "description": "OK", @@ -1499,8 +1525,10 @@ }, "/agent_status/data": { "get": { - "summary": "Agents - Get incoming data", - "tags": [], + "summary": "Get incoming agent data", + "tags": [ + "Agent status" + ], "responses": { "200": { "description": "OK", @@ -1550,8 +1578,10 @@ }, "/agents": { "get": { - "summary": "Agents - List", - "tags": [], + "summary": "List agents", + "tags": [ + "Agents" + ], "responses": { "200": { "description": "OK", @@ -1611,8 +1641,10 @@ }, "/agents/bulk_upgrade": { "post": { - "summary": "Agents - Bulk Upgrade", - "tags": [], + "summary": "Bulk upgrade agents", + "tags": [ + "Agents" + ], "responses": { "200": { "description": "OK", @@ -1663,7 +1695,10 @@ }, "/agents/action_status": { "get": { - "summary": "Agents - Action status", + "summary": "Get agent action status", + "tags": [ + "Agent actions" + ], "parameters": [ { "$ref": "#/components/parameters/page_size" @@ -1774,8 +1809,10 @@ } ], "get": { - "summary": "Agent - Info", - "tags": [], + "summary": "Get agent by ID", + "tags": [ + "Agents" + ], "responses": { "200": { "description": "OK", @@ -1807,8 +1844,10 @@ ] }, "put": { - "summary": "Agent - Update", - "tags": [], + "summary": "Update agent by ID", + "tags": [ + "Agents" + ], "responses": { "200": { "description": "OK", @@ -1861,8 +1900,10 @@ } }, "delete": { - "summary": "Agent - Delete", - "tags": [], + "summary": "Delete agent by ID", + "tags": [ + "Agents" + ], "responses": { "200": { "description": "OK", @@ -1909,8 +1950,10 @@ } ], "post": { - "summary": "Agent - Actions", - "tags": [], + "summary": "Create agent action", + "tags": [ + "Agent actions" + ], "responses": { "200": { "description": "OK", @@ -1983,8 +2026,10 @@ } ], "post": { - "summary": "Agent - Cancel Action", - "tags": [], + "summary": "Cancel agent action", + "tags": [ + "Agent actions" + ], "responses": { "200": { "description": "OK", @@ -2033,8 +2078,10 @@ } ], "get": { - "summary": "Get agent upload file", - "tags": [], + "summary": "Get file uploaded by agent", + "tags": [ + "Agents" + ], "responses": { "200": { "description": "OK", @@ -2078,9 +2125,11 @@ "required": true } ], - "put": { - "summary": "Agent - Reassign", - "tags": [], + "post": { + "summary": "Reassign agent", + "tags": [ + "Agents" + ], "responses": { "200": { "description": "OK", @@ -2120,6 +2169,52 @@ } } } + }, + "put": { + "summary": "Reassign agent", + "tags": [ + "Agents" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "400": { + "$ref": "#/components/responses/error" + } + }, + "operationId": "reassign-agent-deprecated", + "parameters": [ + { + "$ref": "#/components/parameters/kbn_xsrf" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "policy_id": { + "type": "string" + } + }, + "required": [ + "policy_id" + ] + } + } + } + }, + "deprecated": true } }, "/agents/{agentId}/unenroll": { @@ -2134,8 +2229,10 @@ } ], "post": { - "summary": "Agent - Unenroll", - "tags": [], + "summary": "Unenroll agent", + "tags": [ + "Agents" + ], "responses": { "200": { "description": "OK", @@ -2209,8 +2306,10 @@ } ], "post": { - "summary": "Agent - Upgrade", - "tags": [], + "summary": "Upgrade agent", + "tags": [ + "Agents" + ], "responses": { "200": { "description": "OK", @@ -2257,7 +2356,9 @@ ], "get": { "summary": "List agent uploads", - "tags": [], + "tags": [ + "Agents" + ], "responses": { "200": { "description": "OK", @@ -2291,8 +2392,10 @@ }, "/agents/bulk_reassign": { "post": { - "summary": "Agents - Bulk reassign", - "tags": [], + "summary": "Bulk reassign agents", + "tags": [ + "Agents" + ], "responses": { "200": { "description": "OK", @@ -2361,8 +2464,10 @@ }, "/agents/bulk_unenroll": { "post": { - "summary": "Agents - Bulk unenroll", - "tags": [], + "summary": "Bulk unenroll agents", + "tags": [ + "Agents" + ], "responses": { "200": { "description": "OK", @@ -2438,8 +2543,10 @@ }, "/agents/bulk_update_agent_tags": { "post": { - "summary": "Agents - Bulk update tags", - "tags": [], + "summary": "Bulk update agent tags", + "tags": [ + "Agents" + ], "responses": { "200": { "description": "OK", @@ -2526,8 +2633,10 @@ }, "/agents/tags": { "get": { - "summary": "Agent Tags - List", - "description": "List all agent tags", + "summary": "List agent tags", + "tags": [ + "Agents" + ], "responses": { "200": { "description": "OK", @@ -2558,8 +2667,10 @@ } ], "post": { - "summary": "Agent - Request Diagnostics", - "tags": [], + "summary": "Request agent diagnostics", + "tags": [ + "Agents" + ], "responses": { "200": { "description": "OK", @@ -2590,8 +2701,10 @@ }, "/agents/bulk_request_diagnostics": { "post": { - "summary": "Agent - Bulk Request Diagnostics", - "tags": [], + "summary": "Bulk request diagnostics from agents", + "tags": [ + "Agents" + ], "responses": { "200": { "description": "OK", @@ -2657,8 +2770,10 @@ }, "/agent_policies": { "get": { - "summary": "Agent policies - List", - "tags": [], + "summary": "List agent policies", + "tags": [ + "Agent policies" + ], "responses": { "200": { "description": "OK", @@ -2728,8 +2843,10 @@ "description": "" }, "post": { - "summary": "Agent policy - Create", - "tags": [], + "summary": "Create agent policy", + "tags": [ + "Agent policies" + ], "responses": { "200": { "description": "OK", @@ -2780,8 +2897,10 @@ } ], "get": { - "summary": "Agent policy - Info", - "tags": [], + "summary": "Get agent policy by ID", + "tags": [ + "Agent policies" + ], "responses": { "200": { "description": "OK", @@ -2810,8 +2929,10 @@ "parameters": [] }, "put": { - "summary": "Agent policy - Update", - "tags": [], + "summary": "Update agent policy by ID", + "tags": [ + "Agent policies" + ], "responses": { "200": { "description": "OK", @@ -2864,7 +2985,10 @@ } ], "post": { - "summary": "Agent policy - copy one policy", + "summary": "Copy agent policy by ID", + "tags": [ + "Agent policies" + ], "operationId": "agent-policy-copy", "parameters": [ { @@ -2919,7 +3043,10 @@ }, "/agent_policies/{agentPolicyId}/full": { "get": { - "summary": "Agent policy - Get full policy", + "summary": "Get full agent policy by ID", + "tags": [ + "Agent policies" + ], "operationId": "agent-policy-full", "responses": { "200": { @@ -2986,7 +3113,10 @@ }, "/agent_policies/{agentPolicyId}/download": { "get": { - "summary": "Agent policy - Download", + "summary": "Download agent policy by ID", + "tags": [ + "Agent policies" + ], "operationId": "agent-policy-download", "responses": { "200": { @@ -3046,8 +3176,10 @@ }, "/agent_policies/_bulk_get": { "post": { - "summary": "Agent policies - Bulk Get", - "tags": [], + "summary": "Bulk get agent policies", + "tags": [ + "Agent policies" + ], "requestBody": { "content": { "application/json": { @@ -3109,7 +3241,10 @@ }, "/agent_policies/delete": { "post": { - "summary": "Agent policy - Delete", + "summary": "Delete agent policy by ID", + "tags": [ + "Agent policies" + ], "operationId": "delete-agent-policy", "responses": { "200": { @@ -3165,8 +3300,10 @@ }, "/data_streams": { "get": { - "summary": "Data streams - List", - "tags": [], + "summary": "List data streams", + "tags": [ + "Data streams" + ], "responses": { "200": { "description": "OK", @@ -3196,8 +3333,10 @@ }, "/enrollment-api-keys": { "get": { - "summary": "Enrollment API Keys - List", - "tags": [], + "summary": "List enrollment API keys", + "tags": [ + "Enrollment API keys" + ], "responses": { "200": { "description": "OK", @@ -3248,8 +3387,10 @@ "deprecated": true }, "post": { - "summary": "Enrollment API Key - Create", - "tags": [], + "summary": "Create enrollment API key", + "tags": [ + "Enrollment API keys" + ], "responses": { "200": { "description": "OK", @@ -3297,8 +3438,10 @@ } ], "get": { - "summary": "Enrollment API Key - Info", - "tags": [], + "summary": "Get enrollment API key by ID", + "tags": [ + "Enrollment API keys" + ], "responses": { "200": { "description": "OK", @@ -3326,8 +3469,10 @@ "deprecated": true }, "delete": { - "summary": "Enrollment API Key - Delete", - "tags": [], + "summary": "Delete enrollment API key by ID", + "tags": [ + "Enrollment API keys" + ], "responses": { "200": { "description": "OK", @@ -3365,8 +3510,10 @@ }, "/enrollment_api_keys": { "get": { - "summary": "Enrollment API Keys - List", - "tags": [], + "summary": "List enrollment API keys", + "tags": [ + "Enrollment API keys" + ], "responses": { "200": { "description": "OK", @@ -3416,8 +3563,10 @@ "parameters": [] }, "post": { - "summary": "Enrollment API Key - Create", - "tags": [], + "summary": "Create enrollment API key", + "tags": [ + "Enrollment API keys" + ], "responses": { "200": { "description": "OK", @@ -3464,8 +3613,10 @@ } ], "get": { - "summary": "Enrollment API Key - Info", - "tags": [], + "summary": "Get enrollment API key by ID", + "tags": [ + "Enrollment API keys" + ], "responses": { "200": { "description": "OK", @@ -3492,8 +3643,10 @@ "operationId": "get-enrollment-api-key" }, "delete": { - "summary": "Enrollment API Key - Delete", - "tags": [], + "summary": "Delete enrollment API key by ID", + "tags": [ + "Enrollment API keys" + ], "responses": { "200": { "description": "OK", @@ -3530,8 +3683,10 @@ }, "/package_policies": { "get": { - "summary": "Package policies - List", - "tags": [], + "summary": "List package policies", + "tags": [ + "Package policies" + ], "responses": { "200": { "description": "OK", @@ -3573,7 +3728,10 @@ }, "parameters": [], "post": { - "summary": "Package policy - Create", + "summary": "Create package policy", + "tags": [ + "Package policies" + ], "operationId": "create-package-policy", "responses": { "200": { @@ -3617,8 +3775,10 @@ }, "/package_policies/_bulk_get": { "post": { - "summary": "Package policies - Bulk Get", - "tags": [], + "summary": "Bulk get package policies", + "tags": [ + "Package policies" + ], "requestBody": { "content": { "application/json": { @@ -3676,7 +3836,10 @@ }, "/package_policies/delete": { "post": { - "summary": "Package policy - Delete", + "summary": "Delete package policy", + "tags": [ + "Package policies" + ], "operationId": "post-delete-package-policy", "requestBody": { "content": { @@ -3743,7 +3906,10 @@ }, "/package_policies/upgrade": { "post": { - "summary": "Package policy - Upgrade", + "summary": "Upgrade package policy to a newer package version", + "tags": [ + "Package policies" + ], "operationId": "upgrade-package-policy", "requestBody": { "content": { @@ -3802,7 +3968,10 @@ }, "/package_policies/upgrade/dryrun": { "post": { - "summary": "Package policy - Upgrade Dry run", + "summary": "Dry run package policy upgrade", + "tags": [ + "Package policies" + ], "operationId": "upgrade-package-policy-dry-run", "requestBody": { "content": { @@ -3863,8 +4032,10 @@ }, "/package_policies/{packagePolicyId}": { "get": { - "summary": "Package policy - Info", - "tags": [], + "summary": "Get package policy by ID", + "tags": [ + "Package policies" + ], "responses": { "200": { "description": "OK", @@ -3901,7 +4072,10 @@ } ], "put": { - "summary": "Package policy - Update", + "summary": "Update package policy by ID", + "tags": [ + "Package policies" + ], "operationId": "update-package-policy", "requestBody": { "content": { @@ -3946,8 +4120,10 @@ ] }, "delete": { - "summary": "Package policy - Delete", - "tags": [], + "summary": "Delete package policy by ID", + "tags": [ + "Package policies" + ], "operationId": "delete-package-policy", "responses": { "200": { @@ -3985,8 +4161,10 @@ }, "/outputs": { "get": { - "summary": "Outputs", - "tags": [], + "summary": "List outputs", + "tags": [ + "Outputs" + ], "responses": { "200": { "description": "OK", @@ -4022,9 +4200,10 @@ "operationId": "get-outputs" }, "post": { - "summary": "Outputs", - "description": "Create a new output", - "tags": [], + "summary": "Create output", + "tags": [ + "Outputs" + ], "responses": { "200": { "description": "OK", @@ -4095,8 +4274,10 @@ }, "/outputs/{outputId}": { "get": { - "summary": "Output - Info", - "tags": [], + "summary": "Get output by ID", + "tags": [ + "Outputs" + ], "responses": { "200": { "description": "OK", @@ -4133,7 +4314,10 @@ } ], "delete": { - "summary": "Output - Delete", + "summary": "Delete output by ID", + "tags": [ + "Outputs" + ], "operationId": "delete-output", "responses": { "200": { @@ -4165,7 +4349,10 @@ ] }, "put": { - "summary": "Output - Update", + "summary": "Update output by ID", + "tags": [ + "Outputs" + ], "operationId": "update-output", "requestBody": { "content": { @@ -4242,10 +4429,46 @@ ] } }, + "/logstash_api_keys": { + "post": { + "summary": "Generate Logstash API key", + "tags": [ + "Outputs" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "api_key": { + "type": "string" + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/error" + } + }, + "operationId": "generate-logstash-api-key", + "parameters": [ + { + "$ref": "#/components/parameters/kbn_xsrf" + } + ] + } + }, "/agent_download_sources": { "get": { - "summary": "Agent Download Sources", - "tags": [], + "summary": "List agent binary download sources", + "tags": [ + "Agent binary download sources" + ], "responses": { "200": { "description": "OK", @@ -4281,9 +4504,10 @@ "operationId": "get-download-sources" }, "post": { - "summary": "Agent Download Sources", - "description": "Create a new agent download source", - "tags": [], + "summary": "Create agent binary download source", + "tags": [ + "Agent binary download sources" + ], "responses": { "200": { "description": "OK", @@ -4337,8 +4561,10 @@ }, "/agent_download_sources/{sourceId}": { "get": { - "summary": "Agent Download Sources - Info", - "tags": [], + "summary": "Get agent binary download source by ID", + "tags": [ + "Agent binary download sources" + ], "responses": { "200": { "description": "OK", @@ -4375,7 +4601,10 @@ } ], "delete": { - "summary": "Agent Download Sources - Delete", + "summary": "Delete agent binary download source by ID", + "tags": [ + "Agent binary download sources" + ], "operationId": "delete-download-source", "responses": { "200": { @@ -4407,7 +4636,10 @@ ] }, "put": { - "summary": "Agent Download Sources - Update", + "summary": "Update agent binary download source by ID", + "tags": [ + "Agent binary download sources" + ], "operationId": "update-download-source", "requestBody": { "content": { @@ -4464,43 +4696,12 @@ ] } }, - "/logstash_api_keys": { - "post": { - "summary": "Generate Logstash API key", - "tags": [], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "api_key": { - "type": "string" - } - } - } - } - } - }, - "400": { - "$ref": "#/components/responses/error" - } - }, - "operationId": "generate-logstash-api-key", - "parameters": [ - { - "$ref": "#/components/parameters/kbn_xsrf" - } - ] - } - }, "/fleet_server_hosts": { "get": { - "summary": "Fleet Server Hosts - List", - "description": "Return a list of Fleet server hosts", - "tags": [], + "summary": "List Fleet Server hosts", + "tags": [ + "Fleet Server hosts" + ], "responses": { "200": { "description": "OK", @@ -4536,9 +4737,10 @@ "operationId": "get-fleet-server-hosts" }, "post": { - "summary": "Fleet Server Hosts - Create", - "description": "Create a new Fleet Server Host", - "tags": [], + "summary": "Create Fleet Server host", + "tags": [ + "Fleet Server hosts" + ], "responses": { "200": { "description": "OK", @@ -4594,8 +4796,10 @@ }, "/fleet_server_hosts/{itemId}": { "get": { - "summary": "Fleet Server Hosts - Info", - "tags": [], + "summary": "Get Fleet Server host by ID", + "tags": [ + "Fleet Server hosts" + ], "responses": { "200": { "description": "OK", @@ -4632,7 +4836,10 @@ } ], "delete": { - "summary": "Fleet Server Hosts - Delete", + "summary": "Delete Fleet Server host by ID", + "tags": [ + "Fleet Server hosts" + ], "operationId": "delete-fleet-server-hosts", "responses": { "200": { @@ -4664,7 +4871,10 @@ ] }, "put": { - "summary": "Fleet Server Hosts - Update", + "summary": "Update Fleet Server host by ID", + "tags": [ + "Fleet Server hosts" + ], "operationId": "update-fleet-server-hosts", "requestBody": { "content": { @@ -4721,9 +4931,10 @@ }, "/proxies": { "get": { - "summary": "Fleet Proxies - List", - "description": "Return a list of Proxies", - "tags": [], + "summary": "List proxies", + "tags": [ + "Proxies" + ], "responses": { "200": { "description": "OK", @@ -4759,9 +4970,10 @@ "operationId": "get-fleet-proxies" }, "post": { - "summary": "Fleet Proxies - Create", - "description": "Create a new Fleet Server Host", - "tags": [], + "summary": "Create proxy", + "tags": [ + "Proxies" + ], "responses": { "200": { "description": "OK", @@ -4823,8 +5035,10 @@ }, "/proxies/{itemId}": { "get": { - "summary": "Fleet Proxies - Info", - "tags": [], + "summary": "Get proxy by ID", + "tags": [ + "Proxies" + ], "responses": { "200": { "description": "OK", @@ -4861,7 +5075,10 @@ } ], "delete": { - "summary": "Fleet Proxies - Delete", + "summary": "Delete proxy by ID", + "tags": [ + "Proxies" + ], "operationId": "delete-fleet-proxies", "responses": { "200": { @@ -4893,7 +5110,10 @@ ] }, "put": { - "summary": "Fleet Proxies - Update", + "summary": "Update proxy by ID", + "tags": [ + "Proxies" + ], "operationId": "update-fleet-proxies", "requestBody": { "content": { @@ -4956,8 +5176,10 @@ }, "/kubernetes": { "get": { - "summary": "Get K8s Full Agent Manifest", - "tags": [], + "summary": "Get full K8s agent manifest", + "tags": [ + "Kubernetes" + ], "responses": { "200": { "description": "OK", @@ -5110,6 +5332,29 @@ } } }, + "responses": { + "error": { + "description": "Generic Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "statusCode": { + "type": "number" + }, + "error": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + } + } + } + }, "schemas": { "fleet_setup_response": { "title": "Fleet Setup response", @@ -7107,29 +7352,6 @@ "url" ] } - }, - "responses": { - "error": { - "description": "Generic Error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "statusCode": { - "type": "number" - }, - "error": { - "type": "string" - }, - "message": { - "type": "string" - } - } - } - } - } - } } }, "security": [ diff --git a/x-pack/plugins/fleet/common/openapi/bundled.yaml b/x-pack/plugins/fleet/common/openapi/bundled.yaml index 7127151e5c2c3..9d6ae17a04a41 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.yaml +++ b/x-pack/plugins/fleet/common/openapi/bundled.yaml @@ -13,10 +13,44 @@ servers: - url: http://localhost:5601/api/fleet description: local paths: + /health_check: + post: + summary: Fleet Server health check + tags: + - Fleet internals + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + name: + type: string + status: + type: string + host: + type: string + '400': + $ref: '#/components/responses/error' + operationId: fleet-server-health-check + parameters: + - $ref: '#/components/parameters/kbn_xsrf' + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + host: + type: string /setup: post: - summary: Setup - tags: [] + summary: Initiate Fleet setup + tags: + - Fleet internals responses: '200': description: OK @@ -40,8 +74,9 @@ paths: - $ref: '#/components/parameters/kbn_xsrf' /settings: get: - summary: Settings - tags: [] + summary: Get settings + tags: + - Fleet internals responses: '200': description: OK @@ -53,8 +88,9 @@ paths: $ref: '#/components/responses/error' operationId: get-settings put: - summary: Settings - Update - tags: [] + summary: Update settings + tags: + - Fleet internals requestBody: content: application/json: @@ -80,42 +116,11 @@ paths: '400': $ref: '#/components/responses/error' operationId: update-settings - /health_check: - post: - summary: Fleet Server Health Check - tags: [] - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - name: - type: string - status: - type: string - host: - type: string - '400': - $ref: '#/components/responses/error' - operationId: fleet-server-health-check - parameters: - - $ref: '#/components/parameters/kbn_xsrf' - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - host: - type: string /service-tokens: post: - summary: Generate service tokens - tags: [] + summary: Create service token + tags: + - Service tokens responses: '200': description: OK @@ -136,8 +141,9 @@ paths: deprecated: true /service_tokens: post: - summary: Generate service tokens - tags: [] + summary: Create service token + tags: + - Service tokens responses: '200': description: OK @@ -158,7 +164,8 @@ paths: /epm/verification_key_id: get: summary: Get package signature verification key ID - tags: [] + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -183,26 +190,12 @@ paths: '400': $ref: '#/components/responses/error' operationId: packages-get-verification-key-id - parameters: - - schema: - type: string - name: pkgName - in: path - required: true - - schema: - type: string - name: pkgVersion - in: path - required: true - - schema: - type: string - name: filePath - in: path - required: true + parameters: [] /epm/categories: get: - summary: Package categories - tags: [] + summary: List package categories + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -221,7 +214,7 @@ paths: default: false description: >- Whether to include prerelease packages in categories count (e.g. beta, - rc, preview) + rc, preview) - in: query name: experimental deprecated: true @@ -235,8 +228,9 @@ paths: default: false /epm/packages/limited: get: - summary: Packages - Get limited list - tags: [] + summary: Get limited package list + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -255,8 +249,9 @@ paths: parameters: [] /epm/packages: get: - summary: Packages - List - tags: [] + summary: List packages + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -286,7 +281,7 @@ paths: default: false description: >- Whether to return prerelease versions of packages (e.g. beta, rc, - preview) + preview) - in: query name: experimental deprecated: true @@ -298,8 +293,9 @@ paths: schema: type: string post: - summary: Packages - Install by upload - tags: [] + summary: Install by package by direct upload + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -351,8 +347,9 @@ paths: format: binary /epm/packages/_bulk: post: - summary: Packages - Bulk install - tags: [] + summary: Bulk install packages + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -371,7 +368,7 @@ paths: default: false description: >- Whether to return prerelease versions of packages (e.g. beta, rc, - preview) + preview) requestBody: content: application/json: @@ -390,8 +387,9 @@ paths: - packages /epm/packages/{pkgkey}: get: - summary: Packages - Info - tags: [] + summary: Get package + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -434,11 +432,12 @@ paths: default: false description: >- Whether to return prerelease versions of packages (e.g. beta, rc, - preview) + preview) deprecated: true post: - summary: Packages - Install - tags: [] + summary: Install package + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -484,8 +483,9 @@ paths: type: boolean deprecated: true delete: - summary: Packages - Delete - tags: [] + summary: Delete ackage + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -531,8 +531,9 @@ paths: deprecated: true /epm/packages/{pkgName}/{pkgVersion}: get: - summary: Packages - Info - tags: [] + summary: Get package + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -600,10 +601,11 @@ paths: default: false description: >- Whether to return prerelease versions of packages (e.g. beta, rc, - preview) + preview) post: - summary: Packages - Install - tags: [] + summary: Install package + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -654,8 +656,9 @@ paths: ignore_constraints: type: boolean put: - summary: Packages - Update - tags: [] + summary: Update package settings + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -693,8 +696,9 @@ paths: keepPoliciesUpToDate: type: boolean delete: - summary: Packages - Delete - tags: [] + summary: Delete package + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -734,8 +738,9 @@ paths: type: boolean /epm/packages/{pkgName}/{pkgVersion}/{filePath}: get: - summary: Packages - Get file from registry - tags: [] + summary: Get package file + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -771,8 +776,9 @@ paths: required: true /epm/packages/{pkgName}/stats: get: - summary: Get stats for a package - tags: [] + summary: Get package stats + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -798,8 +804,9 @@ paths: required: true /agents/setup: get: - summary: Agents setup - Info - tags: [] + summary: Get agent setup info + tags: + - Agents responses: '200': description: OK @@ -813,7 +820,9 @@ paths: security: - basicAuth: [] post: - summary: Agents setup - Create + summary: Initiate agent setup + tags: + - Agents operationId: setup-agents responses: '200': @@ -841,8 +850,9 @@ paths: - $ref: '#/components/parameters/kbn_xsrf' /agent-status: get: - summary: Agents - Summary stats - tags: [] + summary: Get agent status summary + tags: + - Agent status responses: '200': description: OK @@ -888,8 +898,9 @@ paths: deprecated: true /agent_status: get: - summary: Agents - Summary stats - tags: [] + summary: Get agent status summary + tags: + - Agent status responses: '200': description: OK @@ -948,8 +959,9 @@ paths: required: false /agent_status/data: get: - summary: Agents - Get incoming data - tags: [] + summary: Get incoming agent data + tags: + - Agent status responses: '200': description: OK @@ -980,8 +992,9 @@ paths: required: true /agents: get: - summary: Agents - List - tags: [] + summary: List agents + tags: + - Agents responses: '200': description: OK @@ -1010,8 +1023,9 @@ paths: - basicAuth: [] /agents/bulk_upgrade: post: - summary: Agents - Bulk Upgrade - tags: [] + summary: Bulk upgrade agents + tags: + - Agents responses: '200': description: OK @@ -1043,7 +1057,9 @@ paths: start_time: '2022-08-03T14:00:00.000Z' /agents/action_status: get: - summary: Agents - Action status + summary: Get agent action status + tags: + - Agent actions parameters: - $ref: '#/components/parameters/page_size' - $ref: '#/components/parameters/page_index' @@ -1116,8 +1132,9 @@ paths: in: path required: true get: - summary: Agent - Info - tags: [] + summary: Get agent by ID + tags: + - Agents responses: '200': description: OK @@ -1136,8 +1153,9 @@ paths: parameters: - $ref: '#/components/parameters/with_metrics' put: - summary: Agent - Update - tags: [] + summary: Update agent by ID + tags: + - Agents responses: '200': description: OK @@ -1169,8 +1187,9 @@ paths: items: type: string delete: - summary: Agent - Delete - tags: [] + summary: Delete agent by ID + tags: + - Agents responses: '200': description: OK @@ -1198,8 +1217,9 @@ paths: in: path required: true post: - summary: Agent - Actions - tags: [] + summary: Create agent action + tags: + - Agent actions responses: '200': description: OK @@ -1243,8 +1263,9 @@ paths: in: path required: true post: - summary: Agent - Cancel Action - tags: [] + summary: Cancel agent action + tags: + - Agent actions responses: '200': description: OK @@ -1273,8 +1294,9 @@ paths: in: path required: true get: - summary: Get agent upload file - tags: [] + summary: Get file uploaded by agent + tags: + - Agents responses: '200': description: OK @@ -1301,9 +1323,10 @@ paths: name: agentId in: path required: true - put: - summary: Agent - Reassign - tags: [] + post: + summary: Reassign agent + tags: + - Agents responses: '200': description: OK @@ -1327,6 +1350,34 @@ paths: type: string required: - policy_id + put: + summary: Reassign agent + tags: + - Agents + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + '400': + $ref: '#/components/responses/error' + operationId: reassign-agent-deprecated + parameters: + - $ref: '#/components/parameters/kbn_xsrf' + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + policy_id: + type: string + required: + - policy_id + deprecated: true /agents/{agentId}/unenroll: parameters: - schema: @@ -1335,8 +1386,9 @@ paths: in: path required: true post: - summary: Agent - Unenroll - tags: [] + summary: Unenroll agent + tags: + - Agents responses: '200': description: OK @@ -1380,8 +1432,9 @@ paths: in: path required: true post: - summary: Agent - Upgrade - tags: [] + summary: Upgrade agent + tags: + - Agents responses: '200': description: OK @@ -1409,7 +1462,8 @@ paths: required: true get: summary: List agent uploads - tags: [] + tags: + - Agents responses: '200': description: OK @@ -1430,8 +1484,9 @@ paths: operationId: list-agent-uploads /agents/bulk_reassign: post: - summary: Agents - Bulk reassign - tags: [] + summary: Bulk reassign agents + tags: + - Agents responses: '200': description: OK @@ -1472,8 +1527,9 @@ paths: agents: 'fleet-agents.policy_id : ("policy1" or "policy2")' /agents/bulk_unenroll: post: - summary: Agents - Bulk unenroll - tags: [] + summary: Bulk unenroll agents + tags: + - Agents responses: '200': description: OK @@ -1519,8 +1575,9 @@ paths: - agent2 /agents/bulk_update_agent_tags: post: - summary: Agents - Bulk update tags - tags: [] + summary: Bulk update agent tags + tags: + - Agents responses: '200': description: OK @@ -1572,8 +1629,9 @@ paths: - existingTag /agents/tags: get: - summary: Agent Tags - List - description: List all agent tags + summary: List agent tags + tags: + - Agents responses: '200': description: OK @@ -1592,8 +1650,9 @@ paths: in: path required: true post: - summary: Agent - Request Diagnostics - tags: [] + summary: Request agent diagnostics + tags: + - Agents responses: '200': description: OK @@ -1611,8 +1670,9 @@ paths: - $ref: '#/components/parameters/kbn_xsrf' /agents/bulk_request_diagnostics: post: - summary: Agent - Bulk Request Diagnostics - tags: [] + summary: Bulk request diagnostics from agents + tags: + - Agents responses: '200': description: OK @@ -1650,8 +1710,9 @@ paths: agents: 'fleet-agents.policy_id : ("policy1" or "policy2")' /agent_policies: get: - summary: Agent policies - List - tags: [] + summary: List agent policies + tags: + - Agent policies responses: '200': description: OK @@ -1700,8 +1761,9 @@ paths: 0 if set to true. description: '' post: - summary: Agent policy - Create - tags: [] + summary: Create agent policy + tags: + - Agent policies responses: '200': description: OK @@ -1731,8 +1793,9 @@ paths: in: path required: true get: - summary: Agent policy - Info - tags: [] + summary: Get agent policy by ID + tags: + - Agent policies responses: '200': description: OK @@ -1751,8 +1814,9 @@ paths: description: Get one agent policy parameters: [] put: - summary: Agent policy - Update - tags: [] + summary: Update agent policy by ID + tags: + - Agent policies responses: '200': description: OK @@ -1783,7 +1847,9 @@ paths: in: path required: true post: - summary: Agent policy - copy one policy + summary: Copy agent policy by ID + tags: + - Agent policies operationId: agent-policy-copy parameters: - $ref: '#/components/parameters/kbn_xsrf' @@ -1816,7 +1882,9 @@ paths: description: '' /agent_policies/{agentPolicyId}/full: get: - summary: Agent policy - Get full policy + summary: Get full agent policy by ID + tags: + - Agent policies operationId: agent-policy-full responses: '200': @@ -1855,7 +1923,9 @@ paths: required: false /agent_policies/{agentPolicyId}/download: get: - summary: Agent policy - Download + summary: Download agent policy by ID + tags: + - Agent policies operationId: agent-policy-download responses: '200': @@ -1892,8 +1962,9 @@ paths: required: false /agent_policies/_bulk_get: post: - summary: Agent policies - Bulk Get - tags: [] + summary: Bulk get agent policies + tags: + - Agent policies requestBody: content: application/json: @@ -1933,7 +2004,9 @@ paths: parameters: [] /agent_policies/delete: post: - summary: Agent policy - Delete + summary: Delete agent policy by ID + tags: + - Agent policies operationId: delete-agent-policy responses: '200': @@ -1967,8 +2040,9 @@ paths: parameters: [] /data_streams: get: - summary: Data streams - List - tags: [] + summary: List data streams + tags: + - Data streams responses: '200': description: OK @@ -1987,8 +2061,9 @@ paths: parameters: [] /enrollment-api-keys: get: - summary: Enrollment API Keys - List - tags: [] + summary: List enrollment API keys + tags: + - Enrollment API keys responses: '200': description: OK @@ -2023,8 +2098,9 @@ paths: parameters: [] deprecated: true post: - summary: Enrollment API Key - Create - tags: [] + summary: Create enrollment API key + tags: + - Enrollment API keys responses: '200': description: OK @@ -2053,8 +2129,9 @@ paths: in: path required: true get: - summary: Enrollment API Key - Info - tags: [] + summary: Get enrollment API key by ID + tags: + - Enrollment API keys responses: '200': description: OK @@ -2072,8 +2149,9 @@ paths: operationId: get-enrollment-api-key-deprecated deprecated: true delete: - summary: Enrollment API Key - Delete - tags: [] + summary: Delete enrollment API key by ID + tags: + - Enrollment API keys responses: '200': description: OK @@ -2096,8 +2174,9 @@ paths: deprecated: true /enrollment_api_keys: get: - summary: Enrollment API Keys - List - tags: [] + summary: List enrollment API keys + tags: + - Enrollment API keys responses: '200': description: OK @@ -2131,8 +2210,9 @@ paths: operationId: get-enrollment-api-keys parameters: [] post: - summary: Enrollment API Key - Create - tags: [] + summary: Create enrollment API key + tags: + - Enrollment API keys responses: '200': description: OK @@ -2160,8 +2240,9 @@ paths: in: path required: true get: - summary: Enrollment API Key - Info - tags: [] + summary: Get enrollment API key by ID + tags: + - Enrollment API keys responses: '200': description: OK @@ -2178,8 +2259,9 @@ paths: $ref: '#/components/responses/error' operationId: get-enrollment-api-key delete: - summary: Enrollment API Key - Delete - tags: [] + summary: Delete enrollment API key by ID + tags: + - Enrollment API keys responses: '200': description: OK @@ -2201,8 +2283,9 @@ paths: - $ref: '#/components/parameters/kbn_xsrf' /package_policies: get: - summary: Package policies - List - tags: [] + summary: List package policies + tags: + - Package policies responses: '200': description: OK @@ -2230,7 +2313,9 @@ paths: parameters: [] parameters: [] post: - summary: Package policy - Create + summary: Create package policy + tags: + - Package policies operationId: create-package-policy responses: '200': @@ -2258,8 +2343,9 @@ paths: - $ref: '#/components/parameters/kbn_xsrf' /package_policies/_bulk_get: post: - summary: Package policies - Bulk Get - tags: [] + summary: Bulk get package policies + tags: + - Package policies requestBody: content: application/json: @@ -2296,7 +2382,9 @@ paths: parameters: [] /package_policies/delete: post: - summary: Package policy - Delete + summary: Delete package policy + tags: + - Package policies operationId: post-delete-package-policy requestBody: content: @@ -2337,7 +2425,9 @@ paths: - $ref: '#/components/parameters/kbn_xsrf' /package_policies/upgrade: post: - summary: Package policy - Upgrade + summary: Upgrade package policy to a newer package version + tags: + - Package policies operationId: upgrade-package-policy requestBody: content: @@ -2374,7 +2464,9 @@ paths: $ref: '#/components/responses/error' /package_policies/upgrade/dryrun: post: - summary: Package policy - Upgrade Dry run + summary: Dry run package policy upgrade + tags: + - Package policies operationId: upgrade-package-policy-dry-run requestBody: content: @@ -2412,8 +2504,9 @@ paths: $ref: '#/components/responses/error' /package_policies/{packagePolicyId}: get: - summary: Package policy - Info - tags: [] + summary: Get package policy by ID + tags: + - Package policies responses: '200': description: OK @@ -2436,7 +2529,9 @@ paths: in: path required: true put: - summary: Package policy - Update + summary: Update package policy by ID + tags: + - Package policies operationId: update-package-policy requestBody: content: @@ -2463,8 +2558,9 @@ paths: parameters: - $ref: '#/components/parameters/kbn_xsrf' delete: - summary: Package policy - Delete - tags: [] + summary: Delete package policy by ID + tags: + - Package policies operationId: delete-package-policy responses: '200': @@ -2487,8 +2583,9 @@ paths: in: query /outputs: get: - summary: Outputs - tags: [] + summary: List outputs + tags: + - Outputs responses: '200': description: OK @@ -2511,9 +2608,9 @@ paths: $ref: '#/components/responses/error' operationId: get-outputs post: - summary: Outputs - description: Create a new output - tags: [] + summary: Create output + tags: + - Outputs responses: '200': description: OK @@ -2558,8 +2655,9 @@ paths: operationId: post-outputs /outputs/{outputId}: get: - summary: Output - Info - tags: [] + summary: Get output by ID + tags: + - Outputs responses: '200': description: OK @@ -2582,7 +2680,9 @@ paths: in: path required: true delete: - summary: Output - Delete + summary: Delete output by ID + tags: + - Outputs operationId: delete-output responses: '200': @@ -2601,7 +2701,9 @@ paths: parameters: - $ref: '#/components/parameters/kbn_xsrf' put: - summary: Output - Update + summary: Update output by ID + tags: + - Outputs operationId: update-output requestBody: content: @@ -2648,10 +2750,31 @@ paths: $ref: '#/components/responses/error' parameters: - $ref: '#/components/parameters/kbn_xsrf' + /logstash_api_keys: + post: + summary: Generate Logstash API key + tags: + - Outputs + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + api_key: + type: string + '400': + $ref: '#/components/responses/error' + operationId: generate-logstash-api-key + parameters: + - $ref: '#/components/parameters/kbn_xsrf' /agent_download_sources: get: - summary: Agent Download Sources - tags: [] + summary: List agent binary download sources + tags: + - Agent binary download sources responses: '200': description: OK @@ -2674,9 +2797,9 @@ paths: $ref: '#/components/responses/error' operationId: get-download-sources post: - summary: Agent Download Sources - description: Create a new agent download source - tags: [] + summary: Create agent binary download source + tags: + - Agent binary download sources responses: '200': description: OK @@ -2710,8 +2833,9 @@ paths: operationId: post-download-sources /agent_download_sources/{sourceId}: get: - summary: Agent Download Sources - Info - tags: [] + summary: Get agent binary download source by ID + tags: + - Agent binary download sources responses: '200': description: OK @@ -2734,7 +2858,9 @@ paths: in: path required: true delete: - summary: Agent Download Sources - Delete + summary: Delete agent binary download source by ID + tags: + - Agent binary download sources operationId: delete-download-source responses: '200': @@ -2753,7 +2879,9 @@ paths: parameters: - $ref: '#/components/parameters/kbn_xsrf' put: - summary: Agent Download Sources - Update + summary: Update agent binary download source by ID + tags: + - Agent binary download sources operationId: update-download-source requestBody: content: @@ -2787,30 +2915,11 @@ paths: $ref: '#/components/responses/error' parameters: - $ref: '#/components/parameters/kbn_xsrf' - /logstash_api_keys: - post: - summary: Generate Logstash API key - tags: [] - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - api_key: - type: string - '400': - $ref: '#/components/responses/error' - operationId: generate-logstash-api-key - parameters: - - $ref: '#/components/parameters/kbn_xsrf' /fleet_server_hosts: get: - summary: Fleet Server Hosts - List - description: Return a list of Fleet server hosts - tags: [] + summary: List Fleet Server hosts + tags: + - Fleet Server hosts responses: '200': description: OK @@ -2833,9 +2942,9 @@ paths: $ref: '#/components/responses/error' operationId: get-fleet-server-hosts post: - summary: Fleet Server Hosts - Create - description: Create a new Fleet Server Host - tags: [] + summary: Create Fleet Server host + tags: + - Fleet Server hosts responses: '200': description: OK @@ -2870,8 +2979,9 @@ paths: operationId: post-fleet-server-hosts /fleet_server_hosts/{itemId}: get: - summary: Fleet Server Hosts - Info - tags: [] + summary: Get Fleet Server host by ID + tags: + - Fleet Server hosts responses: '200': description: OK @@ -2894,7 +3004,9 @@ paths: in: path required: true delete: - summary: Fleet Server Hosts - Delete + summary: Delete Fleet Server host by ID + tags: + - Fleet Server hosts operationId: delete-fleet-server-hosts responses: '200': @@ -2913,7 +3025,9 @@ paths: parameters: - $ref: '#/components/parameters/kbn_xsrf' put: - summary: Fleet Server Hosts - Update + summary: Update Fleet Server host by ID + tags: + - Fleet Server hosts operationId: update-fleet-server-hosts requestBody: content: @@ -2947,9 +3061,9 @@ paths: - $ref: '#/components/parameters/kbn_xsrf' /proxies: get: - summary: Fleet Proxies - List - description: Return a list of Proxies - tags: [] + summary: List proxies + tags: + - Proxies responses: '200': description: OK @@ -2972,9 +3086,9 @@ paths: $ref: '#/components/responses/error' operationId: get-fleet-proxies post: - summary: Fleet Proxies - Create - description: Create a new Fleet Server Host - tags: [] + summary: Create proxy + tags: + - Proxies responses: '200': description: OK @@ -3013,8 +3127,9 @@ paths: operationId: post-fleet-proxies /proxies/{itemId}: get: - summary: Fleet Proxies - Info - tags: [] + summary: Get proxy by ID + tags: + - Proxies responses: '200': description: OK @@ -3037,7 +3152,9 @@ paths: in: path required: true delete: - summary: Fleet Proxies - Delete + summary: Delete proxy by ID + tags: + - Proxies operationId: delete-fleet-proxies responses: '200': @@ -3056,7 +3173,9 @@ paths: parameters: - $ref: '#/components/parameters/kbn_xsrf' put: - summary: Fleet Proxies - Update + summary: Update proxy by ID + tags: + - Proxies operationId: update-fleet-proxies requestBody: content: @@ -3094,8 +3213,9 @@ paths: - $ref: '#/components/parameters/kbn_xsrf' /kubernetes: get: - summary: Get K8s Full Agent Manifest - tags: [] + summary: Get full K8s agent manifest + tags: + - Kubernetes responses: '200': description: OK @@ -3203,6 +3323,20 @@ components: required: false schema: type: boolean + responses: + error: + description: Generic Error + content: + application/json: + schema: + type: object + properties: + statusCode: + type: number + error: + type: string + message: + type: string schemas: fleet_setup_response: title: Fleet Setup response @@ -4585,19 +4719,5 @@ components: required: - name - url - responses: - error: - description: Generic Error - content: - application/json: - schema: - type: object - properties: - statusCode: - type: number - error: - type: string - message: - type: string security: - basicAuth: [] diff --git a/x-pack/plugins/fleet/common/openapi/entrypoint.yaml b/x-pack/plugins/fleet/common/openapi/entrypoint.yaml index 37c33c28b1053..ca9d1cd3c8e19 100644 --- a/x-pack/plugins/fleet/common/openapi/entrypoint.yaml +++ b/x-pack/plugins/fleet/common/openapi/entrypoint.yaml @@ -13,18 +13,18 @@ servers: - url: 'http://localhost:5601/api/fleet' description: local paths: - # plugin-wide endpoint(s) + # Fleet internals + /health_check: + $ref: paths/health_check.yaml /setup: $ref: paths/setup.yaml /settings: $ref: paths/settings.yaml - # App endpoints - /health_check: - $ref: paths/health_check.yaml /service-tokens: $ref: paths/service_tokens_deprecated.yaml /service_tokens: $ref: paths/service_tokens.yaml + # EPM / integrations endpoints /epm/verification_key_id: $ref: paths/epm@verification_key_id.yaml @@ -44,7 +44,8 @@ paths: $ref: paths/epm@get_file.yaml '/epm/packages/{pkgName}/stats': $ref: 'paths/epm@packages@{pkg_name}@stats.yaml' - # Agent-related endpoints + + # Agent endpoints /agents/setup: $ref: paths/agents@setup.yaml /agent-status: @@ -87,6 +88,7 @@ paths: $ref: 'paths/agents@{agent_id}@request_diagnostics.yaml' /agents/bulk_request_diagnostics: $ref: 'paths/agents@bulk_request_diagnostics.yaml' + # Agent policies endpoints /agent_policies: $ref: paths/agent_policies.yaml @@ -102,9 +104,11 @@ paths: $ref: paths/agent_policies@_bulk_get.yaml /agent_policies/delete: $ref: paths/agent_policies@delete.yaml + # Data streams endpoints /data_streams: $ref: paths/data_streams.yaml + # Enrollment endpoints /enrollment-api-keys: $ref: paths/enrollment_api_keys_deprecated.yaml @@ -114,6 +118,7 @@ paths: $ref: paths/enrollment_api_keys.yaml '/enrollment_api_keys/{keyId}': $ref: 'paths/enrollment_api_keys@{key_id}.yaml' + # Package policies endpoints /package_policies: $ref: paths/package_policies.yaml @@ -127,27 +132,33 @@ paths: $ref: paths/package_policies@upgrade_dryrun.yaml '/package_policies/{packagePolicyId}': $ref: 'paths/package_policies@{package_policy_id}.yaml' + # Outputs /outputs: $ref: paths/outputs.yaml /outputs/{outputId}: $ref: paths/outputs@{output_id}.yaml + /logstash_api_keys: + $ref: paths/logstash_api_keys.yaml + + # Agent binary download sources /agent_download_sources: $ref: paths/agent_download_sources.yaml /agent_download_sources/{sourceId}: $ref: paths/agent_download_sources@{source_id}.yaml - /logstash_api_keys: - $ref: paths/logstash_api_keys.yaml + # Fleet server hosts /fleet_server_hosts: $ref: paths/fleet_server_hosts.yaml /fleet_server_hosts/{itemId}: $ref: paths/fleet_server_hosts@{item_id}.yaml + # Fleet proxies /proxies: $ref: paths/proxies.yaml /proxies/{itemId}: $ref: paths/proxies@{item_id}.yaml + # K8s /kubernetes: $ref: paths/kubernetes.yaml diff --git a/x-pack/plugins/fleet/common/openapi/paths/agent_download_sources.yaml b/x-pack/plugins/fleet/common/openapi/paths/agent_download_sources.yaml index 619b3c626ac66..89a69c9adfdaf 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agent_download_sources.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agent_download_sources.yaml @@ -1,6 +1,7 @@ get: - summary: Agent Download Sources - tags: [] + summary: List agent binary download sources + tags: + - Agent binary download sources responses: '200': description: OK @@ -23,9 +24,9 @@ get: $ref: ../components/responses/error.yaml operationId: get-download-sources post: - summary: Agent Download Sources - description: 'Create a new agent download source' - tags: [] + summary: Create agent binary download source + tags: + - Agent binary download sources responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/agent_download_sources@{source_id}.yaml b/x-pack/plugins/fleet/common/openapi/paths/agent_download_sources@{source_id}.yaml index 364d292082c8b..afb7771283e59 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agent_download_sources@{source_id}.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agent_download_sources@{source_id}.yaml @@ -1,6 +1,7 @@ get: - summary: Agent Download Sources - Info - tags: [] + summary: Get agent binary download source by ID + tags: + - Agent binary download sources responses: '200': description: OK @@ -23,7 +24,9 @@ parameters: in: path required: true delete: - summary: Agent Download Sources - Delete + summary: Delete agent binary download source by ID + tags: + - Agent binary download sources operationId: delete-download-source responses: '200': @@ -42,7 +45,9 @@ delete: parameters: - $ref: ../components/headers/kbn_xsrf.yaml put: - summary: Agent Download Sources - Update + summary: Update agent binary download source by ID + tags: + - Agent binary download sources operationId: update-download-source requestBody: content: diff --git a/x-pack/plugins/fleet/common/openapi/paths/agent_policies.yaml b/x-pack/plugins/fleet/common/openapi/paths/agent_policies.yaml index 33fd8a2348412..cbf29f3859519 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agent_policies.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agent_policies.yaml @@ -1,6 +1,7 @@ get: - summary: Agent policies - List - tags: [] + summary: List agent policies + tags: + - Agent policies responses: '200': description: OK @@ -44,8 +45,9 @@ get: description: '' post: - summary: Agent policy - Create - tags: [] + summary: Create agent policy + tags: + - Agent policies responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/agent_policies@_bulk_get.yaml b/x-pack/plugins/fleet/common/openapi/paths/agent_policies@_bulk_get.yaml index 75267e2a262a9..ace09ef721677 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agent_policies@_bulk_get.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agent_policies@_bulk_get.yaml @@ -1,6 +1,7 @@ post: - summary: Agent policies - Bulk Get - tags: [] + summary: Bulk get agent policies + tags: + - Agent policies requestBody: content: application/json: diff --git a/x-pack/plugins/fleet/common/openapi/paths/agent_policies@delete.yaml b/x-pack/plugins/fleet/common/openapi/paths/agent_policies@delete.yaml index 51967a697cf0e..966d8abc1e328 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agent_policies@delete.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agent_policies@delete.yaml @@ -1,5 +1,7 @@ post: - summary: Agent policy - Delete + summary: Delete agent policy by ID + tags: + - Agent policies operationId: delete-agent-policy responses: '200': diff --git a/x-pack/plugins/fleet/common/openapi/paths/agent_policies@{agent_policy_id}.yaml b/x-pack/plugins/fleet/common/openapi/paths/agent_policies@{agent_policy_id}.yaml index 4a7e88abcbab8..55d644ab0aab2 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agent_policies@{agent_policy_id}.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agent_policies@{agent_policy_id}.yaml @@ -5,8 +5,9 @@ parameters: in: path required: true get: - summary: Agent policy - Info - tags: [] + summary: Get agent policy by ID + tags: + - Agent policies responses: '200': description: OK @@ -25,8 +26,9 @@ get: description: Get one agent policy parameters: [] put: - summary: Agent policy - Update - tags: [] + summary: Update agent policy by ID + tags: + - Agent policies responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/agent_policies@{agent_policy_id}@copy.yaml b/x-pack/plugins/fleet/common/openapi/paths/agent_policies@{agent_policy_id}@copy.yaml index 21d4a1d493b01..dab79eef58dff 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agent_policies@{agent_policy_id}@copy.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agent_policies@{agent_policy_id}@copy.yaml @@ -5,7 +5,9 @@ parameters: in: path required: true post: - summary: Agent policy - copy one policy + summary: Copy agent policy by ID + tags: + - Agent policies operationId: agent-policy-copy parameters: - $ref: ../components/headers/kbn_xsrf.yaml @@ -36,4 +38,3 @@ post: required: - name description: '' - diff --git a/x-pack/plugins/fleet/common/openapi/paths/agent_policies@{agent_policy_id}@download.yaml b/x-pack/plugins/fleet/common/openapi/paths/agent_policies@{agent_policy_id}@download.yaml index 5c7887d6f1bb2..1748950fdaf09 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agent_policies@{agent_policy_id}@download.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agent_policies@{agent_policy_id}@download.yaml @@ -1,6 +1,7 @@ - get: - summary: Agent policy - Download + summary: Download agent policy by ID + tags: + - Agent policies operationId: agent-policy-download responses: '200': diff --git a/x-pack/plugins/fleet/common/openapi/paths/agent_policies@{agent_policy_id}@full.yaml b/x-pack/plugins/fleet/common/openapi/paths/agent_policies@{agent_policy_id}@full.yaml index 1a79266e27732..dc5a1b996b2e4 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agent_policies@{agent_policy_id}@full.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agent_policies@{agent_policy_id}@full.yaml @@ -1,6 +1,7 @@ - get: - summary: Agent policy - Get full policy + summary: Get full agent policy by ID + tags: + - Agent policies operationId: agent-policy-full responses: '200': diff --git a/x-pack/plugins/fleet/common/openapi/paths/agent_status.yaml b/x-pack/plugins/fleet/common/openapi/paths/agent_status.yaml index 46d8ac2f32ff9..6d4b1c77d991a 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agent_status.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agent_status.yaml @@ -1,6 +1,7 @@ get: - summary: Agents - Summary stats - tags: [] + summary: Get agent status summary + tags: + - Agent status responses: '200': description: OK @@ -29,7 +30,7 @@ get: updating: type: integer all: - type: integer + type: integer active: type: integer required: diff --git a/x-pack/plugins/fleet/common/openapi/paths/agent_status@data.yaml b/x-pack/plugins/fleet/common/openapi/paths/agent_status@data.yaml index a16fa2f71f8a8..7e90097c3b4dd 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agent_status@data.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agent_status@data.yaml @@ -1,6 +1,7 @@ get: - summary: Agents - Get incoming data - tags: [] + summary: Get incoming agent data + tags: + - Agent status responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/agent_status_deprecated.yaml b/x-pack/plugins/fleet/common/openapi/paths/agent_status_deprecated.yaml index 874cd38632ed3..fe44311fa9801 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agent_status_deprecated.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agent_status_deprecated.yaml @@ -1,6 +1,7 @@ get: - summary: Agents - Summary stats - tags: [] + summary: Get agent status summary + tags: + - Agent status responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/agent_tags.yaml b/x-pack/plugins/fleet/common/openapi/paths/agent_tags.yaml index f01584ef4665a..85a6f6c7ab30a 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agent_tags.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agent_tags.yaml @@ -1,6 +1,7 @@ get: - summary: Agent Tags - List - description: List all agent tags + summary: List agent tags + tags: + - Agents responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents.yaml index b9b62f23552e1..cd74ff0a56636 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents.yaml @@ -1,6 +1,7 @@ get: - summary: Agents - List - tags: [] + summary: List agents + tags: + - Agents responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents@action_status.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@action_status.yaml index 50494a29f4096..1c2d013457d6f 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents@action_status.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents@action_status.yaml @@ -1,8 +1,10 @@ get: - summary: Agents - Action status + summary: Get agent action status + tags: + - Agent actions parameters: - - $ref: ../components/parameters/page_size.yaml - - $ref: ../components/parameters/page_index.yaml + - $ref: ../components/parameters/page_size.yaml + - $ref: ../components/parameters/page_index.yaml responses: '200': description: OK @@ -33,7 +35,7 @@ get: nbAgentsAck: type: number nbAgentsFailed: - type: number + type: number version: type: string startTime: @@ -49,7 +51,7 @@ get: newPolicyId: type: string creationTime: - type: string + type: string required: - actionId - complete @@ -63,4 +65,4 @@ get: - items '400': $ref: ../components/responses/error.yaml - operationId: agents-action-status \ No newline at end of file + operationId: agents-action-status diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_reassign.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_reassign.yaml index 625aee38eafc4..b93b2bd6b9a08 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_reassign.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_reassign.yaml @@ -1,16 +1,17 @@ post: - summary: Agents - Bulk reassign - tags: [] + summary: Bulk reassign agents + tags: + - Agents responses: '200': - description: OK - content: - application/json: - schema: - type: object - properties: - actionId: - type: string + description: OK + content: + application/json: + schema: + type: object + properties: + actionId: + type: string '400': $ref: ../components/responses/error.yaml operationId: bulk-reassign-agents @@ -38,4 +39,4 @@ post: - agents example: policy_id: policy_id - agents: "fleet-agents.policy_id : (\"policy1\" or \"policy2\")" + agents: 'fleet-agents.policy_id : ("policy1" or "policy2")' diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_request_diagnostics.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_request_diagnostics.yaml index e2952e7ae51e0..3f0733ed8f258 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_request_diagnostics.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_request_diagnostics.yaml @@ -1,6 +1,7 @@ post: - summary: Agent - Bulk Request Diagnostics - tags: [] + summary: Bulk request diagnostics from agents + tags: + - Agents responses: '200': description: OK @@ -35,4 +36,4 @@ post: required: - agents example: - agents: "fleet-agents.policy_id : (\"policy1\" or \"policy2\")" + agents: 'fleet-agents.policy_id : ("policy1" or "policy2")' diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_unenroll.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_unenroll.yaml index 7527558a4cc10..1ab9e4038b978 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_unenroll.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_unenroll.yaml @@ -1,16 +1,17 @@ post: - summary: Agents - Bulk unenroll - tags: [] + summary: Bulk unenroll agents + tags: + - Agents responses: '200': - description: OK - content: - application/json: - schema: - type: object - properties: - actionId: - type: string + description: OK + content: + application/json: + schema: + type: object + properties: + actionId: + type: string '400': $ref: ../components/responses/error.yaml operationId: bulk-unenroll-agents diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_update_tags.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_update_tags.yaml index 77c0c0d4cfa7c..ff4c6597b6be0 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_update_tags.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_update_tags.yaml @@ -1,16 +1,17 @@ post: - summary: Agents - Bulk update tags - tags: [] + summary: Bulk update agent tags + tags: + - Agents responses: '200': - description: OK - content: - application/json: - schema: - type: object - properties: - actionId: - type: string + description: OK + content: + application/json: + schema: + type: object + properties: + actionId: + type: string '400': $ref: ../components/responses/error.yaml operationId: bulk-update-agent-tags @@ -39,10 +40,10 @@ post: items: type: string batchSize: - type: number + type: number required: - agents example: agents: [agent1, agent2] tagsToAdd: [newTag] - tagsToRemove: [existingTag] + tagsToRemove: [existingTag] diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_upgrade.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_upgrade.yaml index b8863eaf271fd..ccb55c7c62b17 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_upgrade.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_upgrade.yaml @@ -1,16 +1,17 @@ post: - summary: Agents - Bulk Upgrade - tags: [] + summary: Bulk upgrade agents + tags: + - Agents responses: '200': description: OK content: application/json: schema: - type: object - properties: - actionId: - type: string + type: object + properties: + actionId: + type: string '400': $ref: ../components/responses/error.yaml operationId: bulk-upgrade-agents diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents@current_upgrades.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@current_upgrades.yaml index 162c9e4cb5bc3..36ae723527f9b 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents@current_upgrades.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents@current_upgrades.yaml @@ -1,6 +1,7 @@ get: - summary: Agents - Current Bulk Upgrades - tags: [] + summary: List current bulk upgrade operations + tags: + - Agents responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents@files@{file_id}@{file_name}.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@files@{file_id}@{file_name}.yaml index 82192ade7856b..15f6dd8a421d1 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents@files@{file_id}@{file_name}.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents@files@{file_id}@{file_name}.yaml @@ -10,8 +10,9 @@ parameters: in: path required: true get: - summary: Get agent upload file - tags: [] + summary: Get file uploaded by agent + tags: + - Agents responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents@setup.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@setup.yaml index 104ae1ba084da..214f3a8e68240 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents@setup.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents@setup.yaml @@ -1,6 +1,7 @@ get: - summary: Agents setup - Info - tags: [] + summary: Get agent setup info + tags: + - Agents responses: '200': description: OK @@ -14,7 +15,9 @@ get: security: - basicAuth: [] post: - summary: Agents setup - Create + summary: Initiate agent setup + tags: + - Agents operationId: setup-agents responses: '200': diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}.yaml index 7bf3a7d73f31b..93242e5912a17 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}.yaml @@ -5,8 +5,9 @@ parameters: in: path required: true get: - summary: Agent - Info - tags: [] + summary: Get agent by ID + tags: + - Agents responses: '200': description: OK @@ -25,8 +26,9 @@ get: parameters: - $ref: ../components/parameters/with_metrics.yaml put: - summary: Agent - Update - tags: [] + summary: Update agent by ID + tags: + - Agents responses: '200': description: OK @@ -58,8 +60,9 @@ put: items: type: string delete: - summary: Agent - Delete - tags: [] + summary: Delete agent by ID + tags: + - Agents responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@actions.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@actions.yaml index 38cce1ea54db3..cd327e453b9a7 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@actions.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@actions.yaml @@ -5,8 +5,9 @@ parameters: in: path required: true post: - summary: Agent - Actions - tags: [] + summary: Create agent action + tags: + - Agent actions responses: '200': description: OK @@ -35,5 +36,5 @@ post: schema: type: object properties: - action: - $ref: ../components/schemas/agent_action.yaml + action: + $ref: ../components/schemas/agent_action.yaml diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@actions@{action_id}@cancel.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@actions@{action_id}@cancel.yaml index c9bf661d88a85..f91acd133355d 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@actions@{action_id}@cancel.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@actions@{action_id}@cancel.yaml @@ -10,8 +10,9 @@ parameters: in: path required: true post: - summary: Agent - Cancel Action - tags: [] + summary: Cancel agent action + tags: + - Agent actions responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@reassign.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@reassign.yaml index 4827cc77fc634..c210cee12d424 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@reassign.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@reassign.yaml @@ -4,9 +4,10 @@ parameters: name: agentId in: path required: true -put: - summary: Agent - Reassign - tags: [] +post: + summary: Reassign agent + tags: + - Agents responses: '200': description: OK @@ -30,4 +31,31 @@ put: type: string required: - policy_id - +put: + summary: Reassign agent + tags: + - Agents + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + '400': + $ref: ../components/responses/error.yaml + operationId: reassign-agent-deprecated + parameters: + - $ref: ../components/headers/kbn_xsrf.yaml + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + policy_id: + type: string + required: + - policy_id + deprecated: true diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@request_diagnostics.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@request_diagnostics.yaml index 13b335ffe2300..37aed12d5f4f1 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@request_diagnostics.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@request_diagnostics.yaml @@ -5,8 +5,9 @@ parameters: in: path required: true post: - summary: Agent - Request Diagnostics - tags: [] + summary: Request agent diagnostics + tags: + - Agents responses: '200': description: OK @@ -22,4 +23,3 @@ post: operationId: request-diagnostics-agent parameters: - $ref: ../components/headers/kbn_xsrf.yaml - diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@unenroll.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@unenroll.yaml index b9664ae650112..c30bebfad328a 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@unenroll.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@unenroll.yaml @@ -5,30 +5,31 @@ parameters: in: path required: true post: - summary: Agent - Unenroll - tags: [] + summary: Unenroll agent + tags: + - Agents responses: '200': - description: OK - content: - application/json: - schema: - type: object + description: OK + content: + application/json: + schema: + type: object '400': - description: BAD REQUEST - content: - application/json: - schema: - type: object - properties: - error: - type: string - message: - type: string - statusCode: - type: number - enum: - - 400 + description: BAD REQUEST + content: + application/json: + schema: + type: object + properties: + error: + type: string + message: + type: string + statusCode: + type: number + enum: + - 400 operationId: unenroll-agent parameters: - $ref: ../components/headers/kbn_xsrf.yaml diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@upgrade.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@upgrade.yaml index c6d66f6f52386..d824d4a54f985 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@upgrade.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@upgrade.yaml @@ -5,8 +5,9 @@ parameters: in: path required: true post: - summary: Agent - Upgrade - tags: [] + summary: Upgrade agent + tags: + - Agents responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@uploads.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@uploads.yaml index de812b0e363e3..f92acc7fe5086 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@uploads.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@uploads.yaml @@ -6,7 +6,8 @@ parameters: required: true get: summary: List agent uploads - tags: [] + tags: + - Agents responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/data_streams.yaml b/x-pack/plugins/fleet/common/openapi/paths/data_streams.yaml index c9e9f1be897aa..bb8c667ba933e 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/data_streams.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/data_streams.yaml @@ -1,6 +1,7 @@ get: - summary: Data streams - List - tags: [] + summary: List data streams + tags: + - Data streams responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/enrollment_api_keys.yaml b/x-pack/plugins/fleet/common/openapi/paths/enrollment_api_keys.yaml index 5bc257606087f..3351b63026e57 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/enrollment_api_keys.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/enrollment_api_keys.yaml @@ -1,6 +1,7 @@ get: - summary: Enrollment API Keys - List - tags: [] + summary: List enrollment API keys + tags: + - Enrollment API keys responses: '200': description: OK @@ -34,8 +35,9 @@ get: operationId: get-enrollment-api-keys parameters: [] post: - summary: Enrollment API Key - Create - tags: [] + summary: Create enrollment API key + tags: + - Enrollment API keys responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/enrollment_api_keys@{key_id}.yaml b/x-pack/plugins/fleet/common/openapi/paths/enrollment_api_keys@{key_id}.yaml index bdff9a7152e45..d64b1053f0dc4 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/enrollment_api_keys@{key_id}.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/enrollment_api_keys@{key_id}.yaml @@ -5,8 +5,9 @@ parameters: in: path required: true get: - summary: Enrollment API Key - Info - tags: [] + summary: Get enrollment API key by ID + tags: + - Enrollment API keys responses: '200': description: OK @@ -23,8 +24,9 @@ get: $ref: ../components/responses/error.yaml operationId: get-enrollment-api-key delete: - summary: Enrollment API Key - Delete - tags: [] + summary: Delete enrollment API key by ID + tags: + - Enrollment API keys responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/enrollment_api_keys@{key_id}_deprecated.yaml b/x-pack/plugins/fleet/common/openapi/paths/enrollment_api_keys@{key_id}_deprecated.yaml index 36c0f7f3ef01f..c0f5be7521e8a 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/enrollment_api_keys@{key_id}_deprecated.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/enrollment_api_keys@{key_id}_deprecated.yaml @@ -5,8 +5,9 @@ parameters: in: path required: true get: - summary: Enrollment API Key - Info - tags: [] + summary: Get enrollment API key by ID + tags: + - Enrollment API keys responses: '200': description: OK @@ -24,8 +25,9 @@ get: operationId: get-enrollment-api-key-deprecated deprecated: true delete: - summary: Enrollment API Key - Delete - tags: [] + summary: Delete enrollment API key by ID + tags: + - Enrollment API keys responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/enrollment_api_keys_deprecated.yaml b/x-pack/plugins/fleet/common/openapi/paths/enrollment_api_keys_deprecated.yaml index c5e378c563afc..19022a0b08223 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/enrollment_api_keys_deprecated.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/enrollment_api_keys_deprecated.yaml @@ -1,6 +1,7 @@ get: - summary: Enrollment API Keys - List - tags: [] + summary: List enrollment API keys + tags: + - Enrollment API keys responses: '200': description: OK @@ -35,8 +36,9 @@ get: parameters: [] deprecated: true post: - summary: Enrollment API Key - Create - tags: [] + summary: Create enrollment API key + tags: + - Enrollment API keys responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/epm@categories.yaml b/x-pack/plugins/fleet/common/openapi/paths/epm@categories.yaml index a97c06f66c629..e733f780abe04 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/epm@categories.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/epm@categories.yaml @@ -1,6 +1,7 @@ get: - summary: Package categories - tags: [] + summary: List package categories + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -12,21 +13,21 @@ get: $ref: ../components/responses/error.yaml operationId: get-package-categories parameters: - - in: query + - in: query name: prerelease schema: type: boolean default: false description: >- - Whether to include prerelease packages in categories count (e.g. beta, rc, preview) - - in: query + Whether to include prerelease packages in categories count (e.g. beta, rc, preview) + - in: query name: experimental deprecated: true schema: type: boolean default: false - - in: query + - in: query name: include_policy_templates schema: type: boolean - default: false \ No newline at end of file + default: false diff --git a/x-pack/plugins/fleet/common/openapi/paths/epm@get_file.yaml b/x-pack/plugins/fleet/common/openapi/paths/epm@get_file.yaml index 9c194ea6a8e97..b85b72938feda 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/epm@get_file.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/epm@get_file.yaml @@ -1,6 +1,7 @@ get: - summary: Packages - Get file from registry - tags: [] + summary: Get package file + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/epm@limited_list.yaml b/x-pack/plugins/fleet/common/openapi/paths/epm@limited_list.yaml index ec44a20b61307..a54c45782d3ae 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/epm@limited_list.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/epm@limited_list.yaml @@ -1,6 +1,7 @@ get: - summary: Packages - Get limited list - tags: [] + summary: Get limited package list + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/epm@packages.yaml b/x-pack/plugins/fleet/common/openapi/paths/epm@packages.yaml index 43819dff6d10e..7434fd2d324a5 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/epm@packages.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/epm@packages.yaml @@ -1,6 +1,7 @@ get: - summary: Packages - List - tags: [] + summary: List packages + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -22,26 +23,27 @@ get: caching for the response via `cache-control` headers. If you don't need up-to-date installation info for a package, and are querying for a list of available packages, providing this flag can improve performance substantially. - - in: query + - in: query name: prerelease schema: type: boolean default: false description: >- - Whether to return prerelease versions of packages (e.g. beta, rc, preview) - - in: query + Whether to return prerelease versions of packages (e.g. beta, rc, preview) + - in: query name: experimental deprecated: true schema: type: boolean default: false - - in: query + - in: query name: category schema: type: string post: - summary: Packages - Install by upload - tags: [] + summary: Install by package by direct upload + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -90,4 +92,4 @@ post: application/gzip: schema: type: string - format: binary \ No newline at end of file + format: binary diff --git a/x-pack/plugins/fleet/common/openapi/paths/epm@packages@{pkg_name}@stats.yaml b/x-pack/plugins/fleet/common/openapi/paths/epm@packages@{pkg_name}@stats.yaml index b27b4ef6729dc..f90a275cd19b6 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/epm@packages@{pkg_name}@stats.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/epm@packages@{pkg_name}@stats.yaml @@ -1,6 +1,7 @@ get: - summary: Get stats for a package - tags: [] + summary: Get package stats + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/epm@packages@{pkg_name}@{pkg_version}.yaml b/x-pack/plugins/fleet/common/openapi/paths/epm@packages@{pkg_name}@{pkg_version}.yaml index 4cc2e55e9d29e..4fff7f0b21504 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/epm@packages@{pkg_name}@{pkg_version}.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/epm@packages@{pkg_name}@{pkg_version}.yaml @@ -1,6 +1,7 @@ get: - summary: Packages - Info - tags: [] + summary: Get package + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -59,16 +60,17 @@ parameters: name: full description: 'Return all fields from the package manifest, not just those supported by the Elastic Package Registry' in: query - - in: query + - in: query name: prerelease schema: type: boolean default: false description: >- - Whether to return prerelease versions of packages (e.g. beta, rc, preview) + Whether to return prerelease versions of packages (e.g. beta, rc, preview) post: - summary: Packages - Install - tags: [] + summary: Install package + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -119,8 +121,9 @@ post: ignore_constraints: type: boolean put: - summary: Packages - Update - tags: [] + summary: Update package settings + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -158,8 +161,9 @@ put: keepPoliciesUpToDate: type: boolean delete: - summary: Packages - Delete - tags: [] + summary: Delete package + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/epm@packages@{pkgkey}_deprecated.yaml b/x-pack/plugins/fleet/common/openapi/paths/epm@packages@{pkgkey}_deprecated.yaml index 2967238a467a2..035809782bc07 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/epm@packages@{pkgkey}_deprecated.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/epm@packages@{pkgkey}_deprecated.yaml @@ -1,6 +1,7 @@ get: - summary: Packages - Info - tags: [] + summary: Get package + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -36,17 +37,18 @@ get: name: pkgkey in: path required: true - - in: query + - in: query name: prerelease schema: type: boolean default: false description: >- - Whether to return prerelease versions of packages (e.g. beta, rc, preview) + Whether to return prerelease versions of packages (e.g. beta, rc, preview) deprecated: true post: - summary: Packages - Install - tags: [] + summary: Install package + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -92,8 +94,9 @@ post: type: boolean deprecated: true delete: - summary: Packages - Delete - tags: [] + summary: Delete ackage + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/epm@packages_bulk.yaml b/x-pack/plugins/fleet/common/openapi/paths/epm@packages_bulk.yaml index 7ede68f6b545f..a3775e69a7131 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/epm@packages_bulk.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/epm@packages_bulk.yaml @@ -1,6 +1,7 @@ post: - summary: Packages - Bulk install - tags: [] + summary: Bulk install packages + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -12,13 +13,13 @@ post: $ref: ../components/responses/error.yaml operationId: bulk-install-packages parameters: - - in: query + - in: query name: prerelease schema: type: boolean default: false description: >- - Whether to return prerelease versions of packages (e.g. beta, rc, preview) + Whether to return prerelease versions of packages (e.g. beta, rc, preview) requestBody: content: application/json: @@ -32,7 +33,6 @@ post: description: list of package names to install force: type: boolean - description: force install to ignore package verification errors + description: force install to ignore package verification errors required: - packages - diff --git a/x-pack/plugins/fleet/common/openapi/paths/epm@verification_key_id.yaml b/x-pack/plugins/fleet/common/openapi/paths/epm@verification_key_id.yaml index c9216b85cd78f..24de03ab52cd8 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/epm@verification_key_id.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/epm@verification_key_id.yaml @@ -1,6 +1,7 @@ get: summary: Get package signature verification key ID - tags: [] + tags: + - Elastic Package Manager (EPM) responses: '200': description: OK @@ -12,7 +13,7 @@ get: body: type: object properties: - id: + id: type: string nullable: true description: the key ID of the GPG key used to verify package signatures @@ -23,19 +24,4 @@ get: '400': $ref: ../components/responses/error.yaml operationId: packages-get-verification-key-id -parameters: - - schema: - type: string - name: pkgName - in: path - required: true - - schema: - type: string - name: pkgVersion - in: path - required: true - - schema: - type: string - name: filePath - in: path - required: true +parameters: [] diff --git a/x-pack/plugins/fleet/common/openapi/paths/fleet_server_hosts.yaml b/x-pack/plugins/fleet/common/openapi/paths/fleet_server_hosts.yaml index da599dddca1e3..d7668f3683b7b 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/fleet_server_hosts.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/fleet_server_hosts.yaml @@ -1,7 +1,7 @@ get: - summary: Fleet Server Hosts - List - description: Return a list of Fleet server hosts - tags: [] + summary: List Fleet Server hosts + tags: + - Fleet Server hosts responses: '200': description: OK @@ -24,9 +24,9 @@ get: $ref: ../components/responses/error.yaml operationId: get-fleet-server-hosts post: - summary: Fleet Server Hosts - Create - description: 'Create a new Fleet Server Host' - tags: [] + summary: Create Fleet Server host + tags: + - Fleet Server hosts responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/fleet_server_hosts@{item_id}.yaml b/x-pack/plugins/fleet/common/openapi/paths/fleet_server_hosts@{item_id}.yaml index 968f81d8c6181..d46a8b86fb7f6 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/fleet_server_hosts@{item_id}.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/fleet_server_hosts@{item_id}.yaml @@ -1,6 +1,7 @@ get: - summary: Fleet Server Hosts - Info - tags: [] + summary: Get Fleet Server host by ID + tags: + - Fleet Server hosts responses: '200': description: OK @@ -23,7 +24,9 @@ parameters: in: path required: true delete: - summary: Fleet Server Hosts - Delete + summary: Delete Fleet Server host by ID + tags: + - Fleet Server hosts operationId: delete-fleet-server-hosts responses: '200': @@ -42,7 +45,9 @@ delete: parameters: - $ref: ../components/headers/kbn_xsrf.yaml put: - summary: Fleet Server Hosts - Update + summary: Update Fleet Server host by ID + tags: + - Fleet Server hosts operationId: update-fleet-server-hosts requestBody: content: diff --git a/x-pack/plugins/fleet/common/openapi/paths/health_check.yaml b/x-pack/plugins/fleet/common/openapi/paths/health_check.yaml index 84283ca80dbf0..ae87da3ff0e52 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/health_check.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/health_check.yaml @@ -1,6 +1,7 @@ post: - summary: Fleet Server Health Check - tags: [] + summary: Fleet Server health check + tags: + - Fleet internals responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/kubernetes.yaml b/x-pack/plugins/fleet/common/openapi/paths/kubernetes.yaml index d7852db70fccd..41110808cd62d 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/kubernetes.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/kubernetes.yaml @@ -1,6 +1,7 @@ get: - summary: Get K8s Full Agent Manifest - tags: [] + summary: Get full K8s agent manifest + tags: + - Kubernetes responses: '200': description: OK @@ -15,18 +16,18 @@ get: $ref: ../components/responses/error.yaml operationId: get-full-k8s-manifest parameters: - - schema: - type: boolean - name: download - in: query - required: false - - schema: - type: string - name: fleetServer - in: query - required: false - - schema: - type: string - name: enrolToken - in: query - required: false + - schema: + type: boolean + name: download + in: query + required: false + - schema: + type: string + name: fleetServer + in: query + required: false + - schema: + type: string + name: enrolToken + in: query + required: false diff --git a/x-pack/plugins/fleet/common/openapi/paths/logstash_api_keys.yaml b/x-pack/plugins/fleet/common/openapi/paths/logstash_api_keys.yaml index 495d792191798..74e3bdde4cac8 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/logstash_api_keys.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/logstash_api_keys.yaml @@ -1,6 +1,7 @@ post: summary: Generate Logstash API key - tags: [] + tags: + - Outputs responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/outputs.yaml b/x-pack/plugins/fleet/common/openapi/paths/outputs.yaml index 335d8ec570ca1..5ba06a5c36372 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/outputs.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/outputs.yaml @@ -1,6 +1,7 @@ get: - summary: Outputs - tags: [] + summary: List outputs + tags: + - Outputs responses: '200': description: OK @@ -23,9 +24,9 @@ get: $ref: ../components/responses/error.yaml operationId: get-outputs post: - summary: Outputs - description: 'Create a new output' - tags: [] + summary: Create output + tags: + - Outputs responses: '200': description: OK @@ -50,7 +51,7 @@ post: type: string type: type: string - enum: ["elasticsearch"] + enum: ['elasticsearch'] is_default: type: boolean is_default_monitoring: diff --git a/x-pack/plugins/fleet/common/openapi/paths/outputs@{output_id}.yaml b/x-pack/plugins/fleet/common/openapi/paths/outputs@{output_id}.yaml index ca01024288b95..8bd6ae2fc288c 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/outputs@{output_id}.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/outputs@{output_id}.yaml @@ -1,6 +1,7 @@ get: - summary: Output - Info - tags: [] + summary: Get output by ID + tags: + - Outputs responses: '200': description: OK @@ -23,7 +24,9 @@ parameters: in: path required: true delete: - summary: Output - Delete + summary: Delete output by ID + tags: + - Outputs operationId: delete-output responses: '200': @@ -33,7 +36,7 @@ delete: schema: type: object properties: - id: + id: type: string required: - id @@ -42,7 +45,9 @@ delete: parameters: - $ref: ../components/headers/kbn_xsrf.yaml put: - summary: Output - Update + summary: Update output by ID + tags: + - Outputs operationId: update-output requestBody: content: @@ -54,14 +59,14 @@ put: type: string type: type: string - enum: ["elasticsearch"] + enum: ['elasticsearch'] is_default: type: boolean is_default_monitoring: type: boolean hosts: type: array - items: + items: type: string ca_sha256: type: string 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 8959339426d05..0fe987e1727de 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/package_policies.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/package_policies.yaml @@ -1,6 +1,7 @@ get: - summary: Package policies - List - tags: [] + summary: List package policies + tags: + - Package policies responses: '200': description: OK @@ -28,7 +29,9 @@ get: parameters: [] parameters: [] post: - summary: Package policy - Create + summary: Create package policy + tags: + - Package policies operationId: create-package-policy responses: '200': diff --git a/x-pack/plugins/fleet/common/openapi/paths/package_policies@_bulk_get.yaml b/x-pack/plugins/fleet/common/openapi/paths/package_policies@_bulk_get.yaml index 1ff515dc2de6a..eb22c7d997575 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/package_policies@_bulk_get.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/package_policies@_bulk_get.yaml @@ -1,6 +1,7 @@ post: - summary: Package policies - Bulk Get - tags: [] + summary: Bulk get package policies + tags: + - Package policies requestBody: content: application/json: diff --git a/x-pack/plugins/fleet/common/openapi/paths/package_policies@delete.yaml b/x-pack/plugins/fleet/common/openapi/paths/package_policies@delete.yaml index 6061267b4b2b8..f21111c23757a 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/package_policies@delete.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/package_policies@delete.yaml @@ -1,5 +1,7 @@ post: - summary: Package policy - Delete + summary: Delete package policy + tags: + - Package policies operationId: post-delete-package-policy requestBody: content: diff --git a/x-pack/plugins/fleet/common/openapi/paths/package_policies@upgrade.yaml b/x-pack/plugins/fleet/common/openapi/paths/package_policies@upgrade.yaml index 09f2727ad678c..2b6e69d49c44e 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/package_policies@upgrade.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/package_policies@upgrade.yaml @@ -1,5 +1,7 @@ post: - summary: Package policy - Upgrade + summary: Upgrade package policy to a newer package version + tags: + - Package policies operationId: upgrade-package-policy requestBody: content: diff --git a/x-pack/plugins/fleet/common/openapi/paths/package_policies@upgrade_dryrun.yaml b/x-pack/plugins/fleet/common/openapi/paths/package_policies@upgrade_dryrun.yaml index 6f51bd6812d97..5019aba15898d 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/package_policies@upgrade_dryrun.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/package_policies@upgrade_dryrun.yaml @@ -1,5 +1,7 @@ post: - summary: Package policy - Upgrade Dry run + summary: Dry run package policy upgrade + tags: + - Package policies operationId: upgrade-package-policy-dry-run requestBody: content: 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 9e05e9516d603..30d89d271a5ff 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 @@ -1,6 +1,7 @@ get: - summary: Package policy - Info - tags: [] + summary: Get package policy by ID + tags: + - Package policies responses: '200': description: OK @@ -23,7 +24,9 @@ parameters: in: path required: true put: - summary: Package policy - Update + summary: Update package policy by ID + tags: + - Package policies operationId: update-package-policy requestBody: content: @@ -50,8 +53,9 @@ put: parameters: - $ref: ../components/headers/kbn_xsrf.yaml delete: - summary: Package policy - Delete - tags: [] + summary: Delete package policy by ID + tags: + - Package policies operationId: delete-package-policy responses: '200': diff --git a/x-pack/plugins/fleet/common/openapi/paths/proxies.yaml b/x-pack/plugins/fleet/common/openapi/paths/proxies.yaml index a5f59ec0e7cb5..6c2844a9ac3ef 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/proxies.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/proxies.yaml @@ -1,7 +1,7 @@ get: - summary: Fleet Proxies - List - description: Return a list of Proxies - tags: [] + summary: List proxies + tags: + - Proxies responses: '200': description: OK @@ -24,9 +24,9 @@ get: $ref: ../components/responses/error.yaml operationId: get-fleet-proxies post: - summary: Fleet Proxies - Create - description: 'Create a new Fleet Server Host' - tags: [] + summary: Create proxy + tags: + - Proxies responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/proxies@{item_id}.yaml b/x-pack/plugins/fleet/common/openapi/paths/proxies@{item_id}.yaml index 96a3665718753..3a0a10cb35662 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/proxies@{item_id}.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/proxies@{item_id}.yaml @@ -1,6 +1,7 @@ get: - summary: Fleet Proxies - Info - tags: [] + summary: Get proxy by ID + tags: + - Proxies responses: '200': description: OK @@ -23,7 +24,9 @@ parameters: in: path required: true delete: - summary: Fleet Proxies - Delete + summary: Delete proxy by ID + tags: + - Proxies operationId: delete-fleet-proxies responses: '200': @@ -42,7 +45,9 @@ delete: parameters: - $ref: ../components/headers/kbn_xsrf.yaml put: - summary: Fleet Proxies - Update + summary: Update proxy by ID + tags: + - Proxies operationId: update-fleet-proxies requestBody: content: diff --git a/x-pack/plugins/fleet/common/openapi/paths/service_tokens.yaml b/x-pack/plugins/fleet/common/openapi/paths/service_tokens.yaml index c57614c6c5def..e76f18c5b57d7 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/service_tokens.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/service_tokens.yaml @@ -1,6 +1,7 @@ post: - summary: Generate service tokens - tags: [] + summary: Create service token + tags: + - Service tokens responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/service_tokens_deprecated.yaml b/x-pack/plugins/fleet/common/openapi/paths/service_tokens_deprecated.yaml index f081f207b4d1e..73069830be9e1 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/service_tokens_deprecated.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/service_tokens_deprecated.yaml @@ -1,6 +1,7 @@ post: - summary: Generate service tokens - tags: [] + summary: Create service token + tags: + - Service tokens responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/openapi/paths/settings.yaml b/x-pack/plugins/fleet/common/openapi/paths/settings.yaml index dc711bcefbfae..4e3d1b3af4bb7 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/settings.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/settings.yaml @@ -1,6 +1,7 @@ get: - summary: Settings - tags: [] + summary: Get settings + tags: + - Fleet internals responses: '200': description: OK @@ -12,8 +13,9 @@ get: $ref: ../components/responses/error.yaml operationId: get-settings put: - summary: Settings - Update - tags: [] + summary: Update settings + tags: + - Fleet internals requestBody: content: application/json: diff --git a/x-pack/plugins/fleet/common/openapi/paths/setup.yaml b/x-pack/plugins/fleet/common/openapi/paths/setup.yaml index 048e5cc51faf9..1f1a3cd035665 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/setup.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/setup.yaml @@ -1,6 +1,7 @@ post: - summary: Setup - tags: [] + summary: Initiate Fleet setup + tags: + - Fleet internals responses: '200': description: OK diff --git a/x-pack/plugins/fleet/common/types/rest_spec/agent.ts b/x-pack/plugins/fleet/common/types/rest_spec/agent.ts index 7a73829838d55..603eeed136059 100644 --- a/x-pack/plugins/fleet/common/types/rest_spec/agent.ts +++ b/x-pack/plugins/fleet/common/types/rest_spec/agent.ts @@ -119,15 +119,25 @@ export type PostBulkAgentUpgradeResponse = BulkAgentAction; // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface PostAgentUpgradeResponse {} +// deprecated export interface PutAgentReassignRequest { params: { agentId: string; }; body: { policy_id: string }; } - +// deprecated // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface PutAgentReassignResponse {} +export interface PostAgentReassignRequest { + params: { + agentId: string; + }; + body: { policy_id: string }; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface PostAgentReassignResponse {} export interface PostBulkAgentReassignRequest { body: { diff --git a/x-pack/plugins/fleet/kibana.jsonc b/x-pack/plugins/fleet/kibana.jsonc index bf9db5435be82..4a8076fd09836 100644 --- a/x-pack/plugins/fleet/kibana.jsonc +++ b/x-pack/plugins/fleet/kibana.jsonc @@ -17,7 +17,6 @@ "navigation", "customIntegrations", "share", - "spaces", "security", "unifiedSearch", "savedObjectsTagging", @@ -33,7 +32,8 @@ "globalSearch", "telemetry", "discover", - "ingestPipelines" + "ingestPipelines", + "spaces", ], "requiredBundles": [ "kibanaReact", diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/advanced_tab.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/advanced_tab.tsx index 79582e94a6226..b9135b3dc716b 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/advanced_tab.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/advanced_tab.tsx @@ -5,7 +5,7 @@ * 2.0. */ import React from 'react'; -import { EuiLoadingContent, EuiSteps } from '@elastic/eui'; +import { EuiSkeletonText, EuiSteps } from '@elastic/eui'; import { useAdvancedForm } from './hooks'; import { useLatestFleetServers } from './hooks/use_latest_fleet_servers'; @@ -83,7 +83,7 @@ export const AdvancedTab: React.FunctionComponent = ({ ]; return isSelectFleetServerPolicyLoading ? ( - + ) : ( ); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/confirm_incoming_data_with_preview.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/confirm_incoming_data_with_preview.tsx index 772d90ada7dc1..5e6443986c7c6 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/confirm_incoming_data_with_preview.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/confirm_incoming_data_with_preview.tsx @@ -11,13 +11,13 @@ import { EuiText, EuiSpacer, EuiLink, - EuiLoadingContent, EuiLoadingSpinner, EuiHorizontalRule, EuiFlexGroup, EuiFlexItem, formatDate, EuiDescriptionList, + EuiSkeletonText, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; @@ -188,7 +188,7 @@ export const ConfirmIncomingDataWithPreview: React.FunctionComponent = ({ )} - + ); } diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_overview.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_overview.tsx index 5f0b64051f26f..4459b8f762382 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_overview.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_overview.tsx @@ -15,8 +15,8 @@ import { EuiFlexItem, EuiPanel, EuiIcon, + EuiSkeletonText, EuiToolTip, - EuiLoadingContent, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage, FormattedRelative } from '@kbn/i18n-react'; @@ -52,15 +52,47 @@ export const AgentDetailsOverviewSection: React.FunctionComponent<{ {[ { - title: i18n.translate('xpack.fleet.agentDetails.cpuLabel', { - defaultMessage: 'CPU', - }), + title: ( + + } + > + + +   + + + + ), description: formatAgentCPU(agent.metrics, agentPolicy), }, { - title: i18n.translate('xpack.fleet.agentDetails.memoryLabel', { - defaultMessage: 'Memory', - }), + title: ( + + } + > + + +   + + + + ), description: formatAgentMemory(agent.metrics, agentPolicy), }, ].map(({ title, description }) => { @@ -128,7 +160,7 @@ export const AgentDetailsOverviewSection: React.FunctionComponent<{ description: agentPolicy ? ( ) : ( - + ), }, { @@ -213,7 +245,7 @@ export const AgentDetailsOverviewSection: React.FunctionComponent<{ /> ) ) : ( - + ), }, { @@ -233,7 +265,7 @@ export const AgentDetailsOverviewSection: React.FunctionComponent<{ /> ) ) : ( - + ), }, { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_diagnostics/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_diagnostics/index.tsx index 180a1b894ca13..7ec0de9023e32 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_diagnostics/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_diagnostics/index.tsx @@ -15,9 +15,9 @@ import { EuiFlexItem, EuiIcon, EuiLink, - EuiLoadingContent, EuiLoadingSpinner, EuiText, + EuiSkeletonText, formatDate, } from '@elastic/eui'; import React, { useCallback, useEffect, useState } from 'react'; @@ -272,7 +272,7 @@ export const AgentDiagnosticsTab: React.FunctionComponent {isLoading ? ( - + ) : ( items={diagnosticsEntries} columns={columns} /> )} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_reassign_policy_modal/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_reassign_policy_modal/index.tsx index cb3f0d77eed34..5fd6183863fb6 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_reassign_policy_modal/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_reassign_policy_modal/index.tsx @@ -19,7 +19,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import type { Agent } from '../../../../types'; import { - sendPutAgentReassign, + sendPostAgentReassign, sendPostBulkAgentReassign, useStartServices, useGetAgentPolicies, @@ -71,7 +71,7 @@ export const AgentReassignAgentPolicyModal: React.FunctionComponent = ({ throw new Error('No selected agent policy id'); } const res = isSingleAgent - ? await sendPutAgentReassign((agents[0] as Agent).id, { + ? await sendPostAgentReassign((agents[0] as Agent).id, { policy_id: selectedAgentPolicyId, }) : await sendPostBulkAgentReassign({ diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/index.tsx index 67ea0781bafc2..ffbc34ab2c55e 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/index.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/index.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { Switch } from 'react-router-dom'; import { Route } from '@kbn/shared-ux-router'; -import { EuiLoadingContent } from '@elastic/eui'; +import { EuiSkeletonText } from '@elastic/eui'; import { INTEGRATIONS_ROUTING_PATHS } from '../../constants'; import { IntegrationsStateContextProvider, useBreadcrumbs } from '../../hooks'; @@ -34,7 +34,7 @@ export const EPMApp: React.FunctionComponent = () => { - }> + }> diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/notice_modal.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/notice_modal.tsx index 4ef6dde3fb2f6..a4ab3c19200bc 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/notice_modal.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/notice_modal.tsx @@ -8,13 +8,13 @@ import React, { useEffect, useState } from 'react'; import { EuiCodeBlock, - EuiLoadingContent, EuiModal, EuiModalBody, EuiModalHeader, EuiModalFooter, EuiModalHeaderTitle, EuiButton, + EuiSkeletonText, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -58,10 +58,10 @@ export const NoticeModal: React.FunctionComponent = ({ noticePath, onClos // Simulate a long notice while loading <>

      - +

      - +

      )} diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/readme.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/readme.tsx index 582bbb6dfd31e..52b4f8fbe4fdc 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/readme.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/readme.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiLoadingContent, EuiText } from '@elastic/eui'; +import { EuiText, EuiSkeletonText } from '@elastic/eui'; import React, { Fragment, useEffect, useState } from 'react'; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; @@ -57,13 +57,13 @@ export function Readme({ {/* simulates a long page of text loading */}

      - +

      - +

      - +

      )} diff --git a/x-pack/plugins/fleet/public/hooks/use_request/agents.ts b/x-pack/plugins/fleet/public/hooks/use_request/agents.ts index ca53345bb5799..4a23950b0bf56 100644 --- a/x-pack/plugins/fleet/public/hooks/use_request/agents.ts +++ b/x-pack/plugins/fleet/public/hooks/use_request/agents.ts @@ -26,8 +26,8 @@ import type { PostBulkAgentUnenrollRequest, PostBulkAgentUnenrollResponse, PostAgentUnenrollResponse, - PutAgentReassignRequest, - PutAgentReassignResponse, + PostAgentReassignRequest, + PostAgentReassignResponse, PostBulkAgentReassignRequest, PostBulkAgentReassignResponse, GetAgentsRequest, @@ -126,13 +126,13 @@ export function sendGetAgentTags(query: GetAgentsRequest['query'], options?: Req }); } -export function sendPutAgentReassign( +export function sendPostAgentReassign( agentId: string, - body: PutAgentReassignRequest['body'], + body: PostAgentReassignRequest['body'], options?: RequestOptions ) { - return sendRequest({ - method: 'put', + return sendRequest({ + method: 'post', path: agentRouteService.getReassignPath(agentId), body, ...options, diff --git a/x-pack/plugins/fleet/public/types/index.ts b/x-pack/plugins/fleet/public/types/index.ts index 530b8701ab9f4..8ce5cc30cde9e 100644 --- a/x-pack/plugins/fleet/public/types/index.ts +++ b/x-pack/plugins/fleet/public/types/index.ts @@ -69,8 +69,8 @@ export type { GetAgentIncomingDataRequest, IncomingDataList, GetAgentIncomingDataResponse, - PutAgentReassignRequest, - PutAgentReassignResponse, + PostAgentReassignRequest, + PostAgentReassignResponse, PostBulkAgentReassignRequest, PostBulkAgentReassignResponse, PostNewAgentActionResponse, diff --git a/x-pack/plugins/fleet/server/mocks/index.ts b/x-pack/plugins/fleet/server/mocks/index.ts index 4c1d37e3d0f52..8e228f445977e 100644 --- a/x-pack/plugins/fleet/server/mocks/index.ts +++ b/x-pack/plugins/fleet/server/mocks/index.ts @@ -75,12 +75,7 @@ export const createAppContextStartContractMock = ( kibanaBranch: 'main', telemetryEventsSender: createMockTelemetryEventsSender(), bulkActionsResolver: {} as any, - messageSigningService: { - isEncryptionAvailable: true, - generateKeyPair: jest.fn(), - sign: jest.fn(), - getPublicKey: jest.fn(), - }, + messageSigningService: createMessageSigningServiceMock(), }; }; @@ -165,3 +160,12 @@ export const createMockAgentClient = () => agentServiceMock.createClient(); * Creates a mock PackageService */ export const createMockPackageService = () => packageServiceMock.create(); + +export function createMessageSigningServiceMock() { + return { + isEncryptionAvailable: true, + generateKeyPair: jest.fn(), + sign: jest.fn(), + getPublicKey: jest.fn(), + }; +} diff --git a/x-pack/plugins/fleet/server/plugin.ts b/x-pack/plugins/fleet/server/plugin.ts index 265431a87e265..4e9a7f19b1818 100644 --- a/x-pack/plugins/fleet/server/plugin.ts +++ b/x-pack/plugins/fleet/server/plugin.ts @@ -8,7 +8,7 @@ import type { Observable } from 'rxjs'; import { BehaviorSubject } from 'rxjs'; import { take, filter } from 'rxjs/operators'; - +import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common'; import { i18n } from '@kbn/i18n'; import type { CoreSetup, @@ -118,7 +118,7 @@ export interface FleetSetupDeps { encryptedSavedObjects: EncryptedSavedObjectsPluginSetup; cloud?: CloudSetup; usageCollection?: UsageCollectionSetup; - spaces: SpacesPluginStart; + spaces?: SpacesPluginStart; telemetry?: TelemetryPluginSetup; taskManager: TaskManagerSetupContract; } @@ -386,7 +386,7 @@ export class FleetPlugin return getInternalSoClient(); }, get spaceId() { - return deps.spaces.spacesService.getSpaceId(request); + return deps.spaces?.spacesService?.getSpaceId(request) ?? DEFAULT_SPACE_ID; }, get limitedToPackages() { diff --git a/x-pack/plugins/fleet/server/routes/agent/handlers.ts b/x-pack/plugins/fleet/server/routes/agent/handlers.ts index 24dd0d355411d..b001b27d15b27 100644 --- a/x-pack/plugins/fleet/server/routes/agent/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/agent/handlers.ts @@ -29,6 +29,7 @@ import type { GetAvailableVersionsResponse, GetActionStatusResponse, GetAgentUploadsResponse, + PostAgentReassignResponse, } from '../../../common/types'; import type { GetAgentsRequestSchema, @@ -38,7 +39,8 @@ import type { DeleteAgentRequestSchema, GetAgentStatusRequestSchema, GetAgentDataRequestSchema, - PutAgentReassignRequestSchema, + PutAgentReassignRequestSchemaDeprecated, + PostAgentReassignRequestSchema, PostBulkAgentReassignRequestSchema, PostBulkUpdateAgentTagsRequestSchema, GetActionStatusRequestSchema, @@ -234,10 +236,10 @@ export const getAgentTagsHandler: RequestHandler< } }; -export const putAgentsReassignHandler: RequestHandler< - TypeOf, +export const putAgentsReassignHandlerDeprecated: RequestHandler< + TypeOf, undefined, - TypeOf + TypeOf > = async (context, request, response) => { const coreContext = await context.core; const soClient = coreContext.savedObjects.client; @@ -257,6 +259,29 @@ export const putAgentsReassignHandler: RequestHandler< } }; +export const postAgentsReassignHandler: RequestHandler< + TypeOf, + undefined, + TypeOf +> = async (context, request, response) => { + const coreContext = await context.core; + const soClient = coreContext.savedObjects.client; + const esClient = coreContext.elasticsearch.client.asInternalUser; + try { + await AgentService.reassignAgent( + soClient, + esClient, + request.params.agentId, + request.body.policy_id + ); + + const body: PostAgentReassignResponse = {}; + return response.ok({ body }); + } catch (error) { + return defaultFleetErrorHandler({ error, response }); + } +}; + export const postBulkAgentsReassignHandler: RequestHandler< undefined, undefined, diff --git a/x-pack/plugins/fleet/server/routes/agent/index.ts b/x-pack/plugins/fleet/server/routes/agent/index.ts index d7eb3657cbfd6..9e5204e64ac26 100644 --- a/x-pack/plugins/fleet/server/routes/agent/index.ts +++ b/x-pack/plugins/fleet/server/routes/agent/index.ts @@ -21,7 +21,8 @@ import { GetAgentStatusRequestSchema, GetAgentDataRequestSchema, PostNewAgentActionRequestSchema, - PutAgentReassignRequestSchema, + PutAgentReassignRequestSchemaDeprecated, + PostAgentReassignRequestSchema, PostBulkAgentReassignRequestSchema, PostAgentUpgradeRequestSchema, PostBulkAgentUpgradeRequestSchema, @@ -46,7 +47,7 @@ import { updateAgentHandler, deleteAgentHandler, getAgentStatusForAgentPolicyHandler, - putAgentsReassignHandler, + putAgentsReassignHandlerDeprecated, postBulkAgentsReassignHandler, getAgentDataHandler, bulkUpdateAgentTagsHandler, @@ -54,6 +55,7 @@ import { getActionStatusHandler, getAgentUploadsHandler, getAgentUploadFileHandler, + postAgentsReassignHandler, } from './handlers'; import { postNewAgentActionHandlerBuilder, @@ -178,15 +180,27 @@ export const registerAPIRoutes = (router: FleetAuthzRouter, config: FleetConfigT postAgentUnenrollHandler ); + // mark as deprecated router.put( { path: AGENT_API_ROUTES.REASSIGN_PATTERN, - validate: PutAgentReassignRequestSchema, + validate: PutAgentReassignRequestSchemaDeprecated, fleetAuthz: { fleet: { all: true }, }, }, - putAgentsReassignHandler + putAgentsReassignHandlerDeprecated + ); + + router.post( + { + path: AGENT_API_ROUTES.REASSIGN_PATTERN, + validate: PostAgentReassignRequestSchema, + fleetAuthz: { + fleet: { all: true }, + }, + }, + postAgentsReassignHandler ); router.post( diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/retry.test.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/retry.test.ts index 51a516e68ad6d..5947401917245 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/retry.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/retry.test.ts @@ -85,4 +85,13 @@ describe('retryTransientErrors', () => { await expect(retryTransientEsErrors(esCallMock)).rejects.toThrow(error); expect(esCallMock).toHaveBeenCalledTimes(1); }); + + it('retries with additionalResponseStatuses', async () => { + const error = new EsErrors.ResponseError({ statusCode: 123, meta: {} as any, warnings: [] }); + const esCallMock = jest.fn().mockRejectedValueOnce(error).mockResolvedValue('success'); + expect(await retryTransientEsErrors(esCallMock, { additionalResponseStatuses: [123] })).toEqual( + 'success' + ); + expect(esCallMock).toHaveBeenCalledTimes(2); + }); }); diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/retry.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/retry.ts index c8ea36a4addec..773ed7273d885 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/retry.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/retry.ts @@ -17,11 +17,12 @@ const retryResponseStatuses = [ 410, // Gone ]; -const isRetryableError = (e: any) => +const isRetryableError = (e: any, additionalResponseStatuses: number[] = []) => e instanceof EsErrors.NoLivingConnectionsError || e instanceof EsErrors.ConnectionError || e instanceof EsErrors.TimeoutError || - (e instanceof EsErrors.ResponseError && retryResponseStatuses.includes(e?.statusCode!)); + (e instanceof EsErrors.ResponseError && + [...retryResponseStatuses, ...additionalResponseStatuses].includes(e?.statusCode!)); /** * Retries any transient network or configuration issues encountered from Elasticsearch with an exponential backoff. @@ -29,12 +30,16 @@ const isRetryableError = (e: any) => */ export const retryTransientEsErrors = async ( esCall: () => Promise, - { logger, attempt = 0 }: { logger?: Logger; attempt?: number } = {} + { + logger, + attempt = 0, + additionalResponseStatuses = [], + }: { logger?: Logger; attempt?: number; additionalResponseStatuses?: number[] } = {} ): Promise => { try { return await esCall(); } catch (e) { - if (attempt < MAX_ATTEMPTS && isRetryableError(e)) { + if (attempt < MAX_ATTEMPTS && isRetryableError(e, additionalResponseStatuses)) { const retryCount = attempt + 1; const retryDelaySec = Math.min(Math.pow(2, retryCount), 64); // 2s, 4s, 8s, 16s, 32s, 64s, 64s, 64s ... diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/install.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/install.ts index e8881bb247e12..dc775d6f52e01 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/install.ts @@ -703,9 +703,13 @@ async function handleTransformInstall({ // start transform by default if not set in yml file // else, respect the setting if (startTransform === undefined || startTransform === true) { - await esClient.transform.startTransform( - { transform_id: transform.installationName }, - { ignore: [409] } + await retryTransientEsErrors( + () => + esClient.transform.startTransform( + { transform_id: transform.installationName }, + { ignore: [409] } + ), + { logger, additionalResponseStatuses: [400] } ); logger.debug(`Started transform: ${transform.installationName}`); } diff --git a/x-pack/plugins/fleet/server/services/epm/package_service.mock.ts b/x-pack/plugins/fleet/server/services/epm/package_service.mock.ts index 806f295ec2c4e..69e1217b0493c 100644 --- a/x-pack/plugins/fleet/server/services/epm/package_service.mock.ts +++ b/x-pack/plugins/fleet/server/services/epm/package_service.mock.ts @@ -12,6 +12,7 @@ const createClientMock = (): jest.Mocked => ({ ensureInstalledPackage: jest.fn(), fetchFindLatestPackage: jest.fn(), getPackage: jest.fn(), + getPackages: jest.fn(), reinstallEsAssets: jest.fn(), }); diff --git a/x-pack/plugins/fleet/server/services/epm/package_service.ts b/x-pack/plugins/fleet/server/services/epm/package_service.ts index dfc02c4f68c57..b0dd6e9dc38b4 100644 --- a/x-pack/plugins/fleet/server/services/epm/package_service.ts +++ b/x-pack/plugins/fleet/server/services/epm/package_service.ts @@ -14,7 +14,10 @@ import type { Logger, } from '@kbn/core/server'; +import type { PackageList } from '../../../common'; + import type { + CategoryId, EsAssetReference, InstallablePackage, Installation, @@ -28,7 +31,7 @@ import { FleetUnauthorizedError } from '../../errors'; import { installTransforms, isTransform } from './elasticsearch/transform/install'; import type { FetchFindLatestPackageOptions } from './registry'; import { fetchFindLatestPackageOrThrow, getPackage } from './registry'; -import { ensureInstalledPackage, getInstallation } from './packages'; +import { ensureInstalledPackage, getInstallation, getPackages } from './packages'; export type InstalledAssetType = EsAssetReference; @@ -56,6 +59,12 @@ export interface PackageClient { packageVersion: string ): Promise<{ packageInfo: ArchivePackage; paths: string[] }>; + getPackages(params?: { + excludeInstallStatus?: false; + category?: CategoryId; + prerelease?: false; + }): Promise; + reinstallEsAssets( packageInfo: InstallablePackage, assetPaths: string[] @@ -137,6 +146,21 @@ class PackageClientImpl implements PackageClient { return getPackage(packageName, packageVersion, options); } + public async getPackages(params?: { + excludeInstallStatus?: false; + category?: CategoryId; + prerelease?: false; + }) { + const { excludeInstallStatus, category, prerelease } = params || {}; + await this.#runPreflight(); + return getPackages({ + savedObjectsClient: this.internalSoClient, + excludeInstallStatus, + category, + prerelease, + }); + } + public async reinstallEsAssets( packageInfo: InstallablePackage, assetPaths: string[] diff --git a/x-pack/plugins/fleet/server/types/rest_spec/agent.ts b/x-pack/plugins/fleet/server/types/rest_spec/agent.ts index 92b0f098ae19d..598ea0fa67fe1 100644 --- a/x-pack/plugins/fleet/server/types/rest_spec/agent.ts +++ b/x-pack/plugins/fleet/server/types/rest_spec/agent.ts @@ -120,7 +120,16 @@ export const PostBulkAgentUpgradeRequestSchema = { }), }; -export const PutAgentReassignRequestSchema = { +export const PutAgentReassignRequestSchemaDeprecated = { + params: schema.object({ + agentId: schema.string(), + }), + body: schema.object({ + policy_id: schema.string(), + }), +}; + +export const PostAgentReassignRequestSchema = { params: schema.object({ agentId: schema.string(), }), diff --git a/x-pack/plugins/graph/kibana.jsonc b/x-pack/plugins/graph/kibana.jsonc index c47a12d71cc92..5c85742b492a2 100644 --- a/x-pack/plugins/graph/kibana.jsonc +++ b/x-pack/plugins/graph/kibana.jsonc @@ -16,7 +16,9 @@ "navigation", "savedObjects", "unifiedSearch", - "inspector" + "inspector", + "savedObjectsManagement", + "savedObjectsFinder", ], "optionalPlugins": [ "home", diff --git a/x-pack/plugins/graph/public/application.tsx b/x-pack/plugins/graph/public/application.tsx index bbb14a96ac5eb..90cbc2b88b19f 100644 --- a/x-pack/plugins/graph/public/application.tsx +++ b/x-pack/plugins/graph/public/application.tsx @@ -35,6 +35,7 @@ import('./font_awesome'); import { SavedObjectsStart } from '@kbn/saved-objects-plugin/public'; import { SpacesApi } from '@kbn/spaces-plugin/public'; import { KibanaThemeProvider, toMountPoint } from '@kbn/kibana-react-plugin/public'; +import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public'; import { GraphSavePolicy } from './types'; import { graphRouter } from './router'; import { checkLicense } from '../common/check_license'; @@ -72,6 +73,7 @@ export interface GraphDependencies { history: ScopedHistory; spaces?: SpacesApi; inspect: InspectorPublicPluginStart; + savedObjectsManagement: SavedObjectsManagementPluginStart; } export type GraphServices = Omit; diff --git a/x-pack/plugins/graph/public/apps/workspace_route.tsx b/x-pack/plugins/graph/public/apps/workspace_route.tsx index 75aafeb1a7868..8fcff177d054b 100644 --- a/x-pack/plugins/graph/public/apps/workspace_route.tsx +++ b/x-pack/plugins/graph/public/apps/workspace_route.tsx @@ -43,6 +43,7 @@ export const WorkspaceRoute = ({ spaces, indexPatterns: getIndexPatternProvider, inspect, + savedObjectsManagement, }, }: WorkspaceRouteProps) => { /** @@ -70,9 +71,10 @@ export const WorkspaceRoute = ({ storage, data, unifiedSearch, + savedObjectsManagement, ...coreStart, }), - [coreStart, data, storage, unifiedSearch] + [coreStart, data, storage, unifiedSearch, savedObjectsManagement] ); const { loading, requestAdapter, callNodeProxy, callSearchNodeProxy, handleSearchQueryError } = diff --git a/x-pack/plugins/graph/public/components/guidance_panel/guidance_panel.tsx b/x-pack/plugins/graph/public/components/guidance_panel/guidance_panel.tsx index 81be2e191516a..6b6c06dbc02ba 100644 --- a/x-pack/plugins/graph/public/components/guidance_panel/guidance_panel.tsx +++ b/x-pack/plugins/graph/public/components/guidance_panel/guidance_panel.tsx @@ -77,7 +77,7 @@ function GuidancePanelComponent(props: GuidancePanelProps) { const kibana = useKibana(); const { services, overlays } = kibana; - const { http, uiSettings, application, data } = services; + const { http, uiSettings, application, data, savedObjectsManagement } = services; const [hasDataViews, setHasDataViews] = useState(true); useEffect(() => { @@ -90,7 +90,7 @@ function GuidancePanelComponent(props: GuidancePanelProps) { if (!overlays || !application) return null; const onOpenDatasourcePicker = () => { - openSourceModal({ overlays, http, uiSettings }, onIndexPatternSelected); + openSourceModal({ overlays, http, uiSettings, savedObjectsManagement }, onIndexPatternSelected); }; let content = ( diff --git a/x-pack/plugins/graph/public/components/search_bar.test.tsx b/x-pack/plugins/graph/public/components/search_bar.test.tsx index ca21e16c0fb36..e1ee8cb9d6331 100644 --- a/x-pack/plugins/graph/public/components/search_bar.test.tsx +++ b/x-pack/plugins/graph/public/components/search_bar.test.tsx @@ -29,6 +29,7 @@ import { GraphStore, setDatasource, submitSearchSaga } from '../state_management import { ReactWrapper } from 'enzyme'; import { createMockGraphStore } from '../state_management/mocks'; import { Provider } from 'react-redux'; +import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public'; jest.mock('../services/source_modal', () => ({ openSourceModal: jest.fn() })); @@ -42,6 +43,7 @@ function getServiceMocks() { }, } as IUiSettingsClient, savedObjects: {} as SavedObjectsStart, + savedObjectsManagement: {} as SavedObjectsManagementPluginStart, notifications: {} as NotificationsStart, docLinks: { links: { diff --git a/x-pack/plugins/graph/public/components/search_bar.tsx b/x-pack/plugins/graph/public/components/search_bar.tsx index fcd5d576116d9..5bf23c1705dec 100644 --- a/x-pack/plugins/graph/public/components/search_bar.tsx +++ b/x-pack/plugins/graph/public/components/search_bar.tsx @@ -107,9 +107,9 @@ export function SearchBarComponent(props: SearchBarStateProps & SearchBarProps) notifications, http, docLinks, + savedObjectsManagement, } = services; if (!overlays) return null; - return (
      { @@ -131,7 +131,11 @@ export function SearchBarComponent(props: SearchBarStateProps & SearchBarProps) data-test-subj="graphDatasourceButton" onClick={() => { confirmWipeWorkspace( - () => openSourceModal({ overlays, http, uiSettings }, onIndexPatternSelected), + () => + openSourceModal( + { overlays, http, uiSettings, savedObjectsManagement }, + onIndexPatternSelected + ), i18n.translate('xpack.graph.clearWorkspace.confirmText', { defaultMessage: 'If you change data sources, your current fields and vertices will be reset.', diff --git a/x-pack/plugins/graph/public/components/source_picker.tsx b/x-pack/plugins/graph/public/components/source_picker.tsx index ceb165fd50840..ec227fdddc5bd 100644 --- a/x-pack/plugins/graph/public/components/source_picker.tsx +++ b/x-pack/plugins/graph/public/components/source_picker.tsx @@ -9,22 +9,28 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { CoreStart } from '@kbn/core/public'; -import { SavedObjectFinderUi } from '@kbn/saved-objects-plugin/public'; +import { SavedObjectFinder } from '@kbn/saved-objects-finder-plugin/public'; +import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public'; import { IndexPatternSavedObject } from '../types'; export interface SourcePickerProps { onIndexPatternSelected: (indexPattern: IndexPatternSavedObject) => void; http: CoreStart['http']; uiSettings: CoreStart['uiSettings']; + savedObjectsManagement: SavedObjectsManagementPluginStart; } const fixedPageSize = 8; -export function SourcePicker({ http, uiSettings, onIndexPatternSelected }: SourcePickerProps) { +export function SourcePicker({ + http, + uiSettings, + savedObjectsManagement, + onIndexPatternSelected, +}: SourcePickerProps) { return ( - { onIndexPatternSelected(indexPattern as IndexPatternSavedObject); }} diff --git a/x-pack/plugins/graph/public/plugin.ts b/x-pack/plugins/graph/public/plugin.ts index 96dac017eeabb..feca5656f73d6 100644 --- a/x-pack/plugins/graph/public/plugin.ts +++ b/x-pack/plugins/graph/public/plugin.ts @@ -28,6 +28,7 @@ import { LicensingPluginStart } from '@kbn/licensing-plugin/public'; import type { HomePublicPluginSetup, HomePublicPluginStart } from '@kbn/home-plugin/public'; import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import { SavedObjectsStart } from '@kbn/saved-objects-plugin/public'; +import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public'; import { checkLicense } from '../common/check_license'; import { ConfigSchema } from '../config'; @@ -44,6 +45,7 @@ export interface GraphPluginStartDependencies { inspector: InspectorPublicPluginStart; home?: HomePublicPluginStart; spaces?: SpacesApi; + savedObjectsManagement: SavedObjectsManagementPluginStart; } export class GraphPlugin @@ -113,6 +115,7 @@ export class GraphPlugin uiSettings: core.uiSettings, spaces: pluginsStart.spaces, inspect: pluginsStart.inspector, + savedObjectsManagement: pluginsStart.savedObjectsManagement, }); }, }); diff --git a/x-pack/plugins/graph/public/services/source_modal.tsx b/x-pack/plugins/graph/public/services/source_modal.tsx index 258bb58d1e077..6da003833d1a9 100644 --- a/x-pack/plugins/graph/public/services/source_modal.tsx +++ b/x-pack/plugins/graph/public/services/source_modal.tsx @@ -8,6 +8,7 @@ import { CoreStart } from '@kbn/core/public'; import React from 'react'; import { KibanaReactOverlays } from '@kbn/kibana-react-plugin/public'; +import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public'; import { SourceModal } from '../components/source_modal'; import { IndexPatternSavedObject } from '../types'; @@ -16,17 +17,20 @@ export function openSourceModal( overlays, http, uiSettings, + savedObjectsManagement, }: { overlays: KibanaReactOverlays; http: CoreStart['http']; uiSettings: CoreStart['uiSettings']; + savedObjectsManagement: SavedObjectsManagementPluginStart; }, onSelected: (indexPattern: IndexPatternSavedObject) => void ) { const modalRef = overlays.openModal( { onSelected(indexPattern); modalRef.close(); diff --git a/x-pack/plugins/graph/tsconfig.json b/x-pack/plugins/graph/tsconfig.json index 0f9bc04b6975a..3979bb5b4d9a0 100644 --- a/x-pack/plugins/graph/tsconfig.json +++ b/x-pack/plugins/graph/tsconfig.json @@ -38,6 +38,8 @@ "@kbn/utility-types", "@kbn/react-field", "@kbn/shared-ux-router", + "@kbn/saved-objects-management-plugin", + "@kbn/saved-objects-finder-plugin", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/delete_data_stream_confirmation_modal/delete_data_stream_confirmation_modal.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/delete_data_stream_confirmation_modal/delete_data_stream_confirmation_modal.tsx index f10718788eeab..bdf3958c79ca7 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/delete_data_stream_confirmation_modal/delete_data_stream_confirmation_modal.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/delete_data_stream_confirmation_modal/delete_data_stream_confirmation_modal.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { Fragment } from 'react'; +import React, { Fragment, useState } from 'react'; import { EuiCallOut, EuiConfirmModal, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -25,60 +25,65 @@ export const DeleteDataStreamConfirmationModal: React.FunctionComponent = dataStreams: string[]; onClose: (data?: { hasDeletedDataStreams: boolean }) => void; }) => { + const [isLoading, setLoading] = useState(false); + const dataStreamsCount = dataStreams.length; const handleDeleteDataStreams = () => { - deleteDataStreams(dataStreams).then(({ data: { dataStreamsDeleted, errors }, error }) => { - const hasDeletedDataStreams = dataStreamsDeleted && dataStreamsDeleted.length; + setLoading(true); + + deleteDataStreams(dataStreams) + .then(({ data: { dataStreamsDeleted, errors }, error }) => { + const hasDeletedDataStreams = dataStreamsDeleted && dataStreamsDeleted.length; + + if (hasDeletedDataStreams) { + const successMessage = + dataStreamsDeleted.length === 1 + ? i18n.translate( + 'xpack.idxMgmt.deleteDataStreamsConfirmationModal.successDeleteSingleNotificationMessageText', + { + defaultMessage: "Deleted data stream '{dataStreamName}'", + values: { dataStreamName: dataStreams[0] }, + } + ) + : i18n.translate( + 'xpack.idxMgmt.deleteDataStreamsConfirmationModal.successDeleteMultipleNotificationMessageText', + { + defaultMessage: + 'Deleted {numSuccesses, plural, one {# data stream} other {# data streams}}', + values: { numSuccesses: dataStreamsDeleted.length }, + } + ); - if (hasDeletedDataStreams) { - const successMessage = - dataStreamsDeleted.length === 1 + onClose({ hasDeletedDataStreams }); + notificationService.showSuccessToast(successMessage); + } + + if (error || (errors && errors.length)) { + const hasMultipleErrors = + (errors && errors.length > 1) || (error && dataStreams.length > 1); + + const errorMessage = hasMultipleErrors ? i18n.translate( - 'xpack.idxMgmt.deleteDataStreamsConfirmationModal.successDeleteSingleNotificationMessageText', + 'xpack.idxMgmt.deleteDataStreamsConfirmationModal.multipleErrorsNotificationMessageText', { - defaultMessage: "Deleted data stream '{dataStreamName}'", - values: { dataStreamName: dataStreams[0] }, + defaultMessage: 'Error deleting {count} data streams', + values: { + count: (errors && errors.length) || dataStreams.length, + }, } ) : i18n.translate( - 'xpack.idxMgmt.deleteDataStreamsConfirmationModal.successDeleteMultipleNotificationMessageText', + 'xpack.idxMgmt.deleteDataStreamsConfirmationModal.errorNotificationMessageText', { - defaultMessage: - 'Deleted {numSuccesses, plural, one {# data stream} other {# data streams}}', - values: { numSuccesses: dataStreamsDeleted.length }, + defaultMessage: "Error deleting data stream '{name}'", + values: { name: (errors && errors[0].name) || dataStreams[0] }, } ); - - onClose({ hasDeletedDataStreams }); - notificationService.showSuccessToast(successMessage); - } - - if (error || (errors && errors.length)) { - const hasMultipleErrors = - (errors && errors.length > 1) || (error && dataStreams.length > 1); - - const errorMessage = hasMultipleErrors - ? i18n.translate( - 'xpack.idxMgmt.deleteDataStreamsConfirmationModal.multipleErrorsNotificationMessageText', - { - defaultMessage: 'Error deleting {count} data streams', - values: { - count: (errors && errors.length) || dataStreams.length, - }, - } - ) - : i18n.translate( - 'xpack.idxMgmt.deleteDataStreamsConfirmationModal.errorNotificationMessageText', - { - defaultMessage: "Error deleting data stream '{name}'", - values: { name: (errors && errors[0].name) || dataStreams[0] }, - } - ); - - notificationService.showDangerToast(errorMessage); - } - }); + notificationService.showDangerToast(errorMessage); + } + }) + .finally(() => setLoading(false)); }; return ( @@ -107,6 +112,7 @@ export const DeleteDataStreamConfirmationModal: React.FunctionComponent = values={{ dataStreamsCount }} /> } + isLoading={isLoading} > ; -export const logViewReferenceRT = rt.type({ +export const persistedLogViewReferenceRT = rt.type({ logViewId: rt.string, type: rt.literal('log-view-reference'), }); +export type PersistedLogViewReference = rt.TypeOf; + +export const inlineLogViewReferenceRT = rt.type({ + type: rt.literal('log-view-inline'), + id: rt.string, + attributes: logViewAttributesRT, +}); + +export const logViewReferenceRT = rt.union([persistedLogViewReferenceRT, inlineLogViewReferenceRT]); + export type LogViewReference = rt.TypeOf; diff --git a/x-pack/plugins/infra/common/search_strategies/log_entries/log_entries.ts b/x-pack/plugins/infra/common/search_strategies/log_entries/log_entries.ts index 65bcec8c98e6a..f8daaa1b9227b 100644 --- a/x-pack/plugins/infra/common/search_strategies/log_entries/log_entries.ts +++ b/x-pack/plugins/infra/common/search_strategies/log_entries/log_entries.ts @@ -13,7 +13,7 @@ import { logEntryCursorRT, logEntryRT, } from '../../log_entry'; -import { logViewColumnConfigurationRT } from '../../log_views'; +import { logViewColumnConfigurationRT, logViewReferenceRT } from '../../log_views'; import { jsonObjectRT } from '../../typed_json'; import { searchStrategyErrorRT } from '../common/errors'; @@ -21,7 +21,7 @@ export const LOG_ENTRIES_SEARCH_STRATEGY = 'infra-log-entries'; const logEntriesBaseSearchRequestParamsRT = rt.intersection([ rt.type({ - sourceId: rt.string, + logView: logViewReferenceRT, startTimestamp: rt.number, endTimestamp: rt.number, size: rt.number, diff --git a/x-pack/plugins/infra/common/search_strategies/log_entries/log_entry.ts b/x-pack/plugins/infra/common/search_strategies/log_entries/log_entry.ts index 2dc182c4ba0e2..6d2a7891264d1 100644 --- a/x-pack/plugins/infra/common/search_strategies/log_entries/log_entry.ts +++ b/x-pack/plugins/infra/common/search_strategies/log_entries/log_entry.ts @@ -7,12 +7,13 @@ import * as rt from 'io-ts'; import { logEntryCursorRT, logEntryFieldRT } from '../../log_entry'; +import { logViewReferenceRT } from '../../log_views'; import { searchStrategyErrorRT } from '../common/errors'; export const LOG_ENTRY_SEARCH_STRATEGY = 'infra-log-entry'; export const logEntrySearchRequestParamsRT = rt.type({ - sourceId: rt.string, + logView: logViewReferenceRT, logEntryId: rt.string, }); diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/editor.tsx b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/editor.tsx index c6b18bebc98b3..1c38dca829aef 100644 --- a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/editor.tsx +++ b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/editor.tsx @@ -13,7 +13,7 @@ import { ForLastExpression, RuleTypeParamsExpressionProps, } from '@kbn/triggers-actions-ui-plugin/public'; -import { LogViewReference, ResolvedLogViewField } from '../../../../../common/log_views'; +import { PersistedLogViewReference, ResolvedLogViewField } from '../../../../../common/log_views'; import { Comparator, isOptimizableGroupedThreshold, @@ -54,7 +54,7 @@ const DEFAULT_BASE_EXPRESSION = { const DEFAULT_FIELD = 'log.level'; -const createLogViewReference = (logViewId: string): LogViewReference => ({ +const createLogViewReference = (logViewId: string): PersistedLogViewReference => ({ logViewId, type: 'log-view-reference', }); @@ -69,7 +69,7 @@ const createDefaultCriterion = ( const createDefaultCountRuleParams = ( availableFields: ResolvedLogViewField[], - logView: LogViewReference + logView: PersistedLogViewReference ): PartialCountRuleParams => ({ ...DEFAULT_BASE_EXPRESSION, logView, @@ -82,7 +82,7 @@ const createDefaultCountRuleParams = ( const createDefaultRatioRuleParams = ( availableFields: ResolvedLogViewField[], - logView: LogViewReference + logView: PersistedLogViewReference ): PartialRatioRuleParams => ({ ...DEFAULT_BASE_EXPRESSION, logView, @@ -226,11 +226,11 @@ export const Editor: React.FC createLogViewReference(logViewId), [logViewId]); + const logViewReference = useMemo(() => createLogViewReference(logViewId), [logViewId]); const defaultCountAlertParams = useMemo( - () => createDefaultCountRuleParams(supportedFields, logViewReferemnce), - [supportedFields, logViewReferemnce] + () => createDefaultCountRuleParams(supportedFields, logViewReference), + [supportedFields, logViewReference] ); const updateType = useCallback( @@ -238,12 +238,12 @@ export const Editor: React.FC { diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/hooks/use_chart_preview_data.tsx b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/hooks/use_chart_preview_data.tsx index 9b9dc501e1591..0b99cea2fd7c9 100644 --- a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/hooks/use_chart_preview_data.tsx +++ b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/hooks/use_chart_preview_data.tsx @@ -73,7 +73,7 @@ export const callGetChartPreviewDataAPI = async ( body: JSON.stringify( getLogAlertsChartPreviewDataRequestPayloadRT.encode({ data: { - sourceId, + logView: { type: 'log-view-reference', logViewId: sourceId }, alertParams, buckets, }, diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/custom_equation/custom_equation_editor.stories.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/custom_equation/custom_equation_editor.stories.tsx index ce30172a74f15..d2fd0639859a6 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/custom_equation/custom_equation_editor.stories.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/custom_equation/custom_equation_editor.stories.tsx @@ -34,6 +34,24 @@ export default { }, } as Meta; +const fakeDataView = { + title: 'metricbeat-*', + fields: [ + { + name: 'system.cpu.user.pct', + type: 'number', + }, + { + name: 'system.cpu.system.pct', + type: 'number', + }, + { + name: 'system.cpu.cores', + type: 'number', + }, + ], +}; + const CustomEquationEditorTemplate: Story = (args) => { const [expression, setExpression] = useState(args.expression); const [errors, setErrors] = useState(args.errors); @@ -60,6 +78,7 @@ const CustomEquationEditorTemplate: Story = (args) => errors={errors} expression={expression} onChange={handleExpressionChange} + dataView={fakeDataView} /> ); }; diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/custom_equation/custom_equation_editor.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/custom_equation/custom_equation_editor.tsx index 866e818688533..8632616866a7e 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/custom_equation/custom_equation_editor.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/custom_equation/custom_equation_editor.tsx @@ -16,6 +16,7 @@ import React, { useState, useCallback, useMemo } from 'react'; import { omit, range, first, xor, debounce } from 'lodash'; import { IErrorObject } from '@kbn/triggers-actions-ui-plugin/public'; import { FormattedMessage } from '@kbn/i18n-react'; +import { DataViewBase } from '@kbn/es-query'; import { OMITTED_AGGREGATIONS_FOR_CUSTOM_METRICS } from '../../../../../common/http_api'; import { Aggregators, @@ -39,6 +40,7 @@ export interface CustomEquationEditorProps { fields: NormalizedFields; aggregationTypes: AggregationTypes; errors: IErrorObject; + dataView: DataViewBase; } const NEW_METRIC = { name: 'A', aggType: Aggregators.AVERAGE as CustomMetricAggTypes }; @@ -53,6 +55,7 @@ export const CustomEquationEditor = ({ fields, aggregationTypes, errors, + dataView, }: CustomEquationEditorProps) => { const [customMetrics, setCustomMetrics] = useState( expression?.customMetrics ?? [NEW_METRIC] @@ -130,6 +133,7 @@ export const CustomEquationEditor = ({ disableDelete={disableDelete} onChange={handleChange} errors={errors} + dataView={dataView} /> ); } diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/custom_equation/metric_row_with_count.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/custom_equation/metric_row_with_count.tsx index 43ac682830bcd..78bc2bf265b4e 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/custom_equation/metric_row_with_count.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/custom_equation/metric_row_with_count.tsx @@ -4,16 +4,11 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { - EuiFieldText, - EuiFormRow, - EuiHorizontalRule, - EuiFlexItem, - EuiFlexGroup, - EuiSelect, -} from '@elastic/eui'; +import { EuiFormRow, EuiHorizontalRule, EuiFlexItem, EuiFlexGroup, EuiSelect } from '@elastic/eui'; import React, { useCallback, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; +import { DataViewBase } from '@kbn/es-query'; +import { MetricsExplorerKueryBar } from '../../../../pages/metrics/metrics_explorer/components/kuery_bar'; import { Aggregators, CustomMetricAggTypes } from '../../../../../common/alerting/metrics'; import { MetricRowControls } from './metric_row_controls'; import { MetricRowBaseProps } from './types'; @@ -21,6 +16,7 @@ import { MetricRowBaseProps } from './types'; interface MetricRowWithCountProps extends MetricRowBaseProps { agg?: Aggregators; filter?: string; + dataView: DataViewBase; } export const MetricRowWithCount = ({ @@ -31,6 +27,7 @@ export const MetricRowWithCount = ({ disableDelete, onChange, aggregationTypes, + dataView, }: MetricRowWithCountProps) => { const aggOptions = useMemo( () => @@ -59,10 +56,10 @@ export const MetricRowWithCount = ({ ); const handleFilterChange = useCallback( - (el: React.ChangeEvent) => { + (filterString: string) => { onChange({ name, - filter: el.target.value, + filter: filterString, aggType: agg as CustomMetricAggTypes, }); }, @@ -89,7 +86,14 @@ export const MetricRowWithCount = ({ { defaultMessage: 'KQL Filter {name}', values: { name } } )} > - + diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx index 5bc6cff8545e1..224bf8d1fb5d0 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx @@ -311,6 +311,7 @@ export const Expressions: React.FC = (props) => { setRuleParams={updateParams} errors={(errors[idx] as IErrorObject) || emptyError} expression={e || {}} + dataView={derivedIndexPattern} > { timeWindowSize: [], }} expression={expression} + dataView={{ fields: [], title: 'metricbeat-*' }} /> ); diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_row.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_row.tsx index 14ff5b1e60eed..a353b954386c1 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_row.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_row.tsx @@ -27,6 +27,7 @@ import { ThresholdExpression, WhenExpression, } from '@kbn/triggers-actions-ui-plugin/public'; +import { DataViewBase } from '@kbn/es-query'; import { Aggregators, Comparator } from '../../../../common/alerting/metrics'; import { decimalToPct, pctToDecimal } from '../../../../common/utils/corrected_percent_convert'; import { DerivedIndexPattern } from '../../../containers/metrics_source'; @@ -54,6 +55,7 @@ interface ExpressionRowProps { addExpression(): void; remove(id: number): void; setRuleParams(id: number, params: MetricExpression): void; + dataView: DataViewBase; } const StyledExpressionRow = euiStyled(EuiFlexGroup)` @@ -74,8 +76,17 @@ const StyledHealth = euiStyled(EuiHealth)` export const ExpressionRow: React.FC = (props) => { const [isExpanded, setRowState] = useState(true); const toggleRowState = useCallback(() => setRowState(!isExpanded), [isExpanded]); - const { children, setRuleParams, expression, errors, expressionId, remove, fields, canDelete } = - props; + const { + dataView, + children, + setRuleParams, + expression, + errors, + expressionId, + remove, + fields, + canDelete, + } = props; const { aggType = AGGREGATION_TYPES.MAX, @@ -325,6 +336,7 @@ export const ExpressionRow: React.FC = (props) => { aggregationTypes={aggregationType} onChange={handleCustomMetricChange} errors={errors} + dataView={dataView} /> diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/validation.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/validation.tsx index b3d4d423c58b5..83d3919ef6c01 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/validation.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/validation.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import { fromKueryExpression } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; import { ValidationResult } from '@kbn/triggers-actions-ui-plugin/public'; import { isEmpty } from 'lodash'; @@ -48,7 +49,7 @@ export function validateMetricThreshold({ }; metric: string[]; customMetricsError?: string; - customMetrics: Record; + customMetrics: Record; equation?: string; }; } & { filterQuery?: string[] } = {}; @@ -169,7 +170,7 @@ export function validateMetricThreshold({ ); } else { c.customMetrics.forEach((metric) => { - const customMetricErrors: { aggType?: string; field?: string } = {}; + const customMetricErrors: { aggType?: string; field?: string; filter?: string } = {}; if (!metric.aggType) { customMetricErrors.aggType = i18n.translate( 'xpack.infra.metrics.alertFlyout.error.customMetrics.aggTypeRequired', @@ -186,6 +187,13 @@ export function validateMetricThreshold({ } ); } + if (metric.aggType === 'count' && metric.filter) { + try { + fromKueryExpression(metric.filter); + } catch (e) { + customMetricErrors.filter = e.message; + } + } if (!isEmpty(customMetricErrors)) { errors[id].customMetrics[metric.name] = customMetricErrors; } diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/hosts/rx.ts b/x-pack/plugins/infra/public/common/visualizations/lens/hosts/rx.ts index 228d479f39ee2..4b2ee6d8a2eda 100644 --- a/x-pack/plugins/infra/public/common/visualizations/lens/hosts/rx.ts +++ b/x-pack/plugins/infra/public/common/visualizations/lens/hosts/rx.ts @@ -53,8 +53,8 @@ export class RX implements ILensVisualization { const dataLayer = this.formula.insertOrReplaceFormulaColumn( 'y_network_in_bytes', { - formula: "counter_rate(max(system.network.in.bytes), kql='system.network.in.bytes: *') * 8", - timeScale: 's', + formula: + "average(host.network.ingress.bytes) * 8 / (max(metricset.period, kql='host.network.ingress.bytes: *') / 1000)", format: { id: 'bits', params: { @@ -95,13 +95,13 @@ export class RX implements ILensVisualization { negate: false, alias: null, index: '3be1e71b-4bc5-4462-a314-04539f877a19', - key: 'system.network.in.bytes', + key: 'host.network.ingress.bytes', value: 'exists', type: 'exists', }, query: { exists: { - field: 'system.network.in.bytes', + field: 'host.network.ingress.bytes', }, }, $state: { diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/hosts/tx.ts b/x-pack/plugins/infra/public/common/visualizations/lens/hosts/tx.ts index da5e4635d55ca..236efed938046 100644 --- a/x-pack/plugins/infra/public/common/visualizations/lens/hosts/tx.ts +++ b/x-pack/plugins/infra/public/common/visualizations/lens/hosts/tx.ts @@ -54,8 +54,7 @@ export class TX implements ILensVisualization { 'y_network_out_bytes', { formula: - "counter_rate(max(system.network.out.bytes), kql='system.network.out.bytes: *') * 8", - timeScale: 's', + "average(host.network.egress.bytes) * 8 / (max(metricset.period, kql='host.network.egress.bytes: *') / 1000)", format: { id: 'bits', params: { @@ -96,13 +95,13 @@ export class TX implements ILensVisualization { negate: false, alias: null, index: '3be1e71b-4bc5-4462-a314-04539f877a19', - key: 'system.network.out.bytes', + key: 'host.network.egress.bytes', value: 'exists', type: 'exists', }, query: { exists: { - field: 'system.network.out.bytes', + field: 'host.network.egress.bytes', }, }, $state: { diff --git a/x-pack/plugins/infra/public/components/autocomplete_field/autocomplete_field.tsx b/x-pack/plugins/infra/public/components/autocomplete_field/autocomplete_field.tsx index e264da646949c..657db92072275 100644 --- a/x-pack/plugins/infra/public/components/autocomplete_field/autocomplete_field.tsx +++ b/x-pack/plugins/infra/public/components/autocomplete_field/autocomplete_field.tsx @@ -24,6 +24,7 @@ interface AutocompleteFieldProps { disabled?: boolean; autoFocus?: boolean; 'aria-label'?: string; + compressed?: boolean; } interface AutocompleteFieldState { @@ -53,6 +54,7 @@ export class AutocompleteField extends React.Component< value, disabled, 'aria-label': ariaLabel, + compressed, } = this.props; const { areSuggestionsVisible, selectedIndex } = this.state; @@ -60,6 +62,7 @@ export class AutocompleteField extends React.Component< { expect(fetchLogSummaryMock).toHaveBeenCalledTimes(1); expect(fetchLogSummaryMock).toHaveBeenLastCalledWith( expect.objectContaining({ - sourceId: 'INITIAL_SOURCE_ID', + logView: { logViewId: 'INITIAL_SOURCE_ID', type: 'log-view-reference' }, }), expect.anything() ); @@ -74,7 +74,7 @@ describe('useLogSummary hook', () => { expect(fetchLogSummaryMock).toHaveBeenCalledTimes(2); expect(fetchLogSummaryMock).toHaveBeenLastCalledWith( expect.objectContaining({ - sourceId: 'CHANGED_SOURCE_ID', + logView: { logViewId: 'CHANGED_SOURCE_ID', type: 'log-view-reference' }, }), expect.anything() ); diff --git a/x-pack/plugins/infra/public/containers/logs/log_summary/log_summary.tsx b/x-pack/plugins/infra/public/containers/logs/log_summary/log_summary.tsx index b3e7f8235ace7..c4b933ab04cd0 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_summary/log_summary.tsx +++ b/x-pack/plugins/infra/public/containers/logs/log_summary/log_summary.tsx @@ -35,7 +35,7 @@ export const useLogSummary = ( pushLogSummaryBucketsArgs([ { - sourceId, + logView: { type: 'log-view-reference', logViewId: sourceId }, startTimestamp, endTimestamp, bucketSize, diff --git a/x-pack/plugins/infra/public/observability_logs/log_stream_position_state/src/state_machine.ts b/x-pack/plugins/infra/public/observability_logs/log_stream_position_state/src/state_machine.ts index d4053d14ab304..96b7dbb687851 100644 --- a/x-pack/plugins/infra/public/observability_logs/log_stream_position_state/src/state_machine.ts +++ b/x-pack/plugins/infra/public/observability_logs/log_stream_position_state/src/state_machine.ts @@ -23,7 +23,7 @@ import type { import { initializeFromUrl, updateContextInUrl } from './url_state_storage_service'; export const createPureLogStreamPositionStateMachine = (initialContext: LogStreamPositionContext) => - /** @xstate-layout N4IgpgJg5mDOIC5QBsD2UDKAXATmAhgLYAK+M2+WYAdAK4B2Alk1o-sowF6QDEAMgHkAggBEAkgDkA4gH1BsgGpiAogHUZGACpCASpuUiA2gAYAuolAAHVLEatU9CyAAeiALQAmAJzUPHgGwArIEALAAcHmHhXsEhADQgAJ6IAQDM1IHGYQDs2ZnZxl5e-l6pAL5lCWiYuAQkZGAUVHRMLGwc3BD8wuLScgKKKuoAYkJifAYm5kgg1rb2jjOuCJ4hAIzUqZklHgX+xqlrkQnJCKlB1P4loYH+qV7hHoEVVejYeESk5FiUNAzMdnaXF4glEklk8hkSjUGgAqgBheHKAyTMxOOaAhxOZbZNbZajGXLBVIkrJrYInRBHYzUEIeVIhVJhNZZLws7IhF4garvOpfRo-Zr-NrsYFdUG9CEDKFDOGI5EiSZraZWGyYxagHEhEIEwkeI7Zc7GO6UhCZHzZML7MKBDwhQp0zmVblvWqfBpNGhofAQZhQPjoBSMMAAd26YL6kOhIzGEyMaJmGIW2JS4VpJTCWXp5K82Q8pvN1Et1tt9oedq5PLd9W+v2o3t99H9geDYYl4P6gxhGARSJR8ZVszVyaWiEZPnt-m8Ry8xjta38pqN1FK+uy+w8xhCgS8lddHxrArrDb9AagQdD4clnZl3d7CqVg6TjCxo4QISumy2MWNMWiAVNO58WMFkImMOc50CMI9xqA9+U9etUB9U8W1DYZ8EYZAQR6Dso1lLRdH0Ad0WHF8NRcdxvF8AJYgiKIwhiUIC1SDwMgNcC8g5O1nmdKs4I9QUaAAC3wWAzwvEMxHoX0AGM4BaAFWFFToeB0ZQkTEBQDBkSQxE0MQhD4GRiF0IQAFllH0HQMCmEj5jIlMEBnfxqAiYojjWVJCjnJcwnSIIrSuNZZ2CGJyl4-c+QEusRLE1DJOkxg5NgBSRQ6XgFEMsQRBkABFWFlB0ABNGR4QACSEaRUSfUjX01Kl-DyWk1giW0p3tElTTxfFwhCPYQIXXE1idV5YKi2tmli8TWyk2T5OFQFlN4SRMr4bK8oK4rSoqqriMTWryOWBdGtckCQMCEknn8MIl21FcWLxVJDUzfwWv8GDeXdCbhNE6bQ1mpL5MUoEVNW9b8sKkrysqqRqrs9VHMGwJmtagI7QOVICznFd1yebdMgutY1gqZ16FQCA4CcPjxqPKh4ZHeqVkiHwtl-XZjQOTzTTcNk2NtQ4-A5q0PureDBNSxb0ogemHLfOkurpahyXArIbTZIItxF-jvsQ5Cmz+kMZbqiiEF2DJQiuK0bVyH94iSRAmTCTZ3ICPMtj8HjRs+w8EJPfX4vQzDICNw7EGR5k7S8NJGrZNXAJ3IsnvtImt28aCIrGr7aZ+uLzxmxLkpDxHNxpC7dn2K404pe331YrwokNQ1GIF7ItZphCpvigHkolpSpaLt8jjTMv12NKd6+r04nlYgbDjxO0-KnNus4736u4LoG0rFAfGc8qIi0Cck1cCQ1K4LJ4iznS1zjzG0WOXn3xcIRhYFsf28-+jf4H2+zjaOw4XKbijt4YIWRbjZHjhaJ6T1cSwIxiTMoQA */ + /** @xstate-layout N4IgpgJg5mDOIC5QBsD2UDKAXATmAhgLYAKqsAlluagHbb5ZgB0Arjee1fsuQF6QBiAEoBRAMIiAkgDURAEQD6kgHKSAKpICCAGQUBFAKoihATQXFNQzQFkRa4xgDaABgC6iUAAcylajQ8gAB6IALQAbACsTAAcAEzRzgCcACzJccmxAIzR0QA0IACeiNkAzExhJSUA7FURsSXRYYklEVUAvm35aJi4BCQ+VLT0jEwcvtx8HFAAYjiohAY4yAIq6lrakgBa8grTQgDy1goGQtou7kgg3hSD-pfBCCGxTBHREYnOTelZ0bX5RQg0okmD8miVMhEwmEqmFGh0uuhsHgiKQbn5hswxlwePwIExrr5aLBRpxyBNcQIAFIGazEBRqfb0ywAcTs5n2GDW+2U5wCBNuAQeVUSz2SULCmQ+zlScQi-0QyWcVSYVU+QIhzlejQi8JA3SRfVRhLoWAYmNJ5Mg+IGfmJWLJOMEomI+yEagU0kknIAQtoROzORpuU43HybbRBYg6mEmGloskqrEqjkwrFYvLAdEyhkItLMlVqtVc+1OnrEb0UeGTWaSeNHXj+bba9i+IINLYFGIABKaZSsuS8y6NiP3RDC57ChOpZzStKxOWFRBZMqpqr5xIpT6xEXRXX6iv9NFDU0je2WvFYAAWcywWB4NCgxHwMBENAgylQVAAZuQAMYMJtyAgZAwGEEQXTdD0vUkX1-RdQNJGDQcvCrSMEHeTJygSWJkg+X5FQhDN4xjNNIk1V4xRKWIwj3ctkUPY0MWbB1Wwva9PzvKYnxfN8P2-P8AKJJgrxvTiHzAiD3U9H0-QDLllBDC4UKPO5QAeTJJTKVVITTRpt3iDMaiiCINUSDTMlw-Nklonp6KNW4mLPethPY2970fZ8wFfd9P3IH9-1uYkRI49yBECWAT2YfAv0YHAAApRG0TQNFkBQRGURQDGIORkv9OQRCSkwAEoBH3Oyq0ci1nOCtyuM87y+L8gTApc0T3OQq5UNHBAzOiFVohFEo5xKZx6mSDNeqYDTakyVMlWiTIhpsg1KxUyq61Y1qQrqnifP4gKmxqsSoDCiKa2i2KEoK5KZH9dLMuy3KFHywqSrKw0Ksi5jzy22qH24rzeN8-zBJoILXOOxxMiUzqVLQ2plTqEoRWnGcUgmpopvjRUYWSbJWhKZaD3s9EvqczajvcgGGuB5qmxoYGCimAQOuHVSggVGpymhMJnAhVUEjqDNMiVKJaiG+dfgG6prN1BmIDgAJ3tWxjIrDOHupCDSyjiBIUjnDJsjyRdHjSGJEwWjd8zeNGifKtavrYcncXV400IyIjMI3b36lVXmIgTO2PodmtnamWZ5kWZBXYFTXnmlWE-hNka+uoxIA6zRJZUJ0tlYYhyyaq1iY78NCSkiFULLnSFkYlaEMwacok2yGcRZF1pMiDlWC9DovcWtFT4CHLq1IVRaYnjapEmTRNnHBYXqKm5vhWaBIJTqLv89J3uNv7tm7T7yAS5HUfAQlCfkinmfYjnzIM23KI04z5Hs83knjx3lt+8pnbAb2pqDpEmPuzB4eMtJV1lBURIdcqgZlhH1PmJkcJDTqItEsCJbLB1Vp-Fi38IZU3qkDfaoM7TATAMA92581wynnFAmBRExQgmormCIFEKjUTfp9HBP0f7-UIf-EGLVeFQAod1cyzx4isKqHOfMkIGEkWYeRYiVEaK5zolgnup5D5sTar-GmxCWoM2-EzB8ojT5t2BK8HClFFqREIibduzgVQRCGiNJUyRWEig6B0IAA */ createMachine( { context: initialContext, @@ -92,7 +92,7 @@ export const createPureLogStreamPositionStateMachine = (initialContext: LogStrea }, throttling: { after: { - [RELATIVE_END_UPDATE_DELAY]: [ + RELATIVE_END_UPDATE_DELAY: [ { target: 'notifying', cond: 'hasReachedPageEndBuffer', @@ -185,6 +185,9 @@ export const createPureLogStreamPositionStateMachine = (initialContext: LogStrea : {} ), }, + delays: { + RELATIVE_END_UPDATE_DELAY, + }, guards: { // User is close to the bottom of the page. hasReachedPageEndBuffer: (context, event) => diff --git a/x-pack/plugins/infra/public/observability_logs/log_stream_query_state/src/state_machine.ts b/x-pack/plugins/infra/public/observability_logs/log_stream_query_state/src/state_machine.ts index bbae54bf8803e..16ddeb9352a31 100644 --- a/x-pack/plugins/infra/public/observability_logs/log_stream_query_state/src/state_machine.ts +++ b/x-pack/plugins/infra/public/observability_logs/log_stream_query_state/src/state_machine.ts @@ -55,7 +55,7 @@ import { DEFAULT_REFRESH_INTERVAL, DEFAULT_REFRESH_TIME_RANGE } from './defaults export const createPureLogStreamQueryStateMachine = ( initialContext: LogStreamQueryContextWithDataViews & LogStreamQueryContextWithTime ) => - /** @xstate-layout N4IgpgJg5mDOIC5QEUCuYBOBPAdKgdgJZEAuhAhgDaEBekAxANoAMAuoqAA4D2shZ3fBxAAPRAEYAHAGYck8QCYArAoAsC8QHZNygJySANCCwTpszc2njpANnH2FN1UtUBfV0bSZcxfhWo0xFAAYhjcALYAqhiU9ACSAHJxACpxAIIAMnEAWgCiACIA+sEASgDyALKFkSUZLOxIIDx8AkKNYghKzJJyCvJOurqqA4bGiPI4XczTdjbM2kq64u6e6Ng4vmRUtEGhEdGxiSnpWXlFpZXVtYziDVy8foLCHSpGJgjizj02SjYa3X0dH8ViAvOtNv4dvgQmFwslCOEwMFCJQSJgAMqYABuhAAxmB4klUpkcgViuUqqkKrlinEMslciVCujGQA1OIAYVy9WEzUebVAHWkkhsOGmmnkqnUumkzBsmmkb0Quk0ot0coUZnscukSkkILBPlIkLoEBwAEc1lh6MhIoyAJrky4stIlDkACUKACFXYUPWkEgBxAo8xp81rPcbiJQ4GzSPoKOXMX7RxVjD7MVTiOTyxOSVT5zRKCUGq0bY3bU0Wq30YJ0hkldFOqout2en1M-1BkNsXkPCPtKMxuMJpMppRp95LHrDZi6CfSbQ2STF0vect+SuQaveej5NLJNKFdm5ADqTa7wfyofuLUIT0HCHkw-jkkTc3Hk-GCgUYvm8kGAtCzXcEKwCbdLXXLFtggcgyGhehWRJfdUjKBJmUiDkuQKHs7iaft7wFUQJCzVQcGkdU7GjOdxF0JUECGZhyMUJRFGYBMVX1DxQTLCEtzNSD1mg6hYPgqBEOQg84jQ4o0jpXC+zvB9BRIz5yMo+wuiWOj03sOYcC0Wi+gA7RkxAo1N3AgSywwMBhMIUSggkrIUOk9DgjkjIFLDAjlOIj5SPUuVNJonT3nESxRVVX4FU0RQbH0aRzI3LYrJ3dZbPsxyEKQlypJk9FMOw-JvNvflIwCtSKOC6jtPo-ocEo5QEo0H9fmSvi0rIRF6CpGkLkpOJqVpelGWZNlORpS9SvwpSiI6Z9Y1fd9kzsCd6KkVRNFjX5-l0BQLCLLjVnXTraG3bqCUiAAFFCaT6woSgDYMb1m8rH0Wkc3zHNavw+eUmOkVRF2XFVhjmGwOrA86zUu+gbrux7clKXJ0U9RIG1y17w0IirPuWn7Uw21QZTFKx4wsH81CUJQocsmGcEulKTQYbHfPm0ws0mTVweBiV51UDadF0SYZDMYHJE0KUtrp1KGaZs7TSYW5FPelSPiB7MVCBmwnEXSQBaFyQem6LUpYogsdHcbj8G4CA4GEQ1VYHdWAFobHo12Y0GH3fb9pLuMNPAiGh01ndxx91A2+McAsKw7F1I7AVlk1dlhA5w78joDp6A383lRctC0wXdLnHBEysTU40UA2ZcD3jQ7TiJ4URZFUQxbE8TATOOYQeV6MGQHFkXRd51owYU-4nuKrikWvpWz96JJpimrsRwJQLaNJ7SwT3jKl3-NVbb58J9b02LGcNXJn8Cx+beGd3nAsrgoJp8fexJfL+Q2q0AtbBLqcLhJgUQiuIP4zgwHHR4qdUOEEyxZTfurMButYzzllBROYUo-j1WcDgKUjhjaEMsNYe+VZH7EAQT5OaFVkGigShOSwuhMHDAUBtOUMZZQaGBrqXUIo3D1xgfTMhNk7IwRftCRB-kP7bT6IoZQv8ZBOBwWROYMoZQ-klpITMpCLoIm7lQtWh95RLVHB+X60cY4TmsOqQuqot4CNAkI3RiJmZTwMQfDoigtCNWilote0wlBCyBuXEm+hpQWF1jo2GeicCwBIC-XEkjPGsTIrRCcE5dYS1okbUUZgzAHWTEDSwUTGYxLibZcg4RX7uIjkglJBk0EZL1gBIWB05B5OjFLYs+hNDW1cEAA */ + /** @xstate-layout N4IgpgJg5mDOIC5QEUCuYBOBPAdKgdgJZEAuhAhgDaEBekAxANoAMAuoqAA4D2shZ3fBxAAPRAEYAHAGYck8QCYArAoAsC8QHZNygJySANCCwTpszc2njpANnH2FN1UtUBfV0bSZcxfhWo0xFAAYhjcALYAqhiU9ACSAHJxACpxAIIAMnEAWgCiACIA+sEASgDyALKFkSUZLOxIIDx8AkKNYghKzJJyCvJOurqqA4bGiPI4XczTdjbM2kq64u6e6Ng4vmRUtEGhEcmE4WDBhJQkmADKmABuhADGYPFJqZk5BcXlVakVucVxGclciVChcgQA1OIAYVy9WEzT8gmEHWkkhsOGmmnkqnUumkzBsmmkRhMCF0mjRunxCjM9nx0iUkhWIC8602-lokBwAEc1lh6MhIkCAJofSog3JpEqQgAShQAQpLCjK0gkAOIFWGNeGtJHjcRKHA2aR9BT45hKOxKIljBDiZiqcRyAmmySqV2aJSYpksnykdl0CDc3n0YL-QElC6iqqgyUy+WK5VqjVsOG8BFtUAdeQGo0ms0W-XWklLHrDZi6K3SbQ2SSe728jZ+7YBoPeej5NLJNKFCG5ADqkcT6vymq4aZ17T1OeNklNcwLVuJ4wUCnR83kgzd7vr3kbfmbnJ5u+u2wg5DI+Cg9DBrw7qTKCRBkUh0IKyYaY5ahERk9tDtUODSJSdj6uW4i6EupKqMwgGKEoijMCaZKMh4zINmyB6Bke6wntQZ4XleN5ZHecQPsUaT-O+qZfj+mYSP+gHAfYXRLBBNr2HMOBaOBfQbto5o7qyTYBIeDYYGAuGEPhQTXrenakY+wQURkVFauO34ZqI9HiABQH4sxYFsSSdq2Dg5IWoSmiKDY+jSIJvr7iJWFiRJp7njJRFxCRZEXM+r75Kpn7prqf46Yx+mgaxkH9DgwHKDZGgrha9l7lsTk4GQRz0N8vylGKOV-ACQLiiUELQkq0oqsOo5NOptFaQg2aGjOc7mpaRYSO6hoWho5YKBYHooasu4YelmWPJEAAKd6-AVJRVTCKZqTRmlZvqzV5vO7WQeIBIwdIqhVjWZLDHMNgpaNHKBuN9BTTNhQlLkpS5BcsqJOGRE1dqGkhU1uazvm23saouLolYxoWCuahKEoF3CVdGWHGAqX+gwX11atpgOpM1KnYdmIVqoO06LokwyGYh2SJo2KqJocOOQj40o5hTDiB+tUrSF1jYyoB02E4VaSITxOSD03Q0tTQFujo9NpYzSM4LAJDuXc9CTWk6qFLkCRFHKkTBMExWPWkMqBRzwW-rtFjosMQv0rSpqaDtVMAXauKYrO8HMDpsuo9dCtK+J5DhDJIhK+eyPkAAZucGAABTiVH4mwAAFgAlPy6Hwy2TOB2AwdBOjnOW4SpNC+6qICxuxMooBZgQ+aB2WO4qH4NwEBwMIPrURbdEIAAtDYkH9wagxj97tjDCuVZuKhPp4EQ2eQD3E59+oO3GmZli7dYnoMn0dNz1nDOBJeexRDEK8-b+-U9OXwyWVoLFE+x5Y4KaVjUkaihC7TvvNrsMI4QDhHBOGcS4Nx7hgCvvVDoBJIKDH2osKsVYKzgUGP-JyMDMYICsqTf6rUFwdQQCDGCcUpC6BNPIOyR8RpL2ct4bBIVySaA2gDLahZIKelLFSU0iE5hkkwQjbCuBJLSUvEwy2UhWF9EUMoLQbpJ4IJcJMICdpdrQ12kNNCdCT6iWPKeSRfddr80NBWPEQE5jYhsAoaKzgcDYkcKLZx28aHDSEnohhQkxFGIaiYtENkrSWF0FYqeO18QGjxBoQ69J6Solnu4hycsWwiJwOJMR7kJHLV7n46R795BJQUTIJwdiAICKAsaA+kh7RCJzkjXxHQWFsMIUDYyeI0RWmsALBCNZYa0I8ckzkTNLoBgaRIDQrCyQ2AZPpPhSga4ATUIMV0CgyT4nOv0pJftEZHEVsrMgdwxm2ngq7cxVp+aU3AiLNE9cG5Wmgm4nRAztm5xIEHEOWSgqrz8ScriZzbBVyuexHQrDyZWE9M4TQ+hD7uCAA */ createMachine( { context: initialContext, @@ -281,7 +281,10 @@ export const createPureLogStreamQueryStateMachine = ( updateTimeContextFromTimeRangeUpdate, updateTimeContextFromRefreshIntervalUpdate, refreshTime: send({ type: 'UPDATE_TIME_RANGE', timeRange: DEFAULT_REFRESH_TIME_RANGE }), - expandPageEnd: send({ type: 'UPDATE_TIME_RANGE', timeRange: { to: 'now' } }), + expandPageEnd: send((context) => ({ + type: 'UPDATE_TIME_RANGE', + timeRange: { to: context.timeRange.to }, + })), updateTimeContextFromUrl, }, guards: { diff --git a/x-pack/plugins/infra/public/observability_logs/log_stream_query_state/src/time_filter_state_service.ts b/x-pack/plugins/infra/public/observability_logs/log_stream_query_state/src/time_filter_state_service.ts index 3bc2adcb79315..c7ce0ac8b9c18 100644 --- a/x-pack/plugins/infra/public/observability_logs/log_stream_query_state/src/time_filter_state_service.ts +++ b/x-pack/plugins/infra/public/observability_logs/log_stream_query_state/src/time_filter_state_service.ts @@ -143,7 +143,7 @@ export const updateTimeContextFromRefreshIntervalUpdate = actions.assign( ? { timestamps: { startTimestamp: datemathToEpochMillis(DEFAULT_REFRESH_TIME_RANGE.from, 'down') ?? 0, - endTimestamp: datemathToEpochMillis(DEFAULT_REFRESH_TIME_RANGE.to, 'down') ?? 0, + endTimestamp: datemathToEpochMillis(DEFAULT_REFRESH_TIME_RANGE.to, 'up') ?? 0, lastChangedTimestamp: nowTimestamp, }, } @@ -170,7 +170,7 @@ const getTimeFromEvent = (context: LogStreamQueryContext, event: LogStreamQueryE ? datemathToEpochMillis(from, 'down') : context.timestamps.startTimestamp; const toTimestamp = event.timeRange?.to - ? datemathToEpochMillis(to, 'down') + ? datemathToEpochMillis(to, 'up') : context.timestamps.endTimestamp; return { diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_log_entry_category_datasets.ts b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_log_entry_category_datasets.ts index 51d4a69ada295..34358b983e12b 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_log_entry_category_datasets.ts +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_log_entry_category_datasets.ts @@ -31,7 +31,7 @@ export const callGetLogEntryCategoryDatasetsAPI = async ( body: JSON.stringify( getLogEntryCategoryDatasetsRequestPayloadRT.encode({ data: { - sourceId, + logView: { type: 'log-view-reference', logViewId: sourceId }, timeRange: { startTime, endTime, diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_log_entry_category_examples.ts b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_log_entry_category_examples.ts index 90727fd6f0853..e3b99750af715 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_log_entry_category_examples.ts +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_log_entry_category_examples.ts @@ -35,7 +35,7 @@ export const callGetLogEntryCategoryExamplesAPI = async ( data: { categoryId, exampleCount, - sourceId, + logView: { type: 'log-view-reference', logViewId: sourceId }, timeRange: { startTime, endTime, diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_top_log_entry_categories.ts b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_top_log_entry_categories.ts index 9472991e15c66..93e9daf0b9cb6 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_top_log_entry_categories.ts +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_top_log_entry_categories.ts @@ -36,7 +36,7 @@ export const callGetTopLogEntryCategoriesAPI = async ( body: JSON.stringify( getLogEntryCategoriesRequestPayloadRT.encode({ data: { - sourceId, + logView: { type: 'log-view-reference', logViewId: sourceId }, timeRange: { startTime, endTime, diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_anomalies.ts b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_anomalies.ts index 5f0f95a3e7976..7916cad0f1e07 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_anomalies.ts +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_anomalies.ts @@ -30,7 +30,7 @@ export const callGetLogEntryAnomaliesAPI = async (requestArgs: RequestArgs, fetc body: JSON.stringify( getLogEntryAnomaliesRequestPayloadRT.encode({ data: { - sourceId, + logView: { type: 'log-view-reference', logViewId: sourceId }, timeRange: { startTime, endTime, diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_anomalies_datasets.ts b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_anomalies_datasets.ts index 9b560845186f7..16a8092f290f8 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_anomalies_datasets.ts +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_anomalies_datasets.ts @@ -29,7 +29,7 @@ export const callGetLogEntryAnomaliesDatasetsAPI = async ( body: JSON.stringify( getLogEntryAnomaliesDatasetsRequestPayloadRT.encode({ data: { - sourceId, + logView: { type: 'log-view-reference', logViewId: sourceId }, timeRange: { startTime, endTime, diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_examples.ts b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_examples.ts index 5844e00ebfe4e..0e44e5b02feb7 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_examples.ts +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_examples.ts @@ -32,7 +32,7 @@ export const callGetLogEntryExamplesAPI = async (requestArgs: RequestArgs, fetch data: { dataset, exampleCount, - sourceId, + logView: { type: 'log-view-reference', logViewId: sourceId }, timeRange: { startTime, endTime, diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpi_charts/tile.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpi_charts/tile.tsx index ed5d0f5831ca6..1b86c5a543d06 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpi_charts/tile.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpi_charts/tile.tsx @@ -22,6 +22,7 @@ export const Tile = ({ type, ...props }: Props) => { metrics: [{ type }], groupBy: null, includeTimeseries: true, + dropPartialBuckets: false, }); return ; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/alerts/alerts_tab_content.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/alerts/alerts_tab_content.tsx index cb31f2c5e9102..7933d3bdb9f3c 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/alerts/alerts_tab_content.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/alerts/alerts_tab_content.tsx @@ -49,7 +49,7 @@ export const AlertsTabContent = () => { return ( - + diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/alerts_tab_badge.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/alerts_tab_badge.tsx index 534d28e671fe3..1333c5b5c3439 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/alerts_tab_badge.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/alerts_tab_badge.tsx @@ -40,7 +40,11 @@ export const AlertsTabBadge = () => { typeof alertsCount?.activeAlertCount === 'number' && alertsCount.activeAlertCount > 0; return shouldRenderBadge ? ( - + {alertsCount?.activeAlertCount} ) : null; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx index 558a7bac920b7..2db824f9c01d7 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx @@ -163,17 +163,17 @@ export const useHostsTable = (nodes: SnapshotNode[], { time }: HostTableParams) align: 'right', }, { - name: averageTXLabel, - field: 'tx.avg', + name: averageRXLabel, + field: 'rx.avg', sortable: true, - render: (avg: number) => formatMetric('tx', avg), + render: (avg: number) => formatMetric('rx', avg), align: 'right', }, { - name: averageRXLabel, - field: 'rx.avg', + name: averageTXLabel, + field: 'tx.avg', sortable: true, - render: (avg: number) => formatMetric('rx', avg), + render: (avg: number) => formatMetric('tx', avg), align: 'right', }, { diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_snaphot.ts b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_snaphot.ts index 8818fac0e39ee..ae39a5f03ea7e 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_snaphot.ts +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_snaphot.ts @@ -34,6 +34,7 @@ export function useSnapshot({ groupBy = null, sendRequestImmediately = true, includeTimeseries = true, + dropPartialBuckets = true, requestTs, ...args }: UseSnapshotRequest) { @@ -56,6 +57,7 @@ export function useSnapshot({ lookbackSize: 5, }, includeTimeseries, + dropPartialBuckets, }; const { error, loading, response, makeRequest } = useHTTPRequest( diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/kuery_bar.tsx b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/kuery_bar.tsx index 0e7d4e6d41e1b..ef2ca1e842ed4 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/kuery_bar.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/kuery_bar.tsx @@ -28,6 +28,7 @@ interface Props { value?: string | null; placeholder?: string; curryLoadSuggestions?: CurryLoadSuggestionsType; + compressed?: boolean; } function validateQuery(query: string) { @@ -46,6 +47,7 @@ export const MetricsExplorerKueryBar = ({ value, placeholder, curryLoadSuggestions = defaultCurryLoadSuggestions, + compressed, }: Props) => { const [draftQuery, setDraftQuery] = useState(value || ''); const [isValid, setValidation] = useState(true); @@ -81,6 +83,7 @@ export const MetricsExplorerKueryBar = ({ {({ isLoadingSuggestions, loadSuggestions, suggestions }) => ( { + it('flattens multi level item', () => { + const data = { + key1: { + item1: 'value 1', + item2: { itemA: 'value 2' }, + }, + key2: { + item3: { itemA: { itemAB: 'value AB' } }, + item4: 'value 4', + }, + }; + + const flatten = flattenObject(data); + expect(flatten).toEqual({ + 'key2.item3.itemA.itemAB': 'value AB', + 'key2.item4': 'value 4', + 'key1.item1': 'value 1', + 'key1.item2.itemA': 'value 2', + }); + }); + + it('does not flatten an array item', () => { + const data = { + key1: { + item1: 'value 1', + item2: { itemA: 'value 2' }, + }, + key2: { + item3: { itemA: { itemAB: 'value AB' } }, + item4: 'value 4', + item5: [1], + item6: { itemA: [1, 2, 3] }, + }, + key3: ['item7', 'item8'], + }; + + const flatten = flattenObject(data); + expect(flatten).toEqual({ + key3: ['item7', 'item8'], + 'key2.item3.itemA.itemAB': 'value AB', + 'key2.item4': 'value 4', + 'key2.item5': [1], + 'key2.item6.itemA': [1, 2, 3], + 'key1.item1': 'value 1', + 'key1.item2.itemA': 'value 2', + }); + }); +}); diff --git a/x-pack/plugins/infra/server/lib/alerting/common/utils.ts b/x-pack/plugins/infra/server/lib/alerting/common/utils.ts index d59192306874a..01c69d4f5d126 100644 --- a/x-pack/plugins/infra/server/lib/alerting/common/utils.ts +++ b/x-pack/plugins/infra/server/lib/alerting/common/utils.ts @@ -226,18 +226,7 @@ export const shouldTermsAggOnContainer = (groupBy: string | string[] | undefined export const flattenAdditionalContext = ( additionalContext: AdditionalContext | undefined | null ): AdditionalContext => { - let flattenedContext: AdditionalContext = {}; - if (additionalContext) { - Object.keys(additionalContext).forEach((context: string) => { - if (additionalContext[context]) { - flattenedContext = { - ...flattenedContext, - ...flattenObject(additionalContext[context], [context + '.']), - }; - } - }); - } - return flattenedContext; + return additionalContext ? flattenObject(additionalContext) : {}; }; export const getContextForRecoveredAlerts = ( @@ -261,39 +250,24 @@ export const unflattenObject = (object: ob return acc; }, {} as T); -/** - * Wrap the key with [] if it is a key from an Array - * @param key The object key - * @param isArrayItem Flag to indicate if it is the key of an Array - */ -const renderKey = (key: string, isArrayItem: boolean): string => (isArrayItem ? `[${key}]` : key); - -export const flattenObject = ( - obj: AdditionalContext, - prefix: string[] = [], - isArrayItem = false -): AdditionalContext => - Object.keys(obj).reduce((acc, k) => { - const nextValue = obj[k]; - - if (typeof nextValue === 'object' && nextValue !== null) { - const isNextValueArray = Array.isArray(nextValue); - const dotSuffix = isNextValueArray ? '' : '.'; - - if (Object.keys(nextValue).length > 0) { - return { - ...acc, - ...flattenObject( - nextValue, - [...prefix, `${renderKey(k, isArrayItem)}${dotSuffix}`], - isNextValueArray - ), - }; +export const flattenObject = (obj: AdditionalContext, prefix: string = ''): AdditionalContext => + Object.keys(obj).reduce((acc, key) => { + const nextValue = obj[key]; + + if (nextValue) { + if (typeof nextValue === 'object' && !Array.isArray(nextValue)) { + const dotSuffix = '.'; + if (Object.keys(nextValue).length > 0) { + return { + ...acc, + ...flattenObject(nextValue, `${prefix}${key}${dotSuffix}`), + }; + } } - } - const fullPath = `${prefix.join('')}${renderKey(k, isArrayItem)}`; - acc[fullPath] = nextValue; + const fullPath = `${prefix}${key}`; + acc[fullPath] = nextValue; + } return acc; }, {}); diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.test.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.test.ts new file mode 100644 index 0000000000000..63b0c81108a47 --- /dev/null +++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.test.ts @@ -0,0 +1,301 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + AlertInstanceContext as AlertContext, + AlertInstanceState as AlertState, +} from '@kbn/alerting-plugin/server'; +import { + AlertInstanceMock, + RuleExecutorServicesMock, + alertsMock, +} from '@kbn/alerting-plugin/server/mocks'; +import { LifecycleAlertServices } from '@kbn/rule-registry-plugin/server'; +import { ruleRegistryMocks } from '@kbn/rule-registry-plugin/server/mocks'; +import { createLifecycleRuleExecutorMock } from '@kbn/rule-registry-plugin/server/utils/create_lifecycle_rule_executor_mock'; +import { + Aggregators, + Comparator, + InventoryMetricConditions, +} from '../../../../common/alerting/metrics'; + +import type { LogMeta, Logger } from '@kbn/logging'; +import { DEFAULT_FLAPPING_SETTINGS } from '@kbn/alerting-plugin/common'; +import { createInventoryMetricThresholdExecutor } from './inventory_metric_threshold_executor'; +import { ConditionResult } from './evaluate_condition'; +import { InfraBackendLibs } from '../../infra_types'; +import { infraPluginMock } from '../../../mocks'; + +jest.mock('./evaluate_condition', () => ({ evaluateCondition: jest.fn() })); + +interface AlertTestInstance { + instance: AlertInstanceMock; + actionQueue: any[]; + state: any; +} + +const persistAlertInstances = false; + +const fakeLogger = (msg: string, meta?: Meta) => {}; + +const logger = { + trace: fakeLogger, + debug: fakeLogger, + info: fakeLogger, + warn: fakeLogger, + error: fakeLogger, + fatal: fakeLogger, + log: () => void 0, + get: () => logger, +} as unknown as Logger; + +const mockOptions = { + executionId: '', + startedAt: new Date(), + previousStartedAt: null, + spaceId: '', + rule: { + id: '', + name: '', + tags: [], + consumer: '', + enabled: true, + schedule: { + interval: '1h', + }, + actions: [], + createdBy: null, + updatedBy: null, + createdAt: new Date(), + updatedAt: new Date(), + throttle: null, + notifyWhen: null, + producer: '', + ruleTypeId: '', + ruleTypeName: '', + muteAll: false, + }, + logger, + flappingSettings: DEFAULT_FLAPPING_SETTINGS, +}; + +const setEvaluationResults = (response: Record) => { + jest.requireMock('./evaluate_condition').evaluateCondition.mockImplementation(() => response); +}; +const createMockStaticConfiguration = (sources: any) => ({ + alerting: { + inventory_threshold: { + group_by_page_size: 100, + }, + metric_threshold: { + group_by_page_size: 100, + }, + }, + inventory: { + compositeSize: 2000, + }, + sources, +}); + +const mockLibs = { + sources: { + getSourceConfiguration: (savedObjectsClient: any, sourceId: string) => { + return Promise.resolve({ + id: sourceId, + configuration: { + logIndices: { + type: 'index_pattern', + indexPatternId: 'some-id', + }, + }, + }); + }, + }, + getStartServices: () => [ + null, + infraPluginMock.createSetupContract(), + infraPluginMock.createStartContract(), + ], + configuration: createMockStaticConfiguration({}), + metricsRules: { + createLifecycleRuleExecutor: createLifecycleRuleExecutorMock, + }, + basePath: { + publicBaseUrl: 'http://localhost:5601', + prepend: (path: string) => path, + }, + logger, +} as unknown as InfraBackendLibs; + +const alertsServices = alertsMock.createRuleExecutorServices(); +const services: RuleExecutorServicesMock & + LifecycleAlertServices = { + ...alertsServices, + ...ruleRegistryMocks.createLifecycleAlertServices(alertsServices), +}; + +const alertInstances = new Map(); + +services.alertFactory.create.mockImplementation((instanceID: string) => { + const newAlertInstance: AlertTestInstance = { + instance: alertsMock.createAlertFactory.create(), + actionQueue: [], + state: {}, + }; + + const alertInstance: AlertTestInstance = persistAlertInstances + ? alertInstances.get(instanceID) || newAlertInstance + : newAlertInstance; + alertInstances.set(instanceID, alertInstance); + + alertInstance.instance.scheduleActions.mockImplementation((id: string, action: any) => { + alertInstance.actionQueue.push({ id, action }); + return alertInstance.instance; + }); + + return alertInstance.instance; +}); + +function mostRecentAction(id: string) { + const instance = alertInstances.get(id); + if (!instance) return undefined; + return instance.actionQueue.pop(); +} + +function clearInstances() { + alertInstances.clear(); +} + +const executor = createInventoryMetricThresholdExecutor(mockLibs); + +const baseCriterion = { + aggType: Aggregators.AVERAGE, + metric: 'count', + timeSize: 1, + timeUnit: 'm', + threshold: [0], + comparator: Comparator.GT, +} as InventoryMetricConditions; + +describe('The inventory threshold alert type', () => { + describe('querying with Hosts and rule tags', () => { + afterAll(() => clearInstances()); + const execute = (comparator: Comparator, threshold: number[], state?: any) => + executor({ + ...mockOptions, + services, + params: { + nodeType: 'host', + criteria: [ + { + ...baseCriterion, + comparator, + threshold, + }, + ], + }, + state: state ?? {}, + rule: { + ...mockOptions.rule, + tags: ['ruleTag1', 'ruleTag2'], + }, + }); + + const instanceIdA = 'host-01'; + const instanceIdB = 'host-02'; + + test('when tags are present in the source, rule tags and source tags are combined in alert context', async () => { + setEvaluationResults({ + 'host-01': { + ...baseCriterion, + metric: 'count', + timeSize: 1, + timeUnit: 'm', + threshold: [0.75], + comparator: Comparator.GT, + shouldFire: true, + shouldWarn: false, + currentValue: 1.0, + isNoData: false, + isError: false, + context: { + tags: ['host-01_tag1', 'host-01_tag2'], + }, + }, + 'host-02': { + ...baseCriterion, + metric: 'count', + timeSize: 1, + timeUnit: 'm', + threshold: [0.75], + comparator: Comparator.GT, + shouldFire: true, + shouldWarn: false, + currentValue: 1.0, + isNoData: false, + isError: false, + context: { + tags: ['host-02_tag1', 'host-02_tag2'], + }, + }, + }); + await execute(Comparator.GT, [0.75]); + expect(mostRecentAction(instanceIdA).action.tags).toStrictEqual([ + 'host-01_tag1', + 'host-01_tag2', + 'ruleTag1', + 'ruleTag2', + ]); + expect(mostRecentAction(instanceIdB).action.tags).toStrictEqual([ + 'host-02_tag1', + 'host-02_tag2', + 'ruleTag1', + 'ruleTag2', + ]); + }); + + test('when tags are NOT present in the source, rule tags are added in alert context', async () => { + setEvaluationResults({ + 'host-01': { + ...baseCriterion, + metric: 'count', + timeSize: 1, + timeUnit: 'm', + threshold: [0.75], + comparator: Comparator.GT, + shouldFire: true, + shouldWarn: false, + currentValue: 1.0, + isNoData: false, + isError: false, + context: { + cloud: undefined, + }, + }, + 'host-02': { + ...baseCriterion, + metric: 'count', + timeSize: 1, + timeUnit: 'm', + threshold: [0.75], + comparator: Comparator.GT, + shouldFire: true, + shouldWarn: false, + currentValue: 1.0, + isNoData: false, + isError: false, + context: { + tags: undefined, + }, + }, + }); + await execute(Comparator.GT, [0.75]); + expect(mostRecentAction(instanceIdA).action.tags).toStrictEqual(['ruleTag1', 'ruleTag2']); + expect(mostRecentAction(instanceIdB).action.tags).toStrictEqual(['ruleTag1', 'ruleTag2']); + }); + }); +}); diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts index ffd0a7e563339..bd033b5285b3e 100644 --- a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts @@ -76,176 +76,221 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = InventoryMetricThresholdAlertState, InventoryMetricThresholdAlertContext, InventoryMetricThresholdAllowedActionGroups - >(async ({ services, params, executionId, spaceId, startedAt, rule: { id: ruleId } }) => { - const startTime = Date.now(); - - const { criteria, filterQuery, sourceId = 'default', nodeType, alertOnNoData } = params; + >( + async ({ + services, + params, + executionId, + spaceId, + startedAt, + rule: { id: ruleId, tags: ruleTags }, + }) => { + const startTime = Date.now(); - if (criteria.length === 0) throw new Error('Cannot execute an alert with 0 conditions'); + const { criteria, filterQuery, sourceId = 'default', nodeType, alertOnNoData } = params; - const logger = createScopedLogger(libs.logger, 'inventoryRule', { - alertId: ruleId, - executionId, - }); + if (criteria.length === 0) throw new Error('Cannot execute an alert with 0 conditions'); - const esClient = services.scopedClusterClient.asCurrentUser; - - const { - alertWithLifecycle, - savedObjectsClient, - getAlertStartedDate, - getAlertUuid, - getAlertByAlertUuid, - } = services; - const alertFactory: InventoryMetricThresholdAlertFactory = ( - id, - reason, - actionGroup, - additionalContext - ) => - alertWithLifecycle({ - id, - fields: { - [ALERT_REASON]: reason, - [ALERT_ACTION_GROUP]: actionGroup, - ...flattenAdditionalContext(additionalContext), - }, + const logger = createScopedLogger(libs.logger, 'inventoryRule', { + alertId: ruleId, + executionId, }); - if (!params.filterQuery && params.filterQueryText) { - try { - const { fromKueryExpression } = await import('@kbn/es-query'); - fromKueryExpression(params.filterQueryText); - } catch (e) { - logger.error(e.message); - const actionGroupId = FIRED_ACTIONS.id; // Change this to an Error action group when able - const reason = buildInvalidQueryAlertReason(params.filterQueryText); - const alert = alertFactory(UNGROUPED_FACTORY_KEY, reason, actionGroupId); - const indexedStartedDate = - getAlertStartedDate(UNGROUPED_FACTORY_KEY) ?? startedAt.toISOString(); - const alertUuid = getAlertUuid(UNGROUPED_FACTORY_KEY); - - alert.scheduleActions(actionGroupId, { - alertDetailsUrl: getAlertDetailsUrl(libs.basePath, spaceId, alertUuid), - alertState: stateToAlertMessage[AlertStates.ERROR], - group: UNGROUPED_FACTORY_KEY, - metric: mapToConditionsLookup(criteria, (c) => c.metric), - reason, - timestamp: startedAt.toISOString(), - value: null, - viewInAppUrl: getViewInInventoryAppUrl({ - basePath: libs.basePath, - criteria, - nodeType, - timestamp: indexedStartedDate, - spaceId, - }), + const esClient = services.scopedClusterClient.asCurrentUser; + + const { + alertWithLifecycle, + savedObjectsClient, + getAlertStartedDate, + getAlertUuid, + getAlertByAlertUuid, + } = services; + const alertFactory: InventoryMetricThresholdAlertFactory = ( + id, + reason, + actionGroup, + additionalContext + ) => + alertWithLifecycle({ + id, + fields: { + [ALERT_REASON]: reason, + [ALERT_ACTION_GROUP]: actionGroup, + ...flattenAdditionalContext(additionalContext), + }, }); - return { state: {} }; + if (!params.filterQuery && params.filterQueryText) { + try { + const { fromKueryExpression } = await import('@kbn/es-query'); + fromKueryExpression(params.filterQueryText); + } catch (e) { + logger.error(e.message); + const actionGroupId = FIRED_ACTIONS.id; // Change this to an Error action group when able + const reason = buildInvalidQueryAlertReason(params.filterQueryText); + const alert = alertFactory(UNGROUPED_FACTORY_KEY, reason, actionGroupId); + const indexedStartedDate = + getAlertStartedDate(UNGROUPED_FACTORY_KEY) ?? startedAt.toISOString(); + const alertUuid = getAlertUuid(UNGROUPED_FACTORY_KEY); + + alert.scheduleActions(actionGroupId, { + alertDetailsUrl: getAlertDetailsUrl(libs.basePath, spaceId, alertUuid), + alertState: stateToAlertMessage[AlertStates.ERROR], + group: UNGROUPED_FACTORY_KEY, + metric: mapToConditionsLookup(criteria, (c) => c.metric), + reason, + timestamp: startedAt.toISOString(), + value: null, + viewInAppUrl: getViewInInventoryAppUrl({ + basePath: libs.basePath, + criteria, + nodeType, + timestamp: indexedStartedDate, + spaceId, + }), + }); + + return { state: {} }; + } } - } - const source = await libs.sources.getSourceConfiguration(savedObjectsClient, sourceId); - - const [, , { logViews }] = await libs.getStartServices(); - const logQueryFields: LogQueryFields | undefined = await logViews - .getClient(savedObjectsClient, esClient) - .getResolvedLogView(sourceId) - .then( - ({ indices }) => ({ indexPattern: indices }), - () => undefined - ); - - const compositeSize = libs.configuration.alerting.inventory_threshold.group_by_page_size; - const results = await Promise.all( - criteria.map((condition) => - evaluateCondition({ - compositeSize, - condition, - esClient, - executionTimestamp: startedAt, - filterQuery, - logger, - logQueryFields, - nodeType, - source, + const source = await libs.sources.getSourceConfiguration(savedObjectsClient, sourceId); + + const [, , { logViews }] = await libs.getStartServices(); + const logQueryFields: LogQueryFields | undefined = await logViews + .getClient(savedObjectsClient, esClient) + .getResolvedLogView({ + type: 'log-view-reference', + logViewId: sourceId, }) - ) - ); + .then( + ({ indices }) => ({ indexPattern: indices }), + () => undefined + ); + + const compositeSize = libs.configuration.alerting.inventory_threshold.group_by_page_size; + const results = await Promise.all( + criteria.map((condition) => + evaluateCondition({ + compositeSize, + condition, + esClient, + executionTimestamp: startedAt, + filterQuery, + logger, + logQueryFields, + nodeType, + source, + }) + ) + ); - let scheduledActionsCount = 0; - const inventoryItems = Object.keys(first(results)!); - for (const group of inventoryItems) { - // AND logic; all criteria must be across the threshold - const shouldAlertFire = results.every((result) => result[group]?.shouldFire); - const shouldAlertWarn = results.every((result) => result[group]?.shouldWarn); - // AND logic; because we need to evaluate all criteria, if one of them reports no data then the - // whole alert is in a No Data/Error state - const isNoData = results.some((result) => result[group]?.isNoData); - const isError = results.some((result) => result[group]?.isError); - - const nextState = isError - ? AlertStates.ERROR - : isNoData - ? AlertStates.NO_DATA - : shouldAlertFire - ? AlertStates.ALERT - : shouldAlertWarn - ? AlertStates.WARNING - : AlertStates.OK; - let reason; - if (nextState === AlertStates.ALERT || nextState === AlertStates.WARNING) { - reason = results - .map((result) => - buildReasonWithVerboseMetricName( - group, - result[group], - buildFiredAlertReason, - nextState === AlertStates.WARNING - ) - ) - .join('\n'); - } - if (alertOnNoData) { - if (nextState === AlertStates.NO_DATA) { + let scheduledActionsCount = 0; + const inventoryItems = Object.keys(first(results)!); + for (const group of inventoryItems) { + // AND logic; all criteria must be across the threshold + const shouldAlertFire = results.every((result) => result[group]?.shouldFire); + const shouldAlertWarn = results.every((result) => result[group]?.shouldWarn); + // AND logic; because we need to evaluate all criteria, if one of them reports no data then the + // whole alert is in a No Data/Error state + const isNoData = results.some((result) => result[group]?.isNoData); + const isError = results.some((result) => result[group]?.isError); + + const nextState = isError + ? AlertStates.ERROR + : isNoData + ? AlertStates.NO_DATA + : shouldAlertFire + ? AlertStates.ALERT + : shouldAlertWarn + ? AlertStates.WARNING + : AlertStates.OK; + let reason; + if (nextState === AlertStates.ALERT || nextState === AlertStates.WARNING) { reason = results - .filter((result) => result[group].isNoData) .map((result) => - buildReasonWithVerboseMetricName(group, result[group], buildNoDataAlertReason) - ) - .join('\n'); - } else if (nextState === AlertStates.ERROR) { - reason = results - .filter((result) => result[group].isError) - .map((result) => - buildReasonWithVerboseMetricName(group, result[group], buildErrorAlertReason) + buildReasonWithVerboseMetricName( + group, + result[group], + buildFiredAlertReason, + nextState === AlertStates.WARNING + ) ) .join('\n'); } + if (alertOnNoData) { + if (nextState === AlertStates.NO_DATA) { + reason = results + .filter((result) => result[group].isNoData) + .map((result) => + buildReasonWithVerboseMetricName(group, result[group], buildNoDataAlertReason) + ) + .join('\n'); + } else if (nextState === AlertStates.ERROR) { + reason = results + .filter((result) => result[group].isError) + .map((result) => + buildReasonWithVerboseMetricName(group, result[group], buildErrorAlertReason) + ) + .join('\n'); + } + } + if (reason) { + const actionGroupId = + nextState === AlertStates.WARNING ? WARNING_ACTIONS_ID : FIRED_ACTIONS_ID; + + const additionalContext = results && results.length > 0 ? results[0][group].context : {}; + additionalContext.tags = Array.from( + new Set([...(additionalContext.tags ?? []), ...ruleTags]) + ); + + const alert = alertFactory(group, reason, actionGroupId, additionalContext); + const indexedStartedDate = getAlertStartedDate(group) ?? startedAt.toISOString(); + const alertUuid = getAlertUuid(group); + + scheduledActionsCount++; + + const context = { + alertDetailsUrl: getAlertDetailsUrl(libs.basePath, spaceId, alertUuid), + alertState: stateToAlertMessage[nextState], + group, + reason, + metric: mapToConditionsLookup(criteria, (c) => c.metric), + timestamp: startedAt.toISOString(), + threshold: mapToConditionsLookup(criteria, (c) => c.threshold), + value: mapToConditionsLookup(results, (result) => + formatMetric(result[group].metric, result[group].currentValue) + ), + viewInAppUrl: getViewInInventoryAppUrl({ + basePath: libs.basePath, + criteria, + nodeType, + timestamp: indexedStartedDate, + spaceId, + }), + ...additionalContext, + }; + alert.scheduleActions(actionGroupId, context); + } } - if (reason) { - const actionGroupId = - nextState === AlertStates.WARNING ? WARNING_ACTIONS_ID : FIRED_ACTIONS_ID; - - const additionalContext = results && results.length > 0 ? results[0][group].context : null; - const alert = alertFactory(group, reason, actionGroupId, additionalContext); - const indexedStartedDate = getAlertStartedDate(group) ?? startedAt.toISOString(); - const alertUuid = getAlertUuid(group); + const { getRecoveredAlerts } = services.alertFactory.done(); + const recoveredAlerts = getRecoveredAlerts(); - scheduledActionsCount++; + for (const alert of recoveredAlerts) { + const recoveredAlertId = alert.getId(); + const indexedStartedDate = getAlertStartedDate(recoveredAlertId) ?? startedAt.toISOString(); + const alertUuid = getAlertUuid(recoveredAlertId); + const alertHits = alertUuid ? await getAlertByAlertUuid(alertUuid) : undefined; + const additionalContext = getContextForRecoveredAlerts(alertHits); + const originalActionGroup = getOriginalActionGroup(alertHits); - const context = { + alert.setContext({ alertDetailsUrl: getAlertDetailsUrl(libs.basePath, spaceId, alertUuid), - alertState: stateToAlertMessage[nextState], - group, - reason, + alertState: stateToAlertMessage[AlertStates.OK], + group: recoveredAlertId, metric: mapToConditionsLookup(criteria, (c) => c.metric), - timestamp: startedAt.toISOString(), threshold: mapToConditionsLookup(criteria, (c) => c.threshold), - value: mapToConditionsLookup(results, (result) => - formatMetric(result[group].metric, result[group].currentValue) - ), + timestamp: startedAt.toISOString(), viewInAppUrl: getViewInInventoryAppUrl({ basePath: libs.basePath, criteria, @@ -253,49 +298,19 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = timestamp: indexedStartedDate, spaceId, }), + originalAlertState: translateActionGroupToAlertState(originalActionGroup), + originalAlertStateWasALERT: originalActionGroup === FIRED_ACTIONS_ID, + originalAlertStateWasWARNING: originalActionGroup === WARNING_ACTIONS_ID, ...additionalContext, - }; - alert.scheduleActions(actionGroupId, context); + }); } - } - const { getRecoveredAlerts } = services.alertFactory.done(); - const recoveredAlerts = getRecoveredAlerts(); - - for (const alert of recoveredAlerts) { - const recoveredAlertId = alert.getId(); - const indexedStartedDate = getAlertStartedDate(recoveredAlertId) ?? startedAt.toISOString(); - const alertUuid = getAlertUuid(recoveredAlertId); - const alertHits = alertUuid ? await getAlertByAlertUuid(alertUuid) : undefined; - const additionalContext = getContextForRecoveredAlerts(alertHits); - const originalActionGroup = getOriginalActionGroup(alertHits); - - alert.setContext({ - alertDetailsUrl: getAlertDetailsUrl(libs.basePath, spaceId, alertUuid), - alertState: stateToAlertMessage[AlertStates.OK], - group: recoveredAlertId, - metric: mapToConditionsLookup(criteria, (c) => c.metric), - threshold: mapToConditionsLookup(criteria, (c) => c.threshold), - timestamp: startedAt.toISOString(), - viewInAppUrl: getViewInInventoryAppUrl({ - basePath: libs.basePath, - criteria, - nodeType, - timestamp: indexedStartedDate, - spaceId, - }), - originalAlertState: translateActionGroupToAlertState(originalActionGroup), - originalAlertStateWasALERT: originalActionGroup === FIRED_ACTIONS_ID, - originalAlertStateWasWARNING: originalActionGroup === WARNING_ACTIONS_ID, - ...additionalContext, - }); - } + const stopTime = Date.now(); + logger.debug(`Scheduled ${scheduledActionsCount} actions in ${stopTime - startTime}ms`); - const stopTime = Date.now(); - logger.debug(`Scheduled ${scheduledActionsCount} actions in ${stopTime - startTime}ms`); - - return { state: {} }; - }); + return { state: {} }; + } + ); const formatThreshold = (metric: SnapshotMetricType, value: number | number[]) => { const metricFormatter = get(METRIC_FORMATTERS, metric, METRIC_FORMATTERS.count); diff --git a/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts index 37eb4698ff089..1d0470c244fd1 100644 --- a/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts @@ -188,7 +188,7 @@ export const createLogThresholdExecutor = (libs: InfraBackendLibs) => const { indices, timestampField, runtimeMappings } = await logViews .getClient(savedObjectsClient, scopedClusterClient.asCurrentUser) - .getResolvedLogView(validatedParams.logView.logViewId); + .getResolvedLogView(validatedParams.logView); if (!isRatioRuleParams(validatedParams)) { await executeAlert( diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts index a94521c22bde4..f363b5baeabf1 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts @@ -690,6 +690,143 @@ describe('The metric threshold alert type', () => { }); }); + describe('querying with a groupBy parameter host.name and rule tags', () => { + afterAll(() => clearInstances()); + const execute = ( + comparator: Comparator, + threshold: number[], + groupBy: string[] = ['host.name'], + metric?: string, + state?: any + ) => + executor({ + ...mockOptions, + services, + params: { + groupBy, + criteria: [ + { + ...baseNonCountCriterion, + comparator, + threshold, + metric: metric ?? baseNonCountCriterion.metric, + }, + ], + }, + state: state ?? mockOptions.state.wrapped, + rule: { + ...mockOptions.rule, + tags: ['ruleTag1', 'ruleTag2'], + }, + }); + const instanceIdA = 'host-01'; + const instanceIdB = 'host-02'; + + test('rule tags and source tags are combined in alert context', async () => { + setEvaluationResults([ + { + 'host-01': { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.1', + currentValue: 1.0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'host-01' }, + context: { + tags: ['host-01_tag1', 'host-01_tag2'], + }, + }, + 'host-02': { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.1', + currentValue: 3, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: 'host-02' }, + context: { + tags: ['host-02_tag1', 'host-02_tag2'], + }, + }, + }, + ]); + await execute(Comparator.GT, [0.75]); + expect(mostRecentAction(instanceIdA).action.tags).toStrictEqual([ + 'host-01_tag1', + 'host-01_tag2', + 'ruleTag1', + 'ruleTag2', + ]); + expect(mostRecentAction(instanceIdB).action.tags).toStrictEqual([ + 'host-02_tag1', + 'host-02_tag2', + 'ruleTag1', + 'ruleTag2', + ]); + }); + }); + + describe('querying without a groupBy parameter and rule tags', () => { + afterAll(() => clearInstances()); + const execute = ( + comparator: Comparator, + threshold: number[], + groupBy: string = '', + metric?: string, + state?: any + ) => + executor({ + ...mockOptions, + services, + params: { + groupBy, + criteria: [ + { + ...baseNonCountCriterion, + comparator, + threshold, + metric: metric ?? baseNonCountCriterion.metric, + }, + ], + }, + state: state ?? mockOptions.state.wrapped, + rule: { + ...mockOptions.rule, + tags: ['ruleTag1', 'ruleTag2'], + }, + }); + + test('rule tags are added in alert context', async () => { + setEvaluationResults([ + { + '*': { + ...baseNonCountCriterion, + comparator: Comparator.GT, + threshold: [0.75], + metric: 'test.metric.1', + currentValue: 1.0, + timestamp: new Date().toISOString(), + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: '*' }, + }, + }, + ]); + + const instanceID = '*'; + await execute(Comparator.GT, [0.75]); + expect(mostRecentAction(instanceID).action.tags).toStrictEqual(['ruleTag1', 'ruleTag2']); + }); + }); + describe('querying with multiple criteria', () => { afterAll(() => clearInstances()); const execute = ( diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts index cd0b0f48f36d4..28a32a8c46174 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts @@ -287,9 +287,13 @@ export const createMetricThresholdExecutor = (libs: InfraBackendLibs) => const additionalContext = hasAdditionalContext(params.groupBy, validGroupByForContext) ? alertResults && alertResults.length > 0 - ? alertResults[0][group].context - : null - : null; + ? alertResults[0][group].context ?? {} + : {} + : {}; + + additionalContext.tags = Array.from( + new Set([...(additionalContext.tags ?? []), ...options.rule.tags]) + ); const alert = alertFactory(`${group}`, reason, actionGroupId, additionalContext); const alertUuid = getAlertUuid(group); diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts b/x-pack/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts index 1e0daad376a1a..fcda9b30b0dac 100644 --- a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts +++ b/x-pack/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts @@ -15,6 +15,7 @@ import { LogColumn, LogEntry, LogEntryCursor } from '../../../../common/log_entr import { LogViewColumnConfiguration, logViewFieldColumnConfigurationRT, + LogViewReference, ResolvedLogView, } from '../../../../common/log_views'; import { decodeOrThrow } from '../../../../common/runtime_types'; @@ -66,7 +67,7 @@ export class InfraLogEntriesDomain { public async getLogEntriesAround( requestContext: InfraPluginRequestHandlerContext, - sourceId: string, + logView: LogViewReference, params: LogEntriesAroundParams, columnOverrides?: LogViewColumnConfiguration[] ): Promise<{ entries: LogEntry[]; hasMoreBefore?: boolean; hasMoreAfter?: boolean }> { @@ -84,7 +85,7 @@ export class InfraLogEntriesDomain { const { entries: entriesBefore, hasMoreBefore } = await this.getLogEntries( requestContext, - sourceId, + logView, { startTimestamp, endTimestamp, @@ -110,7 +111,7 @@ export class InfraLogEntriesDomain { const { entries: entriesAfter, hasMoreAfter } = await this.getLogEntries( requestContext, - sourceId, + logView, { startTimestamp, endTimestamp, @@ -126,7 +127,7 @@ export class InfraLogEntriesDomain { public async getLogEntries( requestContext: InfraPluginRequestHandlerContext, - sourceId: string, + logView: LogViewReference, params: LogEntriesParams, columnOverrides?: LogViewColumnConfiguration[] ): Promise<{ entries: LogEntry[]; hasMoreBefore?: boolean; hasMoreAfter?: boolean }> { @@ -134,7 +135,7 @@ export class InfraLogEntriesDomain { const { savedObjects, elasticsearch } = await requestContext.core; const resolvedLogView = await logViews .getClient(savedObjects.client, elasticsearch.client.asCurrentUser) - .getResolvedLogView(sourceId); + .getResolvedLogView(logView); const columnDefinitions = columnOverrides ?? resolvedLogView.columns; const messageFormattingRules = compileFormattingRules( @@ -184,7 +185,7 @@ export class InfraLogEntriesDomain { public async getLogSummaryBucketsBetween( requestContext: InfraPluginRequestHandlerContext, - sourceId: string, + logView: LogViewReference, start: number, end: number, bucketSize: number, @@ -194,7 +195,7 @@ export class InfraLogEntriesDomain { const { savedObjects, elasticsearch } = await requestContext.core; const resolvedLogView = await logViews .getClient(savedObjects.client, elasticsearch.client.asCurrentUser) - .getResolvedLogView(sourceId); + .getResolvedLogView(logView); const dateRangeBuckets = await this.adapter.getContainedLogSummaryBuckets( requestContext, resolvedLogView, @@ -208,7 +209,7 @@ export class InfraLogEntriesDomain { public async getLogSummaryHighlightBucketsBetween( requestContext: InfraPluginRequestHandlerContext, - sourceId: string, + logView: LogViewReference, startTimestamp: number, endTimestamp: number, bucketSize: number, @@ -219,7 +220,7 @@ export class InfraLogEntriesDomain { const { savedObjects, elasticsearch } = await requestContext.core; const resolvedLogView = await logViews .getClient(savedObjects.client, elasticsearch.client.asCurrentUser) - .getResolvedLogView(sourceId); + .getResolvedLogView(logView); const messageFormattingRules = compileFormattingRules( getBuiltinRules(resolvedLogView.messageField) ); diff --git a/x-pack/plugins/infra/server/lib/log_analysis/log_entry_anomalies.ts b/x-pack/plugins/infra/server/lib/log_analysis/log_entry_anomalies.ts index 22bf5466ee3b4..b17afa68d2d4d 100644 --- a/x-pack/plugins/infra/server/lib/log_analysis/log_entry_anomalies.ts +++ b/x-pack/plugins/infra/server/lib/log_analysis/log_entry_anomalies.ts @@ -16,7 +16,7 @@ import { logEntryRateJobTypes, Pagination, } from '../../../common/log_analysis'; -import { ResolvedLogView } from '../../../common/log_views'; +import { PersistedLogViewReference, ResolvedLogView } from '../../../common/log_views'; import { startTracingSpan, TracingSpan } from '../../../common/performance_tracing'; import { decodeOrThrow } from '../../../common/runtime_types'; import type { @@ -54,11 +54,11 @@ interface MappedAnomalyHit { async function getCompatibleAnomaliesJobIds( spaceId: string, - sourceId: string, + logViewId: string, mlAnomalyDetectors: MlAnomalyDetectors ) { - const logRateJobId = getJobId(spaceId, sourceId, logEntryRateJobTypes[0]); - const logCategoriesJobId = getJobId(spaceId, sourceId, logEntryCategoriesJobTypes[0]); + const logRateJobId = getJobId(spaceId, logViewId, logEntryRateJobTypes[0]); + const logCategoriesJobId = getJobId(spaceId, logViewId, logEntryCategoriesJobTypes[0]); const jobIds: string[] = []; let jobSpans: TracingSpan[] = []; @@ -99,7 +99,7 @@ export async function getLogEntryAnomalies( context: InfraPluginRequestHandlerContext & { infra: Promise>; }, - sourceId: string, + logView: PersistedLogViewReference, startTime: number, endTime: number, sort: AnomaliesSort, @@ -114,7 +114,7 @@ export async function getLogEntryAnomalies( timing: { spans: jobSpans }, } = await getCompatibleAnomaliesJobIds( infraContext.spaceId, - sourceId, + logView.logViewId, infraContext.mlAnomalyDetectors ); @@ -155,7 +155,7 @@ export async function getLogEntryAnomalies( const logEntryCategoriesCountJobId = getJobId( infraContext.spaceId, - sourceId, + logView.logViewId, logEntryCategoriesJobTypes[0] ); @@ -331,7 +331,7 @@ export async function getLogEntryExamples( context: InfraPluginRequestHandlerContext & { infra: Promise>; }, - sourceId: string, + logView: PersistedLogViewReference, startTime: number, endTime: number, dataset: string, @@ -345,7 +345,7 @@ export async function getLogEntryExamples( const jobId = getJobId( infraContext.spaceId, - sourceId, + logView.logViewId, categoryId != null ? logEntryCategoriesJobTypes[0] : logEntryRateJobTypes[0] ); @@ -370,7 +370,7 @@ export async function getLogEntryExamples( timing: { spans: fetchLogEntryExamplesSpans }, } = await fetchLogEntryExamples( context, - sourceId, + logView, indices, runtimeMappings, timestampField, @@ -397,7 +397,7 @@ export async function fetchLogEntryExamples( context: InfraPluginRequestHandlerContext & { infra: Promise>; }, - sourceId: string, + logView: PersistedLogViewReference, indices: string, runtimeMappings: estypes.MappingRuntimeFields, timestampField: string, @@ -420,7 +420,7 @@ export async function fetchLogEntryExamples( const logEntryCategoriesCountJobId = getJobId( infraContext.spaceId, - sourceId, + logView.logViewId, logEntryCategoriesJobTypes[0] ); @@ -483,7 +483,7 @@ export async function getLogEntryAnomaliesDatasets( spaceId: string; }; }, - sourceId: string, + logView: PersistedLogViewReference, startTime: number, endTime: number ) { @@ -492,7 +492,7 @@ export async function getLogEntryAnomaliesDatasets( timing: { spans: jobSpans }, } = await getCompatibleAnomaliesJobIds( context.infra.spaceId, - sourceId, + logView.logViewId, context.infra.mlAnomalyDetectors ); diff --git a/x-pack/plugins/infra/server/lib/log_analysis/log_entry_categories_analysis.ts b/x-pack/plugins/infra/server/lib/log_analysis/log_entry_categories_analysis.ts index 9152945002e2a..d8eb18e4890b5 100644 --- a/x-pack/plugins/infra/server/lib/log_analysis/log_entry_categories_analysis.ts +++ b/x-pack/plugins/infra/server/lib/log_analysis/log_entry_categories_analysis.ts @@ -15,7 +15,7 @@ import { logEntryCategoriesJobTypes, } from '../../../common/log_analysis'; import { LogEntryContext } from '../../../common/log_entry'; -import { ResolvedLogView } from '../../../common/log_views'; +import { PersistedLogViewReference, ResolvedLogView } from '../../../common/log_views'; import { startTracingSpan } from '../../../common/performance_tracing'; import { decodeOrThrow } from '../../../common/runtime_types'; import type { MlAnomalyDetectors, MlSystem } from '../../types'; @@ -47,7 +47,7 @@ export async function getTopLogEntryCategories( spaceId: string; }; }, - sourceId: string, + logView: PersistedLogViewReference, startTime: number, endTime: number, categoryCount: number, @@ -59,7 +59,7 @@ export async function getTopLogEntryCategories( const logEntryCategoriesCountJobId = getJobId( context.infra.spaceId, - sourceId, + logView.logViewId, logEntryCategoriesJobTypes[0] ); @@ -119,13 +119,13 @@ export async function getLogEntryCategoryDatasets( spaceId: string; }; }, - sourceId: string, + logView: PersistedLogViewReference, startTime: number, endTime: number ) { const logEntryCategoriesCountJobId = getJobId( context.infra.spaceId, - sourceId, + logView.logViewId, logEntryCategoriesJobTypes[0] ); @@ -143,7 +143,7 @@ export async function getLogEntryCategoryExamples( spaceId: string; }; }, - sourceId: string, + logView: PersistedLogViewReference, startTime: number, endTime: number, categoryId: number, @@ -154,7 +154,7 @@ export async function getLogEntryCategoryExamples( const logEntryCategoriesCountJobId = getJobId( context.infra.spaceId, - sourceId, + logView.logViewId, logEntryCategoriesJobTypes[0] ); diff --git a/x-pack/plugins/infra/server/lib/log_analysis/log_entry_rate_analysis.ts b/x-pack/plugins/infra/server/lib/log_analysis/log_entry_rate_analysis.ts index 7da6298fff76a..1e043fed0986a 100644 --- a/x-pack/plugins/infra/server/lib/log_analysis/log_entry_rate_analysis.ts +++ b/x-pack/plugins/infra/server/lib/log_analysis/log_entry_rate_analysis.ts @@ -24,13 +24,13 @@ export async function getLogEntryRateBuckets( spaceId: string; }; }, - sourceId: string, + logViewId: string, startTime: number, endTime: number, bucketDuration: number, datasets?: string[] ) { - const logRateJobId = getJobId(context.infra.spaceId, sourceId, 'log-entry-rate'); + const logRateJobId = getJobId(context.infra.spaceId, logViewId, 'log-entry-rate'); let mlModelPlotBuckets: LogRateModelPlotBucket[] = []; let afterLatestBatchKey: CompositeTimestampPartitionKey | undefined; diff --git a/x-pack/plugins/infra/server/routes/log_alerts/chart_preview_data.ts b/x-pack/plugins/infra/server/routes/log_alerts/chart_preview_data.ts index 95b0c8320559e..fbc530397f4e3 100644 --- a/x-pack/plugins/infra/server/routes/log_alerts/chart_preview_data.ts +++ b/x-pack/plugins/infra/server/routes/log_alerts/chart_preview_data.ts @@ -29,11 +29,11 @@ export const initGetLogAlertsChartPreviewDataRoute = ({ }, framework.router.handleLegacyErrors(async (requestContext, request, response) => { const { - data: { sourceId, buckets, alertParams }, + data: { logView, buckets, alertParams }, } = request.body; const [, , { logViews }] = await getStartServices(); - const resolvedLogView = await logViews.getScopedClient(request).getResolvedLogView(sourceId); + const resolvedLogView = await logViews.getScopedClient(request).getResolvedLogView(logView); try { const { series } = await getChartPreviewData( diff --git a/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_anomalies.ts b/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_anomalies.ts index dd6254cf560e2..13df82f8fe343 100644 --- a/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_anomalies.ts +++ b/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_anomalies.ts @@ -31,7 +31,7 @@ export const initGetLogEntryAnomaliesRoute = ({ framework }: InfraBackendLibs) = framework.router.handleLegacyErrors(async (requestContext, request, response) => { const { data: { - sourceId, + logView, timeRange: { startTime, endTime }, sort: sortParam, pagination: paginationParam, @@ -51,7 +51,7 @@ export const initGetLogEntryAnomaliesRoute = ({ framework }: InfraBackendLibs) = timing, } = await getLogEntryAnomalies( infraMlContext, - sourceId, + logView, startTime, endTime, sort, diff --git a/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_anomalies_datasets.ts b/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_anomalies_datasets.ts index 1d1f620063b2e..5f7aec90376af 100644 --- a/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_anomalies_datasets.ts +++ b/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_anomalies_datasets.ts @@ -29,7 +29,7 @@ export const initGetLogEntryAnomaliesDatasetsRoute = ({ framework }: InfraBacken framework.router.handleLegacyErrors(async (requestContext, request, response) => { const { data: { - sourceId, + logView, timeRange: { startTime, endTime }, }, } = request.body; @@ -39,7 +39,7 @@ export const initGetLogEntryAnomaliesDatasetsRoute = ({ framework }: InfraBacken const { datasets, timing } = await getLogEntryAnomaliesDatasets( { infra: await infraMlContext.infra }, - sourceId, + logView, startTime, endTime ); diff --git a/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_categories.ts b/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_categories.ts index 6e2e8e8a6c2ad..1a484a0662e05 100644 --- a/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_categories.ts +++ b/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_categories.ts @@ -31,7 +31,7 @@ export const initGetLogEntryCategoriesRoute = ({ framework }: InfraBackendLibs) data: { categoryCount, histograms, - sourceId, + logView, timeRange: { startTime, endTime }, datasets, sort, @@ -43,7 +43,7 @@ export const initGetLogEntryCategoriesRoute = ({ framework }: InfraBackendLibs) const { data: topLogEntryCategories, timing } = await getTopLogEntryCategories( { infra: await infraMlContext.infra }, - sourceId, + logView, startTime, endTime, categoryCount, diff --git a/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_category_datasets.ts b/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_category_datasets.ts index de5ac9dac4b07..92f0cd576a0f8 100644 --- a/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_category_datasets.ts +++ b/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_category_datasets.ts @@ -29,7 +29,7 @@ export const initGetLogEntryCategoryDatasetsRoute = ({ framework }: InfraBackend framework.router.handleLegacyErrors(async (requestContext, request, response) => { const { data: { - sourceId, + logView, timeRange: { startTime, endTime }, }, } = request.body; @@ -39,7 +39,7 @@ export const initGetLogEntryCategoryDatasetsRoute = ({ framework }: InfraBackend const { data: logEntryCategoryDatasets, timing } = await getLogEntryCategoryDatasets( { infra: await infraMlContext.infra }, - sourceId, + logView, startTime, endTime ); diff --git a/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_category_examples.ts b/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_category_examples.ts index b51aed45b7e11..40de491c1673f 100644 --- a/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_category_examples.ts +++ b/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_category_examples.ts @@ -34,20 +34,20 @@ export const initGetLogEntryCategoryExamplesRoute = ({ data: { categoryId, exampleCount, - sourceId, + logView, timeRange: { startTime, endTime }, }, } = request.body; const [, , { logViews }] = await getStartServices(); - const resolvedLogView = await logViews.getScopedClient(request).getResolvedLogView(sourceId); + const resolvedLogView = await logViews.getScopedClient(request).getResolvedLogView(logView); try { const infraMlContext = await assertHasInfraMlPlugins(requestContext); const { data: logEntryCategoryExamples, timing } = await getLogEntryCategoryExamples( { infra: await infraMlContext.infra, core: await infraMlContext.core }, - sourceId, + logView, startTime, endTime, categoryId, diff --git a/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_examples.ts b/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_examples.ts index fb82a2cd90df5..23ba1072a60fc 100644 --- a/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_examples.ts +++ b/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_examples.ts @@ -34,21 +34,21 @@ export const initGetLogEntryExamplesRoute = ({ data: { dataset, exampleCount, - sourceId, + logView, timeRange: { startTime, endTime }, categoryId, }, } = request.body; const [, , { logViews }] = await getStartServices(); - const resolvedLogView = await logViews.getScopedClient(request).getResolvedLogView(sourceId); + const resolvedLogView = await logViews.getScopedClient(request).getResolvedLogView(logView); try { const infraMlContext = await assertHasInfraMlPlugins(requestContext); const { data: logEntryExamples, timing } = await getLogEntryExamples( infraMlContext, - sourceId, + logView, startTime, endTime, dataset, diff --git a/x-pack/plugins/infra/server/routes/log_entries/highlights.ts b/x-pack/plugins/infra/server/routes/log_entries/highlights.ts index bb7c615358c0e..aa8876951ee6c 100644 --- a/x-pack/plugins/infra/server/routes/log_entries/highlights.ts +++ b/x-pack/plugins/infra/server/routes/log_entries/highlights.ts @@ -38,14 +38,14 @@ export const initLogEntriesHighlightsRoute = ({ framework, logEntries }: InfraBa fold(throwErrors(Boom.badRequest), identity) ); - const { startTimestamp, endTimestamp, sourceId, query, size, highlightTerms } = payload; + const { startTimestamp, endTimestamp, logView, query, size, highlightTerms } = payload; let entriesPerHighlightTerm; if ('center' in payload) { entriesPerHighlightTerm = await Promise.all( highlightTerms.map((highlightTerm) => - logEntries.getLogEntriesAround(requestContext, sourceId, { + logEntries.getLogEntriesAround(requestContext, logView, { startTimestamp, endTimestamp, query: parseFilterQuery(query), @@ -65,7 +65,7 @@ export const initLogEntriesHighlightsRoute = ({ framework, logEntries }: InfraBa entriesPerHighlightTerm = await Promise.all( highlightTerms.map((highlightTerm) => - logEntries.getLogEntries(requestContext, sourceId, { + logEntries.getLogEntries(requestContext, logView, { startTimestamp, endTimestamp, query: parseFilterQuery(query), diff --git a/x-pack/plugins/infra/server/routes/log_entries/summary.ts b/x-pack/plugins/infra/server/routes/log_entries/summary.ts index 3ff0ded8a7c24..dd48c21a590ae 100644 --- a/x-pack/plugins/infra/server/routes/log_entries/summary.ts +++ b/x-pack/plugins/infra/server/routes/log_entries/summary.ts @@ -37,11 +37,11 @@ export const initLogEntriesSummaryRoute = ({ framework, logEntries }: InfraBacke logEntriesSummaryRequestRT.decode(request.body), fold(throwErrors(Boom.badRequest), identity) ); - const { sourceId, startTimestamp, endTimestamp, bucketSize, query } = payload; + const { logView, startTimestamp, endTimestamp, bucketSize, query } = payload; const buckets = await logEntries.getLogSummaryBucketsBetween( requestContext, - sourceId, + logView, startTimestamp, endTimestamp, bucketSize, diff --git a/x-pack/plugins/infra/server/routes/log_entries/summary_highlights.ts b/x-pack/plugins/infra/server/routes/log_entries/summary_highlights.ts index ca219cac41e2b..206e02bc57278 100644 --- a/x-pack/plugins/infra/server/routes/log_entries/summary_highlights.ts +++ b/x-pack/plugins/infra/server/routes/log_entries/summary_highlights.ts @@ -39,11 +39,11 @@ export const initLogEntriesSummaryHighlightsRoute = ({ logEntriesSummaryHighlightsRequestRT.decode(request.body), fold(throwErrors(Boom.badRequest), identity) ); - const { sourceId, startTimestamp, endTimestamp, bucketSize, query, highlightTerms } = payload; + const { logView, startTimestamp, endTimestamp, bucketSize, query, highlightTerms } = payload; const bucketsPerHighlightTerm = await logEntries.getLogSummaryHighlightBucketsBetween( requestContext, - sourceId, + logView, startTimestamp, endTimestamp, bucketSize, diff --git a/x-pack/plugins/infra/server/routes/snapshot/index.ts b/x-pack/plugins/infra/server/routes/snapshot/index.ts index 27c49032c03f4..0c893171b5b67 100644 --- a/x-pack/plugins/infra/server/routes/snapshot/index.ts +++ b/x-pack/plugins/infra/server/routes/snapshot/index.ts @@ -43,7 +43,10 @@ export const initSnapshotRoute = (libs: InfraBackendLibs) => { const [, , { logViews }] = await libs.getStartServices(); const logQueryFields: LogQueryFields | undefined = await logViews .getScopedClient(request) - .getResolvedLogView(snapshotRequest.sourceId) + .getResolvedLogView({ + type: 'log-view-reference', + logViewId: snapshotRequest.sourceId, + }) .then( ({ indices }) => ({ indexPattern: indices }), () => undefined diff --git a/x-pack/plugins/infra/server/routes/snapshot/lib/transform_metrics_ui_response.ts b/x-pack/plugins/infra/server/routes/snapshot/lib/transform_metrics_ui_response.ts index 4b3682db3d1f6..f11a67ecb4a0c 100644 --- a/x-pack/plugins/infra/server/routes/snapshot/lib/transform_metrics_ui_response.ts +++ b/x-pack/plugins/infra/server/routes/snapshot/lib/transform_metrics_ui_response.ts @@ -23,7 +23,7 @@ import { applyMetadataToLastPath } from './apply_metadata_to_last_path'; const getMetricValue = (row: MetricsAPIRow) => { if (!isNumber(row.metric_0)) return null; const value = row.metric_0; - return isFinite(value) ? value : null; + return Number.isFinite(value) ? value : null; }; const calculateMax = (rows: MetricsAPIRow[]) => { @@ -31,7 +31,8 @@ const calculateMax = (rows: MetricsAPIRow[]) => { }; const calculateAvg = (rows: MetricsAPIRow[]): number => { - return sum(rows.map(getMetricValue)) / rows.length || 0; + const values = rows.map(getMetricValue).filter(Number.isFinite); + return sum(values) / Math.max(values.length, 1); }; const getLastValue = (rows: MetricsAPIRow[]) => { diff --git a/x-pack/plugins/infra/server/routes/snapshot/lib/transform_request_to_metrics_api_request.ts b/x-pack/plugins/infra/server/routes/snapshot/lib/transform_request_to_metrics_api_request.ts index ae342cfad7b28..890295561e3a7 100644 --- a/x-pack/plugins/infra/server/routes/snapshot/lib/transform_request_to_metrics_api_request.ts +++ b/x-pack/plugins/infra/server/routes/snapshot/lib/transform_request_to_metrics_api_request.ts @@ -45,7 +45,7 @@ export const transformRequestToMetricsAPIRequest = async ({ ? snapshotRequest.overrideCompositeSize : compositeSize, alignDataToEnd: true, - dropPartialBuckets: true, + dropPartialBuckets: snapshotRequest.dropPartialBuckets ?? true, includeTimeseries: snapshotRequest.includeTimeseries, }; diff --git a/x-pack/plugins/infra/server/services/log_entries/log_entries_search_strategy.test.ts b/x-pack/plugins/infra/server/services/log_entries/log_entries_search_strategy.test.ts index 99579c5a588c6..bb21053cfe9d8 100644 --- a/x-pack/plugins/infra/server/services/log_entries/log_entries_search_strategy.test.ts +++ b/x-pack/plugins/infra/server/services/log_entries/log_entries_search_strategy.test.ts @@ -57,7 +57,7 @@ describe('LogEntries search strategy', () => { logEntriesSearchStrategy.search( { params: { - sourceId: 'SOURCE_ID', + logView: { type: 'log-view-reference', logViewId: 'SOURCE_ID' }, startTimestamp: 100, endTimestamp: 200, size: 3, @@ -143,7 +143,7 @@ describe('LogEntries search strategy', () => { { id: requestId, params: { - sourceId: 'SOURCE_ID', + logView: { type: 'log-view-reference', logViewId: 'SOURCE_ID' }, startTimestamp: 100, endTimestamp: 200, size: 3, @@ -223,7 +223,7 @@ describe('LogEntries search strategy', () => { { id: logEntriesSearchRequestStateRT.encode({ esRequestId: 'UNKNOWN_ID' }), params: { - sourceId: 'SOURCE_ID', + logView: { type: 'log-view-reference', logViewId: 'SOURCE_ID' }, startTimestamp: 100, endTimestamp: 200, size: 3, diff --git a/x-pack/plugins/infra/server/services/log_entries/log_entries_search_strategy.ts b/x-pack/plugins/infra/server/services/log_entries/log_entries_search_strategy.ts index 81ef319828be1..f0f5c6304d615 100644 --- a/x-pack/plugins/infra/server/services/log_entries/log_entries_search_strategy.ts +++ b/x-pack/plugins/infra/server/services/log_entries/log_entries_search_strategy.ts @@ -72,7 +72,7 @@ export const logEntriesSearchStrategyProvider = ({ const request = decodeOrThrow(asyncRequestRT)(rawRequest); const resolvedLogView$ = defer(() => - logViews.getScopedClient(dependencies.request).getResolvedLogView(request.params.sourceId) + logViews.getScopedClient(dependencies.request).getResolvedLogView(request.params.logView) ).pipe(take(1), shareReplay(1)); const messageFormattingRules$ = defer(() => diff --git a/x-pack/plugins/infra/server/services/log_entries/log_entry_search_strategy.test.ts b/x-pack/plugins/infra/server/services/log_entries/log_entry_search_strategy.test.ts index d2d28174490c8..19d5345122374 100644 --- a/x-pack/plugins/infra/server/services/log_entries/log_entry_search_strategy.test.ts +++ b/x-pack/plugins/infra/server/services/log_entries/log_entry_search_strategy.test.ts @@ -56,7 +56,10 @@ describe('LogEntry search strategy', () => { const response = await lastValueFrom( logEntrySearchStrategy.search( { - params: { sourceId: 'SOURCE_ID', logEntryId: 'LOG_ENTRY_ID' }, + params: { + logView: { type: 'log-view-reference', logViewId: 'SOURCE_ID' }, + logEntryId: 'LOG_ENTRY_ID', + }, }, {}, mockDependencies @@ -141,7 +144,10 @@ describe('LogEntry search strategy', () => { logEntrySearchStrategy.search( { id: requestId, - params: { sourceId: 'SOURCE_ID', logEntryId: 'LOG_ENTRY_ID' }, + params: { + logView: { type: 'log-view-reference', logViewId: 'SOURCE_ID' }, + logEntryId: 'LOG_ENTRY_ID', + }, }, {}, mockDependencies @@ -193,7 +199,10 @@ describe('LogEntry search strategy', () => { const response = logEntrySearchStrategy.search( { id: logEntrySearchRequestStateRT.encode({ esRequestId: 'UNKNOWN_ID' }), - params: { sourceId: 'SOURCE_ID', logEntryId: 'LOG_ENTRY_ID' }, + params: { + logView: { type: 'log-view-reference', logViewId: 'SOURCE_ID' }, + logEntryId: 'LOG_ENTRY_ID', + }, }, {}, mockDependencies diff --git a/x-pack/plugins/infra/server/services/log_entries/log_entry_search_strategy.ts b/x-pack/plugins/infra/server/services/log_entries/log_entry_search_strategy.ts index 714e0b792c612..1d558094e351d 100644 --- a/x-pack/plugins/infra/server/services/log_entries/log_entry_search_strategy.ts +++ b/x-pack/plugins/infra/server/services/log_entries/log_entry_search_strategy.ts @@ -48,7 +48,7 @@ export const logEntrySearchStrategyProvider = ({ const request = decodeOrThrow(asyncRequestRT)(rawRequest); const resolvedLogView$ = defer(() => - logViews.getScopedClient(dependencies.request).getResolvedLogView(request.params.sourceId) + logViews.getScopedClient(dependencies.request).getResolvedLogView(request.params.logView) ).pipe(take(1), shareReplay(1)); const recoveredRequest$ = of(request).pipe( diff --git a/x-pack/plugins/infra/server/services/log_views/log_views_client.mock.ts b/x-pack/plugins/infra/server/services/log_views/log_views_client.mock.ts index ac69ae4f85ba5..5738c94c8aa40 100644 --- a/x-pack/plugins/infra/server/services/log_views/log_views_client.mock.ts +++ b/x-pack/plugins/infra/server/services/log_views/log_views_client.mock.ts @@ -5,11 +5,15 @@ * 2.0. */ +import { LogViewReference } from '../../../common/log_views'; +import { createResolvedLogViewMock } from '../../../common/log_views/resolved_log_view.mock'; import { ILogViewsClient } from './types'; export const createLogViewsClientMock = (): jest.Mocked => ({ getLogView: jest.fn(), - getResolvedLogView: jest.fn(), + getResolvedLogView: jest.fn((logViewReference: LogViewReference) => + Promise.resolve(createResolvedLogViewMock()) + ), putLogView: jest.fn(), resolveLogView: jest.fn(), }); diff --git a/x-pack/plugins/infra/server/services/log_views/log_views_client.ts b/x-pack/plugins/infra/server/services/log_views/log_views_client.ts index 9f43cee871f73..3f832c6770717 100644 --- a/x-pack/plugins/infra/server/services/log_views/log_views_client.ts +++ b/x-pack/plugins/infra/server/services/log_views/log_views_client.ts @@ -19,7 +19,9 @@ import { LogIndexReference, LogView, LogViewAttributes, + LogViewReference, LogViewsStaticConfig, + persistedLogViewReferenceRT, ResolvedLogView, resolveLogView, } from '../../../common/log_views'; @@ -65,8 +67,10 @@ export class LogViewsClient implements ILogViewsClient { ); } - public async getResolvedLogView(logViewId: string): Promise { - const logView = await this.getLogView(logViewId); + public async getResolvedLogView(logViewReference: LogViewReference): Promise { + const logView = persistedLogViewReferenceRT.is(logViewReference) + ? await this.getLogView(logViewReference.logViewId) + : logViewReference; const resolvedLogView = await this.resolveLogView(logView.id, logView.attributes); return resolvedLogView; } diff --git a/x-pack/plugins/infra/server/services/log_views/types.ts b/x-pack/plugins/infra/server/services/log_views/types.ts index 50b4e65cf7548..b5f91cb3587b4 100644 --- a/x-pack/plugins/infra/server/services/log_views/types.ts +++ b/x-pack/plugins/infra/server/services/log_views/types.ts @@ -16,6 +16,7 @@ import { PluginStart as DataViewsServerPluginStart } from '@kbn/data-views-plugi import { LogView, LogViewAttributes, + LogViewReference, LogViewsStaticConfig, ResolvedLogView, } from '../../../common/log_views'; @@ -44,7 +45,7 @@ export interface LogViewsServiceStart { export interface ILogViewsClient { getLogView(logViewId: string): Promise; - getResolvedLogView(logViewId: string): Promise; + getResolvedLogView(logView: LogViewReference): Promise; putLogView(logViewId: string, logViewAttributes: Partial): Promise; resolveLogView(logViewId: string, logViewAttributes: LogViewAttributes): Promise; } diff --git a/x-pack/plugins/infra/server/services/rules/rule_data_client.ts b/x-pack/plugins/infra/server/services/rules/rule_data_client.ts index 1435f4812d16c..3a81f957e9314 100644 --- a/x-pack/plugins/infra/server/services/rules/rule_data_client.ts +++ b/x-pack/plugins/infra/server/services/rules/rule_data_client.ts @@ -6,11 +6,11 @@ */ import { CoreSetup, Logger } from '@kbn/core/server'; -import { mappingFromFieldMap } from '@kbn/rule-registry-plugin/common/mapping_from_field_map'; import { experimentalRuleFieldMap } from '@kbn/rule-registry-plugin/common/assets/field_maps/experimental_rule_field_map'; import { Dataset, RuleRegistryPluginSetupContract } from '@kbn/rule-registry-plugin/server'; -import { ECS_COMPONENT_TEMPLATE_NAME } from '@kbn/rule-registry-plugin/common/assets'; +import { mappingFromFieldMap } from '@kbn/alerting-plugin/common'; +import { ECS_COMPONENT_TEMPLATE_NAME } from '@kbn/alerting-plugin/server'; import type { InfraFeatureId } from '../../../common/constants'; import { RuleRegistrationContext, RulesServiceStartDeps } from './types'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor.helpers.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor.helpers.tsx index 9501598c53db2..970aaf83b5ae8 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor.helpers.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor.helpers.tsx @@ -187,4 +187,7 @@ type TestSubject = | 'droppableList.addButton' | 'droppableList.input-0' | 'droppableList.input-1' - | 'droppableList.input-2'; + | 'droppableList.input-2' + | 'prefixField.input' + | 'suffixField.input' + | 'patternDefinitionsField'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/redact.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/redact.test.tsx new file mode 100644 index 0000000000000..d34e4d1476bb8 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/redact.test.tsx @@ -0,0 +1,141 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { act } from 'react-dom/test-utils'; +import { setup, SetupResult, getProcessorValue, setupEnvironment } from './processor.helpers'; + +const REDACT_TYPE = 'redact'; + +describe('Processor: Redact', () => { + let onUpdate: jest.Mock; + let testBed: SetupResult; + let clickAddPattern: () => Promise; + const { httpSetup } = setupEnvironment(); + + beforeAll(() => { + jest.useFakeTimers({ legacyFakeTimers: true }); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + + beforeEach(async () => { + onUpdate = jest.fn(); + + await act(async () => { + testBed = await setup(httpSetup, { + value: { + processors: [], + }, + onFlyoutOpen: jest.fn(), + onUpdate, + }); + }); + + const { find, component, actions } = testBed; + + clickAddPattern = async () => { + await act(async () => { + find('droppableList.addButton').simulate('click'); + }); + component.update(); + }; + + component.update(); + + // Open flyout to add new processor + actions.addProcessor(); + // Add type (the other fields are not visible until a type is selected) + await actions.addProcessorType(REDACT_TYPE); + }); + + test('prevents form submission if required fields are not provided', async () => { + const { + actions: { saveNewProcessor }, + form, + } = testBed; + + // Click submit button with only the type defined + await saveNewProcessor(); + + // Expect form error as "field" is a required parameter + expect(form.getErrorsMessages()).toEqual([ + 'A field value is required.', // "Field" input + 'A value is required.', // First input in "Patterns" list + ]); + }); + + test('saves with default parameter values', async () => { + const { + actions: { saveNewProcessor }, + form, + } = testBed; + + // Add "field" value + form.setInputValue('fieldNameField.input', 'test_redact_processor'); + + // Add pattern 1 + form.setInputValue('droppableList.input-0', 'pattern1'); + + // Add pattern 2 + await clickAddPattern(); + form.setInputValue('droppableList.input-1', 'pattern2'); + + // Save the field + await saveNewProcessor(); + + const processors = getProcessorValue(onUpdate, REDACT_TYPE); + + expect(processors[0][REDACT_TYPE]).toEqual({ + field: 'test_redact_processor', + patterns: ['pattern1', 'pattern2'], + }); + }); + + test('saves with optional parameter values', async () => { + const { + actions: { saveNewProcessor }, + component, + find, + form, + } = testBed; + + // Add "field" value + form.setInputValue('fieldNameField.input', 'test_redact_processor'); + + // Add one pattern to the list + form.setInputValue('droppableList.input-0', 'pattern1'); + + // Set suffix and prefix + form.setInputValue('prefixField.input', '$'); + form.setInputValue('suffixField.input', '$'); + + await act(async () => { + find('patternDefinitionsField').simulate('change', { + jsonContent: JSON.stringify({ GITHUB_NAME: '@%{USERNAME}' }), + }); + + // advance timers to allow the form to validate + jest.advanceTimersByTime(0); + }); + component.update(); + + // Save the field + await saveNewProcessor(); + + const processors = getProcessorValue(onUpdate, REDACT_TYPE); + + expect(processors[0][REDACT_TYPE]).toEqual({ + field: 'test_redact_processor', + patterns: ['pattern1'], + suffix: '$', + prefix: '$', + pattern_definitions: { GITHUB_NAME: '@%{USERNAME}' }, + }); + }); +}); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/field_components/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/field_components/index.ts index bb63461d71209..e4b9817e89579 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/field_components/index.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/field_components/index.ts @@ -8,3 +8,4 @@ export { DragAndDropTextList } from './drag_and_drop_text_list'; export { XJsonEditor } from './xjson_editor'; export { TextEditor } from './text_editor'; +export { InputList } from './input_list'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/field_components/input_list.scss b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/field_components/input_list.scss new file mode 100644 index 0000000000000..bec0c9981e308 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/field_components/input_list.scss @@ -0,0 +1,20 @@ +.pipelineProcessorsEditor__form__inputList { + &__panel { + background-color: $euiColorLightestShade; + padding: $euiSizeM; + } + + &__removeButton { + margin-left: $euiSizeS; + } + + &__item { + background-color: $euiColorLightestShade; + padding-top: $euiSizeS; + padding-bottom: $euiSizeS; + } + + &__labelContainer { + margin-bottom: $euiSizeXS; + } +} diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/field_components/input_list.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/field_components/input_list.tsx new file mode 100644 index 0000000000000..602c5598551a5 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/field_components/input_list.tsx @@ -0,0 +1,166 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import React, { useState } from 'react'; +import { v4 as uuidv4 } from 'uuid'; +import { + EuiButtonEmpty, + EuiButtonIcon, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiFieldText, + EuiFormRow, + EuiText, +} from '@elastic/eui'; + +import { + UseField, + ArrayItem, + ValidationFunc, + getFieldValidityAndErrorMessage, +} from '../../../../../../shared_imports'; + +import './input_list.scss'; + +interface Props { + label: string; + helpText: React.ReactNode; + error: string | null; + value: ArrayItem[]; + onAdd: () => void; + onRemove: (id: number) => void; + addLabel: string; + /** + * Validation to be applied to every text item + */ + textValidations?: Array>; + /** + * Serializer to be applied to every text item + */ + textSerializer?: (v: string) => O; + /** + * Deserializer to be applied to every text item + */ + textDeserializer?: (v: unknown) => string; +} + +const i18nTexts = { + removeItemButtonAriaLabel: i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.inputList.removeItemLabel', + { defaultMessage: 'Remove item' } + ), +}; + +export function InputList({ + label, + helpText, + error, + value, + onAdd, + onRemove, + addLabel, + textValidations, + textDeserializer, + textSerializer, +}: Props): JSX.Element { + const [firstItemId] = useState(() => uuidv4()); + + return ( + + <> + + + + + + + + +

      {helpText}

      +
      +
      +
      + +
      + {value.map((item, idx) => ( + + + + path={item.path} + config={{ + validations: textValidations + ? textValidations.map((validator) => ({ validator })) + : undefined, + deserializer: textDeserializer, + serializer: textSerializer, + }} + readDefaultValueOnForm={!item.isNew} + > + {(field) => { + const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); + return ( + + + + ); + }} + + + + {value.length > 1 ? ( + onRemove(item.id)} + size="s" + /> + ) : ( + + )} + + + ))} + + {addLabel} + +
      + +
      + ); +} diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/index.ts index e3a0fae36e577..bf9ac7006e1c2 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/index.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/index.ts @@ -31,6 +31,7 @@ export { Kv } from './kv'; export { Lowercase } from './lowercase'; export { NetworkDirection } from './network_direction'; export { Pipeline } from './pipeline'; +export { Redact } from './redact'; export { RegisteredDomain } from './registered_domain'; export { Remove } from './remove'; export { Rename } from './rename'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/redact.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/redact.tsx new file mode 100644 index 0000000000000..4e8885e94eb7e --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/redact.tsx @@ -0,0 +1,207 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FunctionComponent } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { EuiCode, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; + +import { + FIELD_TYPES, + Field, + UseField, + UseArray, + fieldValidators, + ValidationFunc, +} from '../../../../../../shared_imports'; + +import { XJsonEditor, InputList } from '../field_components'; + +import { FieldNameField } from './common_fields/field_name_field'; +import { IgnoreMissingField } from './common_fields/ignore_missing_field'; +import { FieldsConfig, to, from, EDITOR_PX_HEIGHT } from './shared'; + +const { isJsonField, emptyField } = fieldValidators; + +const i18nTexts = { + addPatternLabel: i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.redactForm.patternsAddPatternLabel', + { defaultMessage: 'Add pattern' } + ), +}; + +const valueRequiredMessage = i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.redactForm.patternsValueRequiredError', + { defaultMessage: 'A value is required.' } +); + +const patternsValidation: ValidationFunc = ({ value }) => { + if (typeof value === 'string' && value.length === 0) { + return { + message: valueRequiredMessage, + }; + } +}; + +const patternValidations = [emptyField(valueRequiredMessage)]; + +const fieldsConfig: FieldsConfig = { + /* Required field configs */ + patterns: { + label: i18n.translate('xpack.ingestPipelines.pipelineEditor.redactForm.patternsFieldLabel', { + defaultMessage: 'Patterns', + }), + deserializer: String, + helpText: i18n.translate('xpack.ingestPipelines.pipelineEditor.redactForm.patternsHelpText', { + defaultMessage: 'A list of grok expressions to match and redact named captures with.', + }), + validations: [ + { + validator: patternsValidation as ValidationFunc, + }, + ], + }, + /* Optional field configs */ + pattern_definitions: { + type: FIELD_TYPES.TEXT, + deserializer: to.jsonString, + serializer: from.optionalJson, + label: i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.redactForm.patternDefinitionsLabel', + { + defaultMessage: 'Pattern definitions (optional)', + } + ), + helpText: i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.redactForm.patternDefinitionsHelpText', + { + defaultMessage: + 'A map of pattern-name and pattern tuples defining custom patterns to be used by the processor. Patterns matching existing names will override the pre-existing definition.', + } + ), + validations: [ + { + validator: isJsonField( + i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.redactForm.patternsDefinitionsInvalidJSONError', + { defaultMessage: 'Invalid JSON' } + ), + { + allowEmptyString: true, + } + ), + }, + ], + }, + + prefix: { + type: FIELD_TYPES.TEXT, + label: i18n.translate('xpack.ingestPipelines.pipelineEditor.redactForm.prefixLabel', { + defaultMessage: 'Prefix (optional)', + }), + deserializer: String, + serializer: from.undefinedIfValue(''), + helpText: ( + {'<'} }} + /> + ), + }, + + suffix: { + type: FIELD_TYPES.TEXT, + label: i18n.translate('xpack.ingestPipelines.pipelineEditor.redactForm.suffixLabel', { + defaultMessage: 'Suffix (optional)', + }), + deserializer: String, + serializer: from.undefinedIfValue(''), + helpText: ( + {'>'} }} + /> + ), + }, +}; + +export const Redact: FunctionComponent = () => { + return ( + <> + + + + {({ items, addItem, removeItem, error }) => { + return ( + + ); + }} + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/shared/map_processor_type_to_form.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/shared/map_processor_type_to_form.tsx index d47f90abbd36d..cd53afedcc341 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/shared/map_processor_type_to_form.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/shared/map_processor_type_to_form.tsx @@ -37,6 +37,7 @@ import { Lowercase, NetworkDirection, Pipeline, + Redact, RegisteredDomain, Remove, Rename, @@ -577,6 +578,24 @@ export const mapProcessorTypeToDescriptor: MapProcessorTypeToDescriptor = { }, }), }, + redact: { + FieldsComponent: Redact, + docLinkPath: '/redact-processor.html', + label: i18n.translate('xpack.ingestPipelines.processors.label.redact', { + defaultMessage: 'Redact', + }), + typeDescription: i18n.translate('xpack.ingestPipelines.processors.description.redact', { + defaultMessage: + 'The Redact processor uses the Grok rules engine to obscure text in the input document matching the given Grok patterns.', + }), + getDefaultDescription: ({ field }) => + i18n.translate('xpack.ingestPipelines.processors.defaultDescription.redact', { + defaultMessage: 'Redact values from "{field}" that match a grok pattern', + values: { + field, + }, + }), + }, registered_domain: { FieldsComponent: RegisteredDomain, docLinkPath: '/registered-domain-processor.html', 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 72cca338117da..bc711e7d094c1 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 @@ -1520,7 +1520,10 @@ invalid: " operationDefinitionMap ) ).toEqual([ - 'A layer with only static values will not show results, use at least one dynamic metric', + { + message: + 'A layer with only static values will not show results, use at least one dynamic metric', + }, ]); }); @@ -1856,5 +1859,17 @@ invalid: " ).toEqual(undefined); } }); + + it('returns deduped errors on inner operation validation', () => { + expect( + formulaOperation.getErrorMessage!( + getNewLayerWithFormula('sum(clientip) + sum(clientip)', true), + 'col1', + indexPattern, + undefined, + operationDefinitionMap + ) + ).toHaveLength(1); + }); }); }); 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 70ccbc5fd25e8..20432dcdb6e24 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 @@ -7,7 +7,11 @@ import { i18n } from '@kbn/i18n'; import { uniqBy } from 'lodash'; -import type { BaseIndexPatternColumn, OperationDefinition } from '..'; +import type { + BaseIndexPatternColumn, + FieldBasedOperationErrorMessage, + OperationDefinition, +} from '..'; import type { ReferenceBasedIndexPatternColumn } from '../column_types'; import type { IndexPattern } from '../../../../../types'; import { runASTValidation, tryToParse } from './validation'; @@ -94,22 +98,30 @@ export const formulaOperation: OperationDefinition { - const def = visibleOperationsMap[col.operationType]; - if (def?.getErrorMessage) { - const messages = def.getErrorMessage( - layer, - id, - indexPattern, - dateRange, - visibleOperationsMap - ); - return messages ? { message: messages.join(', ') } : []; - } - return []; - }) - .filter(nonNullable); + const innerErrors = [ + ...managedColumns + .flatMap(([id, col]) => { + const def = visibleOperationsMap[col.operationType]; + if (def?.getErrorMessage) { + // TOOD: it would be nice to have nicer column names here rather than `Part of ` + const messages = def.getErrorMessage( + layer, + id, + indexPattern, + dateRange, + visibleOperationsMap + ); + return messages || []; + } + return []; + }) + .filter(nonNullable) + // dedup messages with the same content + .reduce((memo, message) => { + memo.add(message); + return memo; + }, new Set()), + ]; const hasBuckets = layer.columnOrder.some((colId) => layer.columns[colId].isBucketed); const hasOtherMetrics = layer.columnOrder.some((colId) => { const col = layer.columns[colId]; @@ -135,7 +147,7 @@ export const formulaOperation: OperationDefinition message) : undefined; + return innerErrors.length ? innerErrors : undefined; }, getPossibleOperation() { return { 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 a8828a9462ddd..520d0f53b143f 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 @@ -512,7 +512,7 @@ function checkMissingVariableOrFunctions( }, locations: missingVariables.map(({ location }) => location), }), - extraInfo: { missingFields: missingVariables.map(({ value }) => value) }, + extraInfo: { missingFields: [...new Set(missingVariables.map(({ value }) => value))] }, }); } const invalidVariableErrors = checkVariableEdgeCases( diff --git a/x-pack/plugins/lens/public/embeddable/embeddable.tsx b/x-pack/plugins/lens/public/embeddable/embeddable.tsx index 39bc64adef207..edb3d82b09e13 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable.tsx +++ b/x-pack/plugins/lens/public/embeddable/embeddable.tsx @@ -55,6 +55,7 @@ import { cellValueTrigger, CELL_VALUE_TRIGGER, type CellValueContext, + shouldFetch$, } from '@kbn/embeddable-plugin/public'; import type { Action, UiActionsStart } from '@kbn/ui-actions-plugin/public'; import type { DataViewsContract, DataView } from '@kbn/data-views-plugin/public'; @@ -501,20 +502,11 @@ export class Embeddable // Update search context and reload on changes related to search this.inputReloadSubscriptions.push( - this.getUpdated$() - .pipe(map(() => this.getInput())) - .pipe( - distinctUntilChanged((a, b) => - fastIsEqual( - [a.filters, a.query, a.timeRange, a.searchSessionId], - [b.filters, b.query, b.timeRange, b.searchSessionId] - ) - ), - skip(1) - ) - .subscribe(async (input) => { + shouldFetch$(this.getUpdated$(), () => this.getInput()).subscribe( + (input) => { this.onContainerStateChanged(input); - }) + } + ) ); } diff --git a/x-pack/plugins/lens/public/visualizations/partition/to_expression.ts b/x-pack/plugins/lens/public/visualizations/partition/to_expression.ts index 188ddfccea0ba..94438f242ec8d 100644 --- a/x-pack/plugins/lens/public/visualizations/partition/to_expression.ts +++ b/x-pack/plugins/lens/public/visualizations/partition/to_expression.ts @@ -79,10 +79,10 @@ export const getColumnToLabelMap = ( return columnToLabel; }; -export const getSortedGroups = ( +export const getSortedAccessorsForGroup = ( datasource: DatasourcePublicAPI | undefined, layer: PieLayerState, - accessor: 'primaryGroups' | 'secondaryGroups' = 'primaryGroups' + accessor: 'primaryGroups' | 'secondaryGroups' | 'metrics' ) => { const originalOrder = datasource ?.getTableSpec() @@ -174,7 +174,9 @@ const generateCommonArguments = ( datasourceLayers: DatasourceLayers, paletteService: PaletteRegistry ) => { - const columnToLabelMap = getColumnToLabelMap(layer.metrics, datasourceLayers[layer.layerId]); + const datasource = datasourceLayers[layer.layerId]; + const columnToLabelMap = getColumnToLabelMap(layer.metrics, datasource); + const sortedMetricAccessors = getSortedAccessorsForGroup(datasource, layer, 'metrics'); return { labels: generateCommonLabelsAstArgs(state, attributes, layer, columnToLabelMap), @@ -182,7 +184,7 @@ const generateCommonArguments = ( .filter(({ columnId }) => !isCollapsed(columnId, layer)) .map(({ columnId }) => columnId) .map(prepareDimension), - metrics: (layer.allowMultipleMetrics ? layer.metrics : [layer.metrics[0]]).map( + metrics: (layer.allowMultipleMetrics ? sortedMetricAccessors : [sortedMetricAccessors[0]]).map( prepareDimension ), metricsToLabels: JSON.stringify(columnToLabelMap), @@ -290,16 +292,18 @@ function expressionHelper( const layer = state.layers[0]; const datasource = datasourceLayers[layer.layerId]; - const groups = Array.from( + const accessors = Array.from( new Set( [ - getSortedGroups(datasource, layer, 'primaryGroups'), - layer.secondaryGroups ? getSortedGroups(datasource, layer, 'secondaryGroups') : [], + getSortedAccessorsForGroup(datasource, layer, 'primaryGroups'), + layer.secondaryGroups + ? getSortedAccessorsForGroup(datasource, layer, 'secondaryGroups') + : [], ].flat() ) ); - const operations = groups + const operations = accessors .map((columnId) => ({ columnId, operation: datasource?.getOperationForColumnId(columnId) as Operation | null, @@ -323,11 +327,11 @@ function expressionHelper( type: 'expression', chain: [ ...(datasourceAst ? datasourceAst.chain : []), - ...groups + ...accessors .filter((columnId) => layer.collapseFns?.[columnId]) .map((columnId) => { return buildExpressionFunction('lens_collapse', { - by: groups.filter((chk) => chk !== columnId), + by: accessors.filter((chk) => chk !== columnId), metric: layer.metrics, fn: [layer.collapseFns![columnId]!], }).toAst(); diff --git a/x-pack/plugins/lens/public/visualizations/partition/visualization.test.ts b/x-pack/plugins/lens/public/visualizations/partition/visualization.test.ts index 2bf830c0028cd..a11c1667f807f 100644 --- a/x-pack/plugins/lens/public/visualizations/partition/visualization.test.ts +++ b/x-pack/plugins/lens/public/visualizations/partition/visualization.test.ts @@ -448,119 +448,173 @@ describe('pie_visualization', () => { }); }); - it("doesn't count collapsed columns toward the dimension limits", () => { - const colIds = new Array(PartitionChartsMeta.pie.maxBuckets) - .fill(undefined) - .map((_, i) => String(i + 1)); - - const frame = mockFrame(); - frame.datasourceLayers[LAYER_ID]!.getTableSpec = () => - colIds.map((id) => ({ columnId: id, fields: [] })); + it('orders metric accessors by datasource column order', () => { + const colIds = ['1', '2', '3', '4']; const state = getExampleState(); - state.layers[0].primaryGroups = colIds; - - const getConfig = (_state: PieVisualizationState) => - pieVisualization.getConfiguration({ - state: _state, - frame, - layerId: state.layers[0].layerId, - }); - - expect(findPrimaryGroup(getConfig(state))?.supportsMoreColumns).toBeFalsy(); + state.layers[0].metrics = colIds; + state.layers[0].allowMultipleMetrics = true; - const stateWithCollapsed = cloneDeep(state); - stateWithCollapsed.layers[0].collapseFns = { '1': 'sum' }; + const frame = mockFrame(); + frame.datasourceLayers[LAYER_ID]!.getTableSpec = () => + // reverse the column IDs in the datasource + colIds.reverse().map((id) => ({ columnId: id, fields: [] })); + + // this is to make sure the accessors get sorted before palette colors are applied + const palette = paletteServiceMock.get('default'); + palette.getCategoricalColor + .mockReturnValueOnce('color 1') + .mockReturnValueOnce('color 2') + .mockReturnValueOnce('color 3') + .mockReturnValueOnce('color 4'); + + const config = pieVisualization.getConfiguration({ + state, + frame, + layerId: state.layers[0].layerId, + }); - expect(findPrimaryGroup(getConfig(stateWithCollapsed))?.supportsMoreColumns).toBeTruthy(); + expect(findMetricGroup(config)?.accessors).toMatchInlineSnapshot(` + Array [ + Object { + "color": "color 1", + "columnId": "4", + "triggerIconType": "color", + }, + Object { + "color": "color 2", + "columnId": "3", + "triggerIconType": "color", + }, + Object { + "color": "color 3", + "columnId": "2", + "triggerIconType": "color", + }, + Object { + "color": "color 4", + "columnId": "1", + "triggerIconType": "color", + }, + ] + `); }); - it('counts multiple metrics toward the dimension limits when not mosaic', () => { - const colIds = new Array(PartitionChartsMeta.pie.maxBuckets - 1) - .fill(undefined) - .map((_, i) => String(i + 1)); + describe('dimension limits', () => { + it("doesn't count collapsed columns toward the dimension limits", () => { + const colIds = new Array(PartitionChartsMeta.pie.maxBuckets) + .fill(undefined) + .map((_, i) => String(i + 1)); - const frame = mockFrame(); - frame.datasourceLayers[LAYER_ID]!.getTableSpec = () => - colIds.map((id) => ({ columnId: id, fields: [] })); + const frame = mockFrame(); + frame.datasourceLayers[LAYER_ID]!.getTableSpec = () => + colIds.map((id) => ({ columnId: id, fields: [] })); - const state = getExampleState(); - state.layers[0].primaryGroups = colIds; - state.layers[0].allowMultipleMetrics = true; + const state = getExampleState(); + state.layers[0].primaryGroups = colIds; - const getConfig = (_state: PieVisualizationState) => - pieVisualization.getConfiguration({ - state: _state, - frame, - layerId: state.layers[0].layerId, - }); + const getConfig = (_state: PieVisualizationState) => + pieVisualization.getConfiguration({ + state: _state, + frame, + layerId: state.layers[0].layerId, + }); - expect(findPrimaryGroup(getConfig(state))?.supportsMoreColumns).toBeTruthy(); + expect(findPrimaryGroup(getConfig(state))?.supportsMoreColumns).toBeFalsy(); - const stateWithMultipleMetrics = cloneDeep(state); - stateWithMultipleMetrics.layers[0].metrics.push('1', '2'); + const stateWithCollapsed = cloneDeep(state); + stateWithCollapsed.layers[0].collapseFns = { '1': 'sum' }; - expect( - findPrimaryGroup(getConfig(stateWithMultipleMetrics))?.supportsMoreColumns - ).toBeFalsy(); - }); + expect(findPrimaryGroup(getConfig(stateWithCollapsed))?.supportsMoreColumns).toBeTruthy(); + }); - it('does NOT count multiple metrics toward the dimension limits when mosaic', () => { - const frame = mockFrame(); - frame.datasourceLayers[LAYER_ID]!.getTableSpec = () => []; + it('counts multiple metrics toward the dimension limits when not mosaic', () => { + const colIds = new Array(PartitionChartsMeta.pie.maxBuckets - 1) + .fill(undefined) + .map((_, i) => String(i + 1)); - const state = getExampleState(); - state.shape = 'mosaic'; - state.layers[0].primaryGroups = []; - state.layers[0].allowMultipleMetrics = false; // always true for mosaic + const frame = mockFrame(); + frame.datasourceLayers[LAYER_ID]!.getTableSpec = () => + colIds.map((id) => ({ columnId: id, fields: [] })); - const getConfig = (_state: PieVisualizationState) => - pieVisualization.getConfiguration({ - state: _state, - frame, - layerId: state.layers[0].layerId, - }); + const state = getExampleState(); + state.layers[0].primaryGroups = colIds; + state.layers[0].allowMultipleMetrics = true; - expect(findPrimaryGroup(getConfig(state))?.supportsMoreColumns).toBeTruthy(); + const getConfig = (_state: PieVisualizationState) => + pieVisualization.getConfiguration({ + state: _state, + frame, + layerId: state.layers[0].layerId, + }); - const stateWithMultipleMetrics = cloneDeep(state); - stateWithMultipleMetrics.layers[0].metrics.push('1', '2'); + expect(findPrimaryGroup(getConfig(state))?.supportsMoreColumns).toBeTruthy(); - expect( - findPrimaryGroup(getConfig(stateWithMultipleMetrics))?.supportsMoreColumns - ).toBeTruthy(); - }); + const stateWithMultipleMetrics = cloneDeep(state); + stateWithMultipleMetrics.layers[0].metrics.push('1', '2'); - it('reports too many metric dimensions if multiple not enabled', () => { - const colIds = ['1', '2', '3', '4']; + expect( + findPrimaryGroup(getConfig(stateWithMultipleMetrics))?.supportsMoreColumns + ).toBeFalsy(); + }); - const frame = mockFrame(); - frame.datasourceLayers[LAYER_ID]!.getTableSpec = () => - colIds.map((id) => ({ columnId: id, fields: [] })); + it('does NOT count multiple metrics toward the dimension limits when mosaic', () => { + const frame = mockFrame(); + frame.datasourceLayers[LAYER_ID]!.getTableSpec = () => []; - const state = getExampleState(); - state.layers[0].metrics = colIds; - state.layers[0].allowMultipleMetrics = false; - expect( - findMetricGroup( - pieVisualization.getConfiguration({ - state, - frame, - layerId: state.layers[0].layerId, - }) - )?.dimensionsTooMany - ).toBe(3); + const state = getExampleState(); + state.shape = 'mosaic'; + state.layers[0].primaryGroups = []; + state.layers[0].allowMultipleMetrics = false; // always true for mosaic - state.layers[0].allowMultipleMetrics = true; - expect( - findMetricGroup( + const getConfig = (_state: PieVisualizationState) => pieVisualization.getConfiguration({ - state, + state: _state, frame, layerId: state.layers[0].layerId, - }) - )?.dimensionsTooMany - ).toBe(0); + }); + + expect(findPrimaryGroup(getConfig(state))?.supportsMoreColumns).toBeTruthy(); + + const stateWithMultipleMetrics = cloneDeep(state); + stateWithMultipleMetrics.layers[0].metrics.push('1', '2'); + + expect( + findPrimaryGroup(getConfig(stateWithMultipleMetrics))?.supportsMoreColumns + ).toBeTruthy(); + }); + + it('reports too many metric dimensions if multiple not enabled', () => { + const colIds = ['1', '2', '3', '4']; + + const frame = mockFrame(); + frame.datasourceLayers[LAYER_ID]!.getTableSpec = () => + colIds.map((id) => ({ columnId: id, fields: [] })); + + const state = getExampleState(); + state.layers[0].metrics = colIds; + state.layers[0].allowMultipleMetrics = false; + expect( + findMetricGroup( + pieVisualization.getConfiguration({ + state, + frame, + layerId: state.layers[0].layerId, + }) + )?.dimensionsTooMany + ).toBe(3); + + state.layers[0].allowMultipleMetrics = true; + expect( + findMetricGroup( + pieVisualization.getConfiguration({ + state, + frame, + layerId: state.layers[0].layerId, + }) + )?.dimensionsTooMany + ).toBe(0); + }); }); it.each(Object.values(PieChartTypes).filter((type) => type !== 'mosaic'))( diff --git a/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx b/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx index cedfb12f72df7..a5c218a6483e1 100644 --- a/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx @@ -29,7 +29,7 @@ import type { } from '../../types'; import { getColumnToLabelMap, - getSortedGroups, + getSortedAccessorsForGroup, toExpression, toPreviewExpression, } from './to_expression'; @@ -91,12 +91,13 @@ export const getDefaultColorForMultiMetricDimension = ({ datasource: DatasourcePublicAPI | undefined; }) => { const columnToLabelMap = datasource ? getColumnToLabelMap(layer.metrics, datasource) : {}; + const sortedMetrics = getSortedAccessorsForGroup(datasource, layer, 'metrics'); return paletteService.get('default').getCategoricalColor([ { name: columnToLabelMap[columnId], - rankAtDepth: layer.metrics.indexOf(columnId), - totalSeriesAtDepth: layer.metrics.length, + rankAtDepth: sortedMetrics.indexOf(columnId), + totalSeriesAtDepth: sortedMetrics.length, }, ]) as string; }; @@ -167,7 +168,7 @@ export const getPieVisualization = ({ const datasource = frame.datasourceLayers[layer.layerId]; const getPrimaryGroupConfig = (): VisualizationDimensionGroupConfig => { - const originalOrder = getSortedGroups(datasource, layer); + const originalOrder = getSortedAccessorsForGroup(datasource, layer, 'primaryGroups'); // When we add a column it could be empty, and therefore have no order const accessors = originalOrder.map((accessor) => ({ columnId: accessor, @@ -273,7 +274,11 @@ export const getPieVisualization = ({ }; const getSecondaryGroupConfig = (): VisualizationDimensionGroupConfig | undefined => { - const originalSecondaryOrder = getSortedGroups(datasource, layer, 'secondaryGroups'); + const originalSecondaryOrder = getSortedAccessorsForGroup( + datasource, + layer, + 'secondaryGroups' + ); const accessors = originalSecondaryOrder.map((accessor) => ({ columnId: accessor, triggerIconType: isCollapsed(accessor, layer) ? 'aggregate' : undefined, @@ -317,7 +322,11 @@ export const getPieVisualization = ({ const getMetricGroupConfig = (): VisualizationDimensionGroupConfig => { const hasSliceBy = layer.primaryGroups.length + (layer.secondaryGroups?.length ?? 0); - const accessors: AccessorConfig[] = layer.metrics.map((columnId, index) => ({ + const accessors: AccessorConfig[] = getSortedAccessorsForGroup( + datasource, + layer, + 'metrics' + ).map((columnId) => ({ columnId, ...(layer.allowMultipleMetrics ? hasSliceBy @@ -371,7 +380,7 @@ export const getPieVisualization = ({ }; }, - setDimension({ prevState, layerId, columnId, groupId }) { + setDimension({ prevState, layerId, columnId, groupId, previousColumn }) { return { ...prevState, layers: prevState.layers.map((l) => { @@ -393,7 +402,8 @@ export const getPieVisualization = ({ ], }; } - return { ...l, metrics: [...l.metrics.filter((metric) => metric !== columnId), columnId] }; + const metrics = [...l.metrics.filter((metric) => metric !== columnId), columnId]; + return { ...l, metrics }; }), }; }, diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx b/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx index 639c8fe5a20db..ab98b69a79f0b 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx +++ b/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx @@ -155,12 +155,13 @@ export class MbMap extends Component { async _createMbMapInstance(initialView: MapCenterAndZoom | null): Promise { this._reportUsage(); + const glyphsUrlTemplate = await getGlyphUrl(); return new Promise((resolve) => { const mbStyle = { version: 8 as 8, sources: {}, layers: [], - glyphs: getGlyphUrl(), + glyphs: glyphsUrlTemplate, }; const options: MapOptions = { diff --git a/x-pack/plugins/maps/public/embeddable/map_embeddable.test.tsx b/x-pack/plugins/maps/public/embeddable/map_embeddable.test.tsx new file mode 100644 index 0000000000000..bcb5aea3cca85 --- /dev/null +++ b/x-pack/plugins/maps/public/embeddable/map_embeddable.test.tsx @@ -0,0 +1,290 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { v4 as uuidv4 } from 'uuid'; +import { getControlledBy, MapEmbeddable } from './map_embeddable'; +import { buildExistsFilter, disableFilter, pinFilter, toggleFilterNegated } from '@kbn/es-query'; +import type { DataViewFieldBase, DataViewBase } from '@kbn/es-query'; +import { MapEmbeddableConfig, MapEmbeddableInput } from './types'; +import { MapSavedObjectAttributes } from '../../common/map_saved_object_type'; + +jest.mock('../kibana_services', () => { + return { + getHttp() { + return { + basePath: { + prepend: (url: string) => url, + }, + }; + }, + getMapsCapabilities() { + return { save: true }; + }, + getSearchService() { + return { + session: { + getSearchOptions() { + return undefined; + }, + }, + }; + }, + getShowMapsInspectorAdapter() { + return false; + }, + getTimeFilter() { + return { + getTime() { + return { from: 'now-7d', to: 'now' }; + }, + }; + }, + }; +}); + +jest.mock('../connected_components/map_container', () => { + return { + MapContainer: () => { + return
      mockLayerTOC
      ; + }, + }; +}); + +jest.mock('../routes/map_page', () => { + class MockSavedMap { + // eslint-disable-next-line @typescript-eslint/no-var-requires + private _store = require('../reducers/store').createMapStore(); + private _attributes: MapSavedObjectAttributes = { + title: 'myMap', + }; + + whenReady = async function () {}; + + getStore() { + return this._store; + } + getAttributes() { + return this._attributes; + } + getAutoFitToBounds() { + return true; + } + getSharingSavedObjectProps() { + return null; + } + } + return { SavedMap: MockSavedMap }; +}); + +function untilInitialized(mapEmbeddable: MapEmbeddable): Promise { + return new Promise((resolve) => { + // @ts-expect-error setInitializationFinished is protected but we are overriding it to know when embeddable is initialized + mapEmbeddable.setInitializationFinished = () => { + resolve(); + }; + }); +} + +function onNextTick(): Promise { + // wait one tick to give observables time to fire + return new Promise((resolve) => setTimeout(resolve, 0)); +} + +describe('shouldFetch$', () => { + test('should not fetch when search context does not change', async () => { + const mapEmbeddable = new MapEmbeddable( + {} as unknown as MapEmbeddableConfig, + { + id: 'map1', + } as unknown as MapEmbeddableInput + ); + await untilInitialized(mapEmbeddable); + + const fetchSpy = jest.spyOn(mapEmbeddable, '_dispatchSetQuery'); + + mapEmbeddable.updateInput({ + title: 'updated map title', + }); + + await onNextTick(); + + expect(fetchSpy).not.toHaveBeenCalled(); + }); + + describe('on filters change', () => { + test('should fetch on filter change', async () => { + const existsFilter = buildExistsFilter( + { + name: 'myFieldName', + } as DataViewFieldBase, + { + id: 'myDataViewId', + } as DataViewBase + ); + const mapEmbeddable = new MapEmbeddable( + {} as unknown as MapEmbeddableConfig, + { + id: 'map1', + filters: [existsFilter], + } as unknown as MapEmbeddableInput + ); + await untilInitialized(mapEmbeddable); + + const fetchSpy = jest.spyOn(mapEmbeddable, '_dispatchSetQuery'); + + mapEmbeddable.updateInput({ + filters: [toggleFilterNegated(existsFilter)], + }); + + await onNextTick(); + + expect(fetchSpy).toHaveBeenCalled(); + }); + + test('should not fetch on disabled filter change', async () => { + const disabledFilter = disableFilter( + buildExistsFilter( + { + name: 'myFieldName', + } as DataViewFieldBase, + { + id: 'myDataViewId', + } as DataViewBase + ) + ); + const mapEmbeddable = new MapEmbeddable( + {} as unknown as MapEmbeddableConfig, + { + id: 'map1', + filters: [disabledFilter], + } as unknown as MapEmbeddableInput + ); + await untilInitialized(mapEmbeddable); + + const fetchSpy = jest.spyOn(mapEmbeddable, '_dispatchSetQuery'); + + mapEmbeddable.updateInput({ + filters: [toggleFilterNegated(disabledFilter)], + }); + + await onNextTick(); + + expect(fetchSpy).not.toHaveBeenCalled(); + }); + + test('should not fetch when unpinned filter is pinned', async () => { + const unpinnedFilter = buildExistsFilter( + { + name: 'myFieldName', + } as DataViewFieldBase, + { + id: 'myDataViewId', + } as DataViewBase + ); + const mapEmbeddable = new MapEmbeddable( + {} as unknown as MapEmbeddableConfig, + { + id: 'map1', + filters: [unpinnedFilter], + } as unknown as MapEmbeddableInput + ); + await untilInitialized(mapEmbeddable); + + const fetchSpy = jest.spyOn(mapEmbeddable, '_dispatchSetQuery'); + + mapEmbeddable.updateInput({ + filters: [pinFilter(unpinnedFilter)], + }); + + await onNextTick(); + + expect(fetchSpy).not.toHaveBeenCalled(); + }); + + test('should not fetch on filter controlled by map embeddable change', async () => { + const embeddableId = 'map1'; + const filter = buildExistsFilter( + { + name: 'myFieldName', + } as DataViewFieldBase, + { + id: 'myDataViewId', + } as DataViewBase + ); + const controlledByFilter = { + ...filter, + meta: { + ...filter.meta, + controlledBy: getControlledBy(embeddableId), + }, + }; + const mapEmbeddable = new MapEmbeddable( + {} as unknown as MapEmbeddableConfig, + { + id: embeddableId, + filters: [controlledByFilter], + } as unknown as MapEmbeddableInput + ); + await untilInitialized(mapEmbeddable); + + const fetchSpy = jest.spyOn(mapEmbeddable, '_dispatchSetQuery'); + + mapEmbeddable.updateInput({ + filters: [toggleFilterNegated(controlledByFilter)], + }); + + await onNextTick(); + + expect(fetchSpy).not.toHaveBeenCalled(); + }); + }); + + describe('on searchSessionId change', () => { + test('should fetch when filterByMapExtent is false', async () => { + const mapEmbeddable = new MapEmbeddable( + {} as unknown as MapEmbeddableConfig, + { + id: 'map1', + filterByMapExtent: false, + } as unknown as MapEmbeddableInput + ); + await untilInitialized(mapEmbeddable); + + const fetchSpy = jest.spyOn(mapEmbeddable, '_dispatchSetQuery'); + + mapEmbeddable.updateInput({ + searchSessionId: uuidv4(), + }); + + await onNextTick(); + + expect(fetchSpy).toHaveBeenCalled(); + }); + + test('should not fetch when filterByMapExtent is true', async () => { + const mapEmbeddable = new MapEmbeddable( + {} as unknown as MapEmbeddableConfig, + { + id: 'map1', + filterByMapExtent: true, + } as unknown as MapEmbeddableInput + ); + await untilInitialized(mapEmbeddable); + + const fetchSpy = jest.spyOn(mapEmbeddable, '_dispatchSetQuery'); + + mapEmbeddable.updateInput({ + searchSessionId: uuidv4(), + }); + + await onNextTick(); + + expect(fetchSpy).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx index 51b806cec5dce..41579d4f5375d 100644 --- a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx +++ b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx @@ -14,7 +14,7 @@ import { render, unmountComponentAtNode } from 'react-dom'; import { Subscription } from 'rxjs'; import { Unsubscribe } from 'redux'; import { EuiEmptyPrompt } from '@elastic/eui'; -import { type Filter, compareFilters, type TimeRange, type Query } from '@kbn/es-query'; +import { type Filter } from '@kbn/es-query'; import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; import { Embeddable, @@ -24,6 +24,7 @@ import { VALUE_CLICK_TRIGGER, omitGenericEmbeddableInput, FilterableEmbeddable, + shouldFetch$, } from '@kbn/embeddable-plugin/public'; import { ActionExecutionContext } from '@kbn/ui-actions-plugin/public'; import { APPLY_FILTER_TRIGGER } from '@kbn/data-plugin/public'; @@ -104,6 +105,10 @@ function getIsRestore(searchSessionId?: string) { return searchSessionOptions ? searchSessionOptions.isRestore : false; } +export function getControlledBy(id: string) { + return `mapEmbeddablePanel${id}`; +} + export class MapEmbeddable extends Embeddable implements ReferenceOrValueEmbeddable, FilterableEmbeddable @@ -114,15 +119,10 @@ export class MapEmbeddable private _isActive: boolean; private _savedMap: SavedMap; private _renderTooltipContent?: RenderToolTipContent; - private _subscription: Subscription; + private _subscriptions: Subscription[] = []; private _prevIsRestore: boolean = false; private _prevMapExtent?: MapExtent; - private _prevTimeRange?: TimeRange; - private _prevTimeslice?: [number, number]; - private _prevQuery?: Query; - private _prevFilters: Filter[] = []; private _prevSyncColors?: boolean; - private _prevSearchSessionId?: string; private _domNode?: HTMLElement; private _unsubscribeFromStore?: Unsubscribe; private _isInitialized = false; @@ -145,8 +145,8 @@ export class MapEmbeddable this._isActive = true; this._savedMap = new SavedMap({ mapEmbeddableInput: initialInput }); this._initializeSaveMap(); - this._subscription = this.getUpdated$().subscribe(() => this.onUpdate()); - this._controlledBy = `mapEmbeddablePanel${this.id}`; + this._subscriptions.push(this.getUpdated$().subscribe(() => this.onUpdate())); + this._controlledBy = getControlledBy(this.id); } public reportsEmbeddableLoad() { @@ -193,9 +193,20 @@ export class MapEmbeddable // Passing callback into redux store instead of regular pattern of getting redux state changes for performance reasons store.dispatch(setOnMapMove(this._propogateMapMovement)); - this._dispatchSetQuery({ - forceRefresh: false, - }); + this._dispatchSetQuery({ forceRefresh: false }); + this._subscriptions.push( + shouldFetch$(this.getUpdated$(), () => { + return { + ...this.getInput(), + filters: this._getInputFilters(), + searchSessionId: this._getSearchSessionId(), + }; + }).subscribe(() => { + this._dispatchSetQuery({ + forceRefresh: false, + }); + }) + ); const mapStateJSON = this._savedMap.getAttributes().mapStateJSON; if (mapStateJSON) { @@ -309,18 +320,6 @@ export class MapEmbeddable } onUpdate() { - if ( - !_.isEqual(this.input.timeRange, this._prevTimeRange) || - !_.isEqual(this.input.timeslice, this._prevTimeslice) || - !_.isEqual(this.input.query, this._prevQuery) || - !compareFilters(this._getFilters(), this._prevFilters) || - this._getSearchSessionId() !== this._prevSearchSessionId - ) { - this._dispatchSetQuery({ - forceRefresh: false, - }); - } - if (this.input.syncColors !== this._prevSyncColors) { this._dispatchSetChartsPaletteServiceGetColor(this.input.syncColors); } @@ -382,7 +381,7 @@ export class MapEmbeddable } }; - _getFilters() { + _getInputFilters() { return this.input.filters ? this.input.filters.filter( (filter) => !filter.meta.disabled && filter.meta.controlledBy !== this._controlledBy @@ -401,15 +400,9 @@ export class MapEmbeddable } _dispatchSetQuery({ forceRefresh }: { forceRefresh: boolean }) { - const filters = this._getFilters(); - this._prevTimeRange = this.input.timeRange; - this._prevTimeslice = this.input.timeslice; - this._prevQuery = this.input.query; - this._prevFilters = filters; - this._prevSearchSessionId = this._getSearchSessionId(); this._savedMap.getStore().dispatch( setQuery({ - filters, + filters: this._getInputFilters(), query: this.input.query, timeFilters: this.input.timeRange, timeslice: this.input.timeslice @@ -675,9 +668,9 @@ export class MapEmbeddable unmountComponentAtNode(this._domNode); } - if (this._subscription) { - this._subscription.unsubscribe(); - } + this._subscriptions.forEach((subscription) => { + subscription.unsubscribe(); + }); } reload() { diff --git a/x-pack/plugins/maps/public/embeddable/types.ts b/x-pack/plugins/maps/public/embeddable/types.ts index d024eee88ae6d..1d29a23ec44c5 100644 --- a/x-pack/plugins/maps/public/embeddable/types.ts +++ b/x-pack/plugins/maps/public/embeddable/types.ts @@ -5,7 +5,6 @@ * 2.0. */ -import type { Filter } from '@kbn/es-query'; import type { DataView } from '@kbn/data-plugin/common'; import { Embeddable, @@ -13,7 +12,7 @@ import { EmbeddableOutput, SavedObjectEmbeddableInput, } from '@kbn/embeddable-plugin/public'; -import type { Query, TimeRange } from '@kbn/es-query'; +import type { Filter, Query, TimeRange } from '@kbn/es-query'; import { MapCenterAndZoom, MapExtent, MapSettings } from '../../common/descriptor_types'; import { MapSavedObjectAttributes } from '../../common/map_saved_object_type'; diff --git a/x-pack/plugins/maps/public/util.test.js b/x-pack/plugins/maps/public/util.test.js index 7fc88578b378a..48498f87fe7c0 100644 --- a/x-pack/plugins/maps/public/util.test.js +++ b/x-pack/plugins/maps/public/util.test.js @@ -5,60 +5,88 @@ * 2.0. */ -import { getGlyphUrl, makePublicExecutionContext } from './util'; - -const MOCK_EMS_SETTINGS = { - isEMSEnabled: () => true, -}; +import { + getGlyphUrl, + makePublicExecutionContext, + testOnlyClearCanAccessEmsFontsPromise, +} from './util'; describe('getGlyphUrl', () => { describe('EMS enabled', () => { - beforeAll(() => { + beforeEach(() => { require('./kibana_services').getHttp = () => ({ basePath: { - prepend: (url) => url, // No need to actually prepend a dev basepath for test + prepend: (path) => `abc${path}`, }, }); + testOnlyClearCanAccessEmsFontsPromise(); }); - describe('EMS proxy disabled', () => { + describe('offline', () => { beforeAll(() => { require('./kibana_services').getEMSSettings = () => { return { getEMSFontLibraryUrl() { - return 'foobar'; + return 'https://tiles.maps.elastic.co/fonts/{fontstack}/{range}.pbf'; }, isEMSEnabled() { return true; }, }; }; + require('node-fetch').default = () => { + throw new Error('Simulated offline environment with no EMS access'); + }; }); - test('should return EMS fonts URL', async () => { - expect(getGlyphUrl()).toBe('foobar'); + test('should return kibana fonts template URL', async () => { + expect(await getGlyphUrl()).toBe('abc/api/maps/fonts/{fontstack}/{range}'); + }); + }); + + describe('online', () => { + beforeAll(() => { + require('./kibana_services').getEMSSettings = () => { + return { + getEMSFontLibraryUrl() { + return 'https://tiles.maps.elastic.co/fonts/{fontstack}/{range}.pbf'; + }, + isEMSEnabled() { + return true; + }, + }; + }; + require('node-fetch').default = () => { + return Promise.resolve({ status: 200 }); + }; + }); + + test('should return EMS fonts template URL', async () => { + expect(await getGlyphUrl()).toBe( + 'https://tiles.maps.elastic.co/fonts/{fontstack}/{range}.pbf' + ); }); }); }); describe('EMS disabled', () => { beforeAll(() => { - const mockHttp = { - basePath: { - prepend: (path) => `abc${path}`, - }, + require('./kibana_services').getHttp = () => { + return { + basePath: { + prepend: (path) => `abc${path}`, + }, + }; }; - require('./kibana_services').getHttp = () => mockHttp; require('./kibana_services').getEMSSettings = () => { return { - ...MOCK_EMS_SETTINGS, isEMSEnabled: () => false, }; }; }); - test('should return kibana fonts URL', async () => { - expect(getGlyphUrl()).toBe('abc/api/maps/fonts/{fontstack}/{range}'); + test('should return kibana fonts template URL', async () => { + expect(await getGlyphUrl()).toBe('abc/api/maps/fonts/{fontstack}/{range}'); }); }); }); diff --git a/x-pack/plugins/maps/public/util.ts b/x-pack/plugins/maps/public/util.ts index d63072b163599..3243c8a95cf74 100644 --- a/x-pack/plugins/maps/public/util.ts +++ b/x-pack/plugins/maps/public/util.ts @@ -5,6 +5,7 @@ * 2.0. */ +import fetch from 'node-fetch'; import { EMSClient, FileLayer, TMSService } from '@elastic/ems-client'; import type { KibanaExecutionContext } from '@kbn/core/public'; import { FONTS_API_PATH } from '../common/constants'; @@ -60,9 +61,46 @@ async function getEMSClient(): Promise { return emsClient; } -export function getGlyphUrl(): string { +let canAccessEmsFontsPromise: Promise | null = null; +async function canAccessEmsFonts(): Promise { + if (!canAccessEmsFontsPromise) { + canAccessEmsFontsPromise = new Promise(async (resolve) => { + try { + const emsSettings = getEMSSettings(); + if (!emsSettings!.isEMSEnabled()) { + resolve(false); + } + const emsFontUrlTemplate = emsSettings!.getEMSFontLibraryUrl(); + + const emsFontUrl = emsFontUrlTemplate + .replace('{fontstack}', 'Open Sans') + .replace('{range}', '0-255'); + const resp = await fetch(emsFontUrl, { + method: 'HEAD', + }); + if (resp.status >= 400) { + throw new Error(`status: ${resp.status}`); + } + resolve(true); + } catch (error) { + // eslint-disable-next-line no-console + console.warn( + `Unable to access fonts from Elastic Maps Service (EMS). Set kibana.yml 'map.includeElasticMapsService: false' to avoid unnecessary EMS requests.` + ); + resolve(false); + } + }); + } + return canAccessEmsFontsPromise; +} +// test only function to reset singleton for different test cases. +export function testOnlyClearCanAccessEmsFontsPromise() { + canAccessEmsFontsPromise = null; +} + +export async function getGlyphUrl(): Promise { const emsSettings = getEMSSettings(); - if (!emsSettings!.isEMSEnabled()) { + if (!emsSettings!.isEMSEnabled() || !(await canAccessEmsFonts())) { return getHttp().basePath.prepend(`/${FONTS_API_PATH}/{fontstack}/{range}`); } diff --git a/x-pack/plugins/maps/server/kibana_server_services.ts b/x-pack/plugins/maps/server/kibana_server_services.ts index 84cedeb721824..ef12c3edaa81f 100644 --- a/x-pack/plugins/maps/server/kibana_server_services.ts +++ b/x-pack/plugins/maps/server/kibana_server_services.ts @@ -6,19 +6,12 @@ */ import { CoreStart } from '@kbn/core/server'; -import { StartDeps } from './types'; let coreStart: CoreStart; -let pluginsStart: StartDeps; -export function setStartServices(core: CoreStart, plugins: StartDeps) { +export function setStartServices(core: CoreStart) { coreStart = core; - pluginsStart = plugins; } export const getSavedObjectClient = (extraTypes?: string[]) => { return coreStart.savedObjects.createInternalRepository(extraTypes); }; - -export const getIndexPatternsServiceFactory = () => - pluginsStart.data.indexPatterns.dataViewsServiceFactory; -export const getElasticsearch = () => coreStart.elasticsearch; diff --git a/x-pack/plugins/maps/server/maps_telemetry/collectors/register.ts b/x-pack/plugins/maps/server/maps_telemetry/collectors/register.ts index 9f2e520c428a2..77d1112f89817 100644 --- a/x-pack/plugins/maps/server/maps_telemetry/collectors/register.ts +++ b/x-pack/plugins/maps/server/maps_telemetry/collectors/register.ts @@ -18,10 +18,6 @@ export function registerMapsUsageCollector(usageCollection?: UsageCollectionSetu isReady: () => true, fetch: async () => await getMapsTelemetry(), schema: { - indexPatternsWithGeoFieldCount: { type: 'long' }, - indexPatternsWithGeoPointFieldCount: { type: 'long' }, - indexPatternsWithGeoShapeFieldCount: { type: 'long' }, - geoShapeAggLayersCount: { type: 'long' }, mapsTotalCount: { type: 'long' }, timeCaptured: { type: 'date' }, layerTypes: { diff --git a/x-pack/plugins/maps/server/maps_telemetry/find_maps.test.ts b/x-pack/plugins/maps/server/maps_telemetry/find_maps.test.ts index acc9d11c66d4a..64215045bd3eb 100644 --- a/x-pack/plugins/maps/server/maps_telemetry/find_maps.test.ts +++ b/x-pack/plugins/maps/server/maps_telemetry/find_maps.test.ts @@ -29,7 +29,7 @@ function getMockSavedObjectsClient(perPage: number) { } as unknown as ISavedObjectsRepository; } -test('should process all map saved objects with single page', async () => { +test('should process all map saved objects with a single page', async () => { const foundMapIds: string[] = []; await findMaps(getMockSavedObjectsClient(20), async (savedObject) => { foundMapIds.push(savedObject.id); @@ -43,7 +43,7 @@ test('should process all map saved objects with single page', async () => { ]); }); -test('should process all map saved objects with with paging', async () => { +test('should process all map saved objects with paging', async () => { const foundMapIds: string[] = []; await findMaps(getMockSavedObjectsClient(2), async (savedObject) => { foundMapIds.push(savedObject.id); diff --git a/x-pack/plugins/maps/server/maps_telemetry/index_pattern_stats/index_pattern_stats_collector.test.ts b/x-pack/plugins/maps/server/maps_telemetry/index_pattern_stats/index_pattern_stats_collector.test.ts deleted file mode 100644 index 9877c29bc5951..0000000000000 --- a/x-pack/plugins/maps/server/maps_telemetry/index_pattern_stats/index_pattern_stats_collector.test.ts +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { asyncForEach } from '@kbn/std'; -// @ts-ignore -import mapSavedObjects from '../../../common/telemetry/test_resources/sample_map_saved_objects.json'; -import { DataViewsService } from '@kbn/data-views-plugin/common'; -import { IndexPatternStatsCollector } from './index_pattern_stats_collector'; - -test('returns zeroed telemetry data when there are no saved objects', async () => { - const mockIndexPatternService = { - getIds: () => { - return []; - }, - } as unknown as DataViewsService; - const statsCollector = new IndexPatternStatsCollector(mockIndexPatternService); - const stats = await statsCollector.getStats(); - expect(stats).toEqual({ - geoShapeAggLayersCount: 0, - indexPatternsWithGeoFieldCount: 0, - indexPatternsWithGeoPointFieldCount: 0, - indexPatternsWithGeoShapeFieldCount: 0, - }); -}); - -test('returns expected telemetry data from saved objects', async () => { - const mockIndexPatternService = { - get: (id: string) => { - if (id === 'd3d7af60-4c81-11e8-b3d7-01146121b73d') { - return { - getFieldByName: (name: string) => { - return { type: 'geo_point' }; - }, - fields: { - getByType: (type: string) => { - return type === 'geo_point' ? [{}] : []; - }, - }, - }; - } - - if (id === '4a7f6010-0aed-11ea-9dd2-95afd7ad44d4') { - return { - getFieldByName: (name: string) => { - return { type: 'geo_shape' }; - }, - fields: { - getByType: (type: string) => { - return type === 'geo_shape' ? [{}] : []; - }, - }, - }; - } - - if (id === 'indexPatternWithNoGeoFields') { - return { - getFieldByName: (name: string) => { - return null; - }, - fields: { - getByType: (type: string) => { - return []; - }, - }, - }; - } - - throw new Error('Index pattern not found'); - }, - getIds: () => { - return [ - 'd3d7af60-4c81-11e8-b3d7-01146121b73d', - '4a7f6010-0aed-11ea-9dd2-95afd7ad44d4', - 'indexPatternWithNoGeoFields', - 'missingIndexPattern', - ]; - }, - } as unknown as DataViewsService; - const statsCollector = new IndexPatternStatsCollector(mockIndexPatternService); - await asyncForEach(mapSavedObjects, async (savedObject) => { - await statsCollector.push(savedObject); - }); - const stats = await statsCollector.getStats(); - expect(stats).toEqual({ - geoShapeAggLayersCount: 2, // index pattern '4a7f6010-0aed-11ea-9dd2-95afd7ad44d4' with geo_shape field is used in 2 maps with geo_tile_grid aggregation - indexPatternsWithGeoFieldCount: 2, - indexPatternsWithGeoPointFieldCount: 1, - indexPatternsWithGeoShapeFieldCount: 1, - }); -}); diff --git a/x-pack/plugins/maps/server/maps_telemetry/index_pattern_stats/index_pattern_stats_collector.ts b/x-pack/plugins/maps/server/maps_telemetry/index_pattern_stats/index_pattern_stats_collector.ts deleted file mode 100644 index 2b8047cdaf41f..0000000000000 --- a/x-pack/plugins/maps/server/maps_telemetry/index_pattern_stats/index_pattern_stats_collector.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 type { SavedObject } from '@kbn/core/server'; -import { asyncForEach } from '@kbn/std'; -import { KBN_FIELD_TYPES } from '@kbn/field-types'; -import { DataViewsService } from '@kbn/data-views-plugin/common'; -import { SCALING_TYPES, SOURCE_TYPES } from '../../../common/constants'; -import { injectReferences } from '../../../common/migrations/references'; -import { - ESGeoGridSourceDescriptor, - ESSearchSourceDescriptor, - LayerDescriptor, -} from '../../../common/descriptor_types'; -import type { MapSavedObjectAttributes } from '../../../common/map_saved_object_type'; -import { IndexPatternStats } from './types'; - -/* - * Use IndexPatternStatsCollector instance to track index pattern geospatial field stats. - */ -export class IndexPatternStatsCollector { - private _geoShapeAggCount = 0; - private _indexPatternsService: DataViewsService; - - constructor(indexPatternService: DataViewsService) { - this._indexPatternsService = indexPatternService; - } - - async push(savedObject: SavedObject) { - let layerList: LayerDescriptor[] = []; - try { - const { attributes } = injectReferences(savedObject); - if (!attributes.layerListJSON) { - return; - } - layerList = JSON.parse(attributes.layerListJSON); - } catch (e) { - return; - } - - let geoShapeAggCountPerMap = 0; - await asyncForEach(layerList, async (layerDescriptor) => { - if (await this._isGeoShapeAggLayer(layerDescriptor)) { - geoShapeAggCountPerMap++; - } - }); - this._geoShapeAggCount += geoShapeAggCountPerMap; - } - - async getStats(): Promise { - let geoCount = 0; - let pointCount = 0; - let shapeCount = 0; - - const indexPatternIds = await this._indexPatternsService.getIds(); - await asyncForEach(indexPatternIds, async (indexPatternId) => { - let indexPattern; - try { - indexPattern = await this._indexPatternsService.get(indexPatternId); - } catch (e) { - return; - } - const pointFields = indexPattern.fields.getByType(KBN_FIELD_TYPES.GEO_POINT); - const shapeFields = indexPattern.fields.getByType(KBN_FIELD_TYPES.GEO_SHAPE); - if (pointFields.length || shapeFields.length) { - geoCount++; - } - if (pointFields.length) { - pointCount++; - } - if (shapeFields.length) { - shapeCount++; - } - }); - - return { - // Tracks whether user uses Gold+ functionality of aggregating on geo_shape field - geoShapeAggLayersCount: this._geoShapeAggCount, - indexPatternsWithGeoFieldCount: geoCount, - indexPatternsWithGeoPointFieldCount: pointCount, - indexPatternsWithGeoShapeFieldCount: shapeCount, - }; - } - - async _isFieldGeoShape(indexPatternId: string, geoField: string | undefined): Promise { - if (!geoField || !indexPatternId) { - return false; - } - - let indexPattern; - try { - indexPattern = await this._indexPatternsService.get(indexPatternId); - } catch (e) { - return false; - } - - const field = indexPattern.getFieldByName(geoField); - return !!field && field.type === KBN_FIELD_TYPES.GEO_SHAPE; - } - - async _isGeoShapeAggLayer(layer: LayerDescriptor): Promise { - if (!layer.sourceDescriptor) { - return false; - } - - const sourceDescriptor = layer.sourceDescriptor; - if (sourceDescriptor.type === SOURCE_TYPES.ES_GEO_GRID) { - return await this._isFieldGeoShape( - (sourceDescriptor as ESGeoGridSourceDescriptor).indexPatternId, - (sourceDescriptor as ESGeoGridSourceDescriptor).geoField - ); - } - - if ( - sourceDescriptor.type === SOURCE_TYPES.ES_SEARCH && - (sourceDescriptor as ESSearchSourceDescriptor).scalingType === SCALING_TYPES.CLUSTERS - ) { - return await this._isFieldGeoShape( - (sourceDescriptor as ESSearchSourceDescriptor).indexPatternId, - (sourceDescriptor as ESSearchSourceDescriptor).geoField - ); - } - - return false; - } -} diff --git a/x-pack/plugins/maps/server/maps_telemetry/maps_telemetry.ts b/x-pack/plugins/maps/server/maps_telemetry/maps_telemetry.ts index b7497c010f15f..462f8ca57d692 100644 --- a/x-pack/plugins/maps/server/maps_telemetry/maps_telemetry.ts +++ b/x-pack/plugins/maps/server/maps_telemetry/maps_telemetry.ts @@ -5,37 +5,19 @@ * 2.0. */ -import { SavedObjectsClient } from '@kbn/core/server'; -import { - getElasticsearch, - getIndexPatternsServiceFactory, - getSavedObjectClient, -} from '../kibana_server_services'; +import { getSavedObjectClient } from '../kibana_server_services'; import { MapStats, MapStatsCollector } from './map_stats'; -import { IndexPatternStats, IndexPatternStatsCollector } from './index_pattern_stats'; import { findMaps } from './find_maps'; -export type MapsUsage = MapStats & IndexPatternStats; - -async function getReadOnlyIndexPatternsService() { - const factory = getIndexPatternsServiceFactory(); - return factory( - new SavedObjectsClient(getSavedObjectClient()), - getElasticsearch().client.asInternalUser - ); -} +export type MapsUsage = MapStats; export async function getMapsTelemetry(): Promise { const mapStatsCollector = new MapStatsCollector(); - const indexPatternService = await getReadOnlyIndexPatternsService(); - const indexPatternStatsCollector = new IndexPatternStatsCollector(indexPatternService); await findMaps(getSavedObjectClient(), async (savedObject) => { mapStatsCollector.push(savedObject.attributes); - await indexPatternStatsCollector.push(savedObject); }); return { - ...(await indexPatternStatsCollector.getStats()), ...mapStatsCollector.getStats(), }; } diff --git a/x-pack/plugins/maps/server/plugin.ts b/x-pack/plugins/maps/server/plugin.ts index 0fe317beef05e..1dc497a87fdd9 100644 --- a/x-pack/plugins/maps/server/plugin.ts +++ b/x-pack/plugins/maps/server/plugin.ts @@ -207,6 +207,6 @@ export class MapsPlugin implements Plugin { } start(core: CoreStart, plugins: StartDeps) { - setStartServices(core, plugins); + setStartServices(core); } } diff --git a/x-pack/plugins/maps/tsconfig.json b/x-pack/plugins/maps/tsconfig.json index 0505ffb7473c1..8d32c7229a4e0 100644 --- a/x-pack/plugins/maps/tsconfig.json +++ b/x-pack/plugins/maps/tsconfig.json @@ -62,7 +62,6 @@ "@kbn/safer-lodash-set", "@kbn/custom-integrations-plugin", "@kbn/config-schema", - "@kbn/field-types", "@kbn/controls-plugin", "@kbn/shared-ux-router", ], diff --git a/x-pack/plugins/ml/public/application/_app.scss b/x-pack/plugins/ml/public/application/_app.scss deleted file mode 100644 index 6dfae7a8921f3..0000000000000 --- a/x-pack/plugins/ml/public/application/_app.scss +++ /dev/null @@ -1,30 +0,0 @@ -// ML has app specific coloring for it's various warning levels. -// These are used almost everywhere. - -.ml-icon-severity-critical, -.ml-icon-severity-major, -.ml-icon-severity-minor, -.ml-icon-severity-warning, -.ml-icon-severity-unknown { - text-shadow: 1px 1px 1px $euiColorLightShade; -} - -.ml-icon-severity-critical { - color: $mlColorCriticalText; -} - -.ml-icon-severity-major { - color: $mlColorMajorText; -} - -.ml-icon-severity-minor { - color: $mlColorMinorText; -} - -.ml-icon-severity-warning { - color: $mlColorWarningText; -} - -.ml-icon-severity-unknown { - color: $mlColorUnknownText; -} diff --git a/x-pack/plugins/ml/public/application/_hacks.scss b/x-pack/plugins/ml/public/application/_hacks.scss deleted file mode 100644 index 13fabcb8045aa..0000000000000 --- a/x-pack/plugins/ml/public/application/_hacks.scss +++ /dev/null @@ -1,33 +0,0 @@ -.tab-datavisualizer_index_select, -.tab-timeseriesexplorer, -.tab-explorer { - // Make all page background white until More of the pages use EuiPage to wrap in panel-like components - background-color: $euiColorEmptyShade; -} - -// ML specific bootstrap hacks -.button-wrapper { - display: inline; -} - -.button-wrapper.disabled .kuiButton[disabled] { - pointer-events: none; -} - -.button-wrapper.disabled { - cursor: not-allowed; -} - -// SASSTODO: Remove all the floats -.clear, .clearfix { - clear: both; -} - -// Helper class for functional tests to disable anti-aliasing for canvas elements -.mlDisableAntiAliasing { - -webkit-font-smoothing : none; - - * canvas { - image-rendering: pixelated; - } -} diff --git a/x-pack/plugins/ml/public/application/_index.scss b/x-pack/plugins/ml/public/application/_index.scss index 309eea83dd527..ac9e16e5f3e78 100644 --- a/x-pack/plugins/ml/public/application/_index.scss +++ b/x-pack/plugins/ml/public/application/_index.scss @@ -4,30 +4,18 @@ // Protect the rest of Kibana from ML generic namespacing // SASSTODO: Prefix ml selectors instead .ml-app { - // App level - @import 'app'; - // Sub applications @import 'data_frame_analytics/index'; @import 'explorer/index'; // SASSTODO: This file needs to be rewritten - @import 'jobs/index'; // SASSTODO: This collection of sass files has multiple problems - @import 'overview/index'; @import 'timeseriesexplorer/index'; // Components @import 'components/annotations/annotation_description_list/index'; // SASSTODO: This file overwrites EUI directly @import 'components/anomalies_table/index'; // SASSTODO: This file overwrites EUI directly @import 'components/color_range_legend/index'; - @import 'components/controls/index'; @import 'components/entity_cell/index'; @import 'components/influencers_list/index'; - @import 'components/items_grid/index'; @import 'components/job_selector/index'; - @import 'components/loading_indicator/index'; // SASSTODO: This component should be replaced with EuiLoadingSpinner @import 'components/rule_editor/index'; // SASSTODO: This file overwrites EUI directly - @import 'components/stats_bar/index'; - @import 'components/ml_embedded_map/index'; - // Hacks are last so they can overwrite anything above if needed - @import 'hacks'; } diff --git a/x-pack/plugins/ml/public/application/components/anomalies_table/_anomalies_table.scss b/x-pack/plugins/ml/public/application/components/anomalies_table/_anomalies_table.scss index 06dfacf63a213..4f11115e82712 100644 --- a/x-pack/plugins/ml/public/application/components/anomalies_table/_anomalies_table.scss +++ b/x-pack/plugins/ml/public/application/components/anomalies_table/_anomalies_table.scss @@ -1,45 +1,5 @@ // SASSTODO: This file has several direct EUI overwrites that need to be removed .ml-anomalies-table { - .ml-icon-severity-critical, - .ml-icon-severity-major, - .ml-icon-severity-minor, - .ml-icon-severity-warning, - .ml-icon-severity-unknown { - color: inherit; - text-shadow: none; - } - - // SASSTODO: Should only be three options, logic moved to the JS, where EuiIcon accepts a color - .ml-icon-severity-critical { - .euiIcon { - fill: $mlColorCriticalText; - } - } - - .ml-icon-severity-major { - .euiIcon { - fill: $mlColorMajorText; - } - } - - .ml-icon-severity-minor { - .euiIcon { - fill: $mlColorMinorText; - } - } - - .ml-icon-severity-warning { - .euiIcon { - fill: $mlColorWarningText; - } - } - - .ml-icon-severity-unknown { - .euiIcon { - fill: $mlColorUnknownText; - } - } - tr th:first-child, tr td:first-child { width: $euiSizeXL; diff --git a/x-pack/plugins/ml/public/application/components/controls/_controls.scss b/x-pack/plugins/ml/public/application/components/controls/_controls.scss deleted file mode 100644 index d491e88dffa24..0000000000000 --- a/x-pack/plugins/ml/public/application/components/controls/_controls.scss +++ /dev/null @@ -1,16 +0,0 @@ -.ml-table-controls { - label { - font-size: $euiFontSizeXS; - padding: 0 0 $euiSizeXS $euiSizeXS; - } - - .ml-table-controls-element { - display: inline-block; - padding-left: $euiSize; - } - - select { - font-size: $euiFontSizeXS; - font-style: normal; - } -} diff --git a/x-pack/plugins/ml/public/application/components/controls/_index.scss b/x-pack/plugins/ml/public/application/components/controls/_index.scss deleted file mode 100644 index 84c0e21734b89..0000000000000 --- a/x-pack/plugins/ml/public/application/components/controls/_index.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import 'controls'; -@import 'select_severity/index'; diff --git a/x-pack/plugins/ml/public/application/components/controls/select_severity/_index.scss b/x-pack/plugins/ml/public/application/components/controls/select_severity/_index.scss deleted file mode 100644 index f238b65c9b955..0000000000000 --- a/x-pack/plugins/ml/public/application/components/controls/select_severity/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import 'select_severity'; \ No newline at end of file diff --git a/x-pack/plugins/ml/public/application/components/controls/select_severity/_select_severity.scss b/x-pack/plugins/ml/public/application/components/controls/select_severity/_select_severity.scss deleted file mode 100644 index 2edfe612183d0..0000000000000 --- a/x-pack/plugins/ml/public/application/components/controls/select_severity/_select_severity.scss +++ /dev/null @@ -1,6 +0,0 @@ -// SASSTODO: Should be removed -.ml-select-severity { - .euiFormControlLayoutClearButton { - display: none; - } -} diff --git a/x-pack/plugins/ml/public/application/components/items_grid/_index.scss b/x-pack/plugins/ml/public/application/components/items_grid/_index.scss deleted file mode 100644 index 243c80da52918..0000000000000 --- a/x-pack/plugins/ml/public/application/components/items_grid/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import 'items_grid'; \ No newline at end of file diff --git a/x-pack/plugins/ml/public/application/components/items_grid/_items_grid.scss b/x-pack/plugins/ml/public/application/components/items_grid/_items_grid.scss deleted file mode 100644 index c3bd6b115bbd6..0000000000000 --- a/x-pack/plugins/ml/public/application/components/items_grid/_items_grid.scss +++ /dev/null @@ -1,3 +0,0 @@ -.ml-items-grid-page-size-menu { - width: 140px; // SASSTODO: Needs a proper calc -} diff --git a/x-pack/plugins/ml/public/application/components/job_messages/job_messages.tsx b/x-pack/plugins/ml/public/application/components/job_messages/job_messages.tsx index d78f311475501..41b005c7da58b 100644 --- a/x-pack/plugins/ml/public/application/components/job_messages/job_messages.tsx +++ b/x-pack/plugins/ml/public/application/components/job_messages/job_messages.tsx @@ -137,7 +137,6 @@ export const JobMessages: FC = ({ <> = ({ height, label }) => { height = height ? +height : 100; return ( -
      - - {label && ( - <> - -
      {label}
      - - )} -
      + + +
      + + {label && ( + <> + +
      {label}
      + + )} +
      +
      +
      ); }; diff --git a/x-pack/plugins/ml/public/application/components/ml_embedded_map/_index.scss b/x-pack/plugins/ml/public/application/components/ml_embedded_map/_index.scss deleted file mode 100644 index 6d0d30dae670e..0000000000000 --- a/x-pack/plugins/ml/public/application/components/ml_embedded_map/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import 'ml_embedded_map'; diff --git a/x-pack/plugins/ml/public/application/components/ml_embedded_map/_ml_embedded_map.scss b/x-pack/plugins/ml/public/application/components/ml_embedded_map/_ml_embedded_map.scss deleted file mode 100644 index 495fc40ddb27c..0000000000000 --- a/x-pack/plugins/ml/public/application/components/ml_embedded_map/_ml_embedded_map.scss +++ /dev/null @@ -1,8 +0,0 @@ -.mlEmbeddedMapContent { - width: 100%; - height: 100%; - display: flex; - flex: 1 1 100%; - z-index: 1; - min-height: 0; // Absolute must for Firefox to scroll contents -} diff --git a/x-pack/plugins/ml/public/application/components/ml_embedded_map/ml_embedded_map.tsx b/x-pack/plugins/ml/public/application/components/ml_embedded_map/ml_embedded_map.tsx index ae9dbac3c2d02..57d2abe3577b9 100644 --- a/x-pack/plugins/ml/public/application/components/ml_embedded_map/ml_embedded_map.tsx +++ b/x-pack/plugins/ml/public/application/components/ml_embedded_map/ml_embedded_map.tsx @@ -143,7 +143,14 @@ export function MlEmbeddedMapComponent({ return (
      ); diff --git a/x-pack/plugins/ml/public/application/components/stats_bar/_index.scss b/x-pack/plugins/ml/public/application/components/stats_bar/_index.scss deleted file mode 100644 index e8d8e85763eff..0000000000000 --- a/x-pack/plugins/ml/public/application/components/stats_bar/_index.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import 'stat'; -@import 'stats_bar'; diff --git a/x-pack/plugins/ml/public/application/components/stats_bar/_stat.scss b/x-pack/plugins/ml/public/application/components/stats_bar/_stat.scss deleted file mode 100644 index ea570c24f0d7b..0000000000000 --- a/x-pack/plugins/ml/public/application/components/stats_bar/_stat.scss +++ /dev/null @@ -1,3 +0,0 @@ -.stat { - margin-right: $euiSizeS; -} diff --git a/x-pack/plugins/ml/public/application/components/stats_bar/_stats_bar.scss b/x-pack/plugins/ml/public/application/components/stats_bar/_stats_bar.scss deleted file mode 100644 index c433b53789573..0000000000000 --- a/x-pack/plugins/ml/public/application/components/stats_bar/_stats_bar.scss +++ /dev/null @@ -1,6 +0,0 @@ -.mlStatsBar { - // SASSTODO: proper calcs - height: 42px; - padding: 14px; - background-color: $euiColorLightestShade; -} diff --git a/x-pack/plugins/ml/public/application/components/stats_bar/stat.tsx b/x-pack/plugins/ml/public/application/components/stats_bar/stat.tsx index ae04a7a3b2448..2a0e8aad89bf0 100644 --- a/x-pack/plugins/ml/public/application/components/stats_bar/stat.tsx +++ b/x-pack/plugins/ml/public/application/components/stats_bar/stat.tsx @@ -6,6 +6,7 @@ */ import React, { FC } from 'react'; +import { useEuiTheme } from '@elastic/eui'; export interface StatsBarStat { label: string; @@ -18,8 +19,9 @@ interface StatProps { } export const Stat: FC = ({ stat }) => { + const { euiTheme } = useEuiTheme(); return ( - + {stat.label}:{' '} {stat.value} diff --git a/x-pack/plugins/ml/public/application/components/stats_bar/stats_bar.tsx b/x-pack/plugins/ml/public/application/components/stats_bar/stats_bar.tsx index 15324c180e293..535f5708693b0 100644 --- a/x-pack/plugins/ml/public/application/components/stats_bar/stats_bar.tsx +++ b/x-pack/plugins/ml/public/application/components/stats_bar/stats_bar.tsx @@ -6,6 +6,7 @@ */ import React, { FC } from 'react'; +import { useEuiTheme } from '@elastic/eui'; import { Stat, StatsBarStat } from './stat'; interface Stats { @@ -37,9 +38,13 @@ interface StatsBarProps { } export const StatsBar: FC = ({ stats, dataTestSub }) => { + const { euiTheme } = useEuiTheme(); const statsList = Object.keys(stats).map((k) => stats[k as StatsKey]); return ( -
      +
      {statsList .filter((s: StatsBarStat) => s.show) .map((s: StatsBarStat) => ( diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.test.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.test.js index 339925d3f16ee..d77b66b960625 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.test.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.test.js @@ -60,7 +60,7 @@ describe('ExplorerChart', () => { // the directive just ends up being empty. expect(wrapper.isEmptyRender()).toBeTruthy(); expect(wrapper.find('.content-wrapper')).toHaveLength(0); - expect(wrapper.find('.ml-loading-indicator .euiLoadingChart')).toHaveLength(0); + expect(wrapper.find('.euiLoadingChart')).toHaveLength(0); }); test('Loading status active, no chart', () => { @@ -84,7 +84,7 @@ describe('ExplorerChart', () => { // test if the loading indicator is shown // Added span because class appears twice with classNames and Emotion - expect(wrapper.find('.ml-loading-indicator span.euiLoadingChart')).toHaveLength(1); + expect(wrapper.find('span.euiLoadingChart')).toHaveLength(1); }); // For the following tests the directive needs to be rendered in the actual DOM, @@ -121,7 +121,7 @@ describe('ExplorerChart', () => { const wrapper = init(mockChartData); // the loading indicator should not be shown - expect(wrapper.find('.ml-loading-indicator .euiLoadingChart')).toHaveLength(0); + expect(wrapper.find('.euiLoadingChart')).toHaveLength(0); // test if all expected elements are present // need to use getDOMNode() because the chart is not rendered via react itself diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.test.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.test.js index 3748a196e742d..abe8c0c991cc4 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.test.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.test.js @@ -62,7 +62,7 @@ describe('ExplorerChart', () => { // the directive just ends up being empty. expect(wrapper.isEmptyRender()).toBeTruthy(); expect(wrapper.find('.content-wrapper')).toHaveLength(0); - expect(wrapper.find('.ml-loading-indicator .euiLoadingChart')).toHaveLength(0); + expect(wrapper.find('.euiLoadingChart')).toHaveLength(0); }); test('Loading status active, no chart', () => { @@ -87,7 +87,7 @@ describe('ExplorerChart', () => { // test if the loading indicator is shown // Added span because class appears twice with classNames and Emotion - expect(wrapper.find('.ml-loading-indicator span.euiLoadingChart')).toHaveLength(1); + expect(wrapper.find('span.euiLoadingChart')).toHaveLength(1); }); // For the following tests the directive needs to be rendered in the actual DOM, @@ -126,7 +126,7 @@ describe('ExplorerChart', () => { const wrapper = init(mockChartData); // the loading indicator should not be shown - expect(wrapper.find('.ml-loading-indicator .euiLoadingChart')).toHaveLength(0); + expect(wrapper.find('.euiLoadingChart')).toHaveLength(0); // test if all expected elements are present // need to use getDOMNode() because the chart is not rendered via react itself diff --git a/x-pack/plugins/ml/public/application/jobs/_index.scss b/x-pack/plugins/ml/public/application/jobs/_index.scss deleted file mode 100644 index 318ba1d54c082..0000000000000 --- a/x-pack/plugins/ml/public/application/jobs/_index.scss +++ /dev/null @@ -1,3 +0,0 @@ -@import 'components/custom_url_editor/index'; -@import 'jobs_list/index'; // SASSTODO: Various EUI overwrites throughout this folder -@import 'new_job/pages/components/job_details_step/components/additional_section/components/custom_urls/index'; diff --git a/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/__snapshots__/editor.test.tsx.snap b/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/__snapshots__/editor.test.tsx.snap index 6c5d3d473d413..163bad28d8cc2 100644 --- a/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/__snapshots__/editor.test.tsx.snap +++ b/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/__snapshots__/editor.test.tsx.snap @@ -21,7 +21,6 @@ exports[`CustomUrlEditor renders the editor for a dashboard type URL with a labe data-test-subj="mlJobCustomUrlForm" > = ({ label={ } - className="url-label" error={invalidLabelError} isInvalid={isInvalidLabel} display="rowCompressed" diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/_index.scss b/x-pack/plugins/ml/public/application/jobs/jobs_list/_index.scss deleted file mode 100644 index 4af4814be8362..0000000000000 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/_index.scss +++ /dev/null @@ -1,8 +0,0 @@ -@import 'jobs_list'; - -@import 'components/edit_job_flyout/index'; -@import 'components/job_details/index'; // SASSTODO: Dangerous EUI overwrites -@import 'components/job_filter_bar/index'; // SASSTODO: Dangerous EUI overwrites -@import 'components/job_group/index'; -@import 'components/jobs_list_view/index'; -@import 'components/multi_job_actions/index'; // SASSTODO: Dangerous EUI overwrites diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/_jobs_list.scss b/x-pack/plugins/ml/public/application/jobs/jobs_list/_jobs_list.scss deleted file mode 100644 index 824f764de3902..0000000000000 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/_jobs_list.scss +++ /dev/null @@ -1,3 +0,0 @@ -.new-job-button-container { - float: right; -} diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/_edit_job_flyout.scss b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/_edit_job_flyout.scss deleted file mode 100644 index 7018a991ce1dc..0000000000000 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/_edit_job_flyout.scss +++ /dev/null @@ -1,8 +0,0 @@ -.edit-custom-url-panel { - .close-editor-button { - position: relative; - float: right; - top: -$euiSizeXS; - right: 0; - } -} diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/_index.scss b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/_index.scss deleted file mode 100644 index 2fdf5a9671be3..0000000000000 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import 'edit_job_flyout'; \ No newline at end of file diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.tsx b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.tsx index 9a7508c634c6b..ac6ad4ae70b8b 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.tsx +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.tsx @@ -268,7 +268,6 @@ class CustomUrlsUI extends Component { defaultMessage: 'Close custom URL editor', } )} - className="close-editor-button" /> {editor} diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/_index.scss b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/_index.scss deleted file mode 100644 index 3930bb1cf73a5..0000000000000 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import 'job_details'; diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/_job_details.scss b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/_job_details.scss deleted file mode 100644 index fe61be550ecac..0000000000000 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/_job_details.scss +++ /dev/null @@ -1,87 +0,0 @@ -.tab-contents { - margin: -$euiSizeS; - padding: $euiSizeS; - background-color: $euiColorEmptyShade; - - // SASSTODO: Need to remove bootstrap grid - .col-md-6:nth-child(1) { - // SASSTODO: Why is this 7? - padding-right: 7px; - } - - // SASSTODO: Need to remove bootstrap grid - .col-md-6:nth-child(2) { - // SASSTODO: Why is this 7? - padding-left: 7px; - } - - // SASSTODO: This needs to be rewriten. Tons of EUI overwrites - .job-section { - overflow: auto; - padding: 5px 15px; - background-color: $euiColorLightestShade; - border: 1px solid $euiColorLightShade; - border-radius: $euiBorderRadius; - margin: $euiSizeXS 0; - - .euiTable { - background-color: transparent; - - .euiTableRow:first-child { - .euiTableRowCell { - border-top: 0; - } - } - - .euiTableRow:last-child { - .euiTableRowCell { - border-bottom: 0; - } - } - - .euiTableRow { - .euiTableRowCell { - vertical-align: top; - border-bottom: 1px solid $euiColorLightShade; - - .euiTableCellContent__text { - word-wrap: break-word; - } - } - } - - .euiTableRow:hover { - background-color: inherit; - } - } - - .job-item { - font-size: 12px; - } - - .job-item.header { - font-weight: bold; - } - } - - // SASSTODO: This needs a proper calc - .json-textarea { - height: 500px; - } -} - -.job-messages-table { - max-height: 500px; - overflow: auto; - text-align: left; - - tr:last-child { - td { - border-bottom: none; - } - } - - .euiTableRowCell { - background-color: $euiColorEmptyShade; - } -} diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details_pane.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details_pane.js deleted file mode 100644 index c70c049dd7489..0000000000000 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details_pane.js +++ /dev/null @@ -1,110 +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 PropTypes from 'prop-types'; -import React, { Component } from 'react'; - -import { - EuiFlexGroup, - EuiFlexItem, - EuiTitle, - EuiTable, - EuiTableBody, - EuiTableRow, - EuiTableRowCell, - EuiSpacer, -} from '@elastic/eui'; - -function SectionItem({ item }) { - return ( - - {item[0] !== '' && ( - - {item[0]} - - )} - - {item[1]} - - - ); -} -SectionItem.propTypes = { - item: PropTypes.array.isRequired, -}; - -function Section({ section }) { - if (section.items.length === 0) { - return
      ; - } - - return ( - - - - -

      {section.title}

      -
      -
      - {section.titleAction} -
      -
      - - - {section.items.map((item, i) => ( - - ))} - - -
      -
      - ); -} -Section.propTypes = { - section: PropTypes.object.isRequired, -}; - -export class JobDetailsPane extends Component { - constructor(props) { - super(props); - this.state = {}; - } - - static getDerivedStateFromProps(props) { - const { sections, time } = props; - return { sections, time }; - } - - render() { - const { sections, time } = this.state; - return ( - - -
      -
      - {sections - .filter((s) => s.position === 'left') - .map((s, i) => ( -
      - ))} -
      -
      - {sections - .filter((s) => s.position === 'right') - .map((s, i) => ( -
      - ))} -
      -
      -
      - ); - } -} -JobDetailsPane.propTypes = { - sections: PropTypes.array.isRequired, - 'data-test-subj': PropTypes.string, -}; diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details_pane.tsx b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details_pane.tsx new file mode 100644 index 0000000000000..9d090425ff4ef --- /dev/null +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details_pane.tsx @@ -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 React, { FC } from 'react'; +import { css } from '@emotion/react'; + +import { + EuiFlexGroup, + EuiFlexItem, + EuiTitle, + EuiTable, + EuiTableBody, + EuiTableRow, + EuiTableRowCell, + EuiSpacer, + useEuiTheme, +} from '@elastic/eui'; + +type Item = [string, string]; + +interface Section { + id: string; + title: string; + titleAction: string; + position: 'left' | 'right'; + items: Item[]; +} + +// fix for the annotation label being hidden inside the bounds of the chart container + +const SectionItem: FC<{ item: Item }> = ({ item }) => { + const { euiTheme } = useEuiTheme(); + const fontSize = euiTheme.size.m; + + return ( + + {item[0] !== '' && ( + + {item[0]} + + )} + + {item[1]} + + + ); +}; + +const Section: FC<{ section: Section }> = ({ section }) => { + const { euiTheme } = useEuiTheme(); + if (section.items.length === 0) { + return
      ; + } + + const cssOverride = css({ + overflow: 'auto', + padding: `${euiTheme.size.xs} ${euiTheme.size.m}`, + backgroundColor: euiTheme.colors.lightestShade, + border: `1px solid ${euiTheme.colors.lightShade}`, + borderRadius: euiTheme.border.radius.medium, + margin: `${euiTheme.size.s} 0`, + + '.euiTable': { + backgroundColor: 'transparent', + }, + + '.euiTableRow:hover': { + backgroundColor: 'inherit', + }, + '.euiTableRow:first-child': { + '.euiTableRowCell': { + borderTop: 0, + }, + }, + '.euiTableRow:last-child': { + '.euiTableRowCell': { + borderBottom: 0, + }, + }, + }); + + return ( + <> + + + + + +

      {section.title}

      +
      +
      + {section.titleAction} +
      +
      + +
      + + + {section.items.map((item, i) => ( + + ))} + + +
      +
      +
      + + ); +}; + +export const JobDetailsPane: FC<{ sections: any[]; 'data-test-subj': string }> = ({ + sections, + ...props +}) => { + return ( + <> + +
      + + + {sections + .filter((s) => s.position === 'left') + .map((s, i) => ( +
      + ))} + + + {sections + .filter((s) => s.position === 'right') + .map((s, i) => ( +
      + ))} + + +
      + + ); +}; diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_filter_bar/_index.scss b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_filter_bar/_index.scss deleted file mode 100644 index bd0e4ba6a9d45..0000000000000 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_filter_bar/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import 'job_filter_bar'; \ No newline at end of file diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_filter_bar/_job_filter_bar.scss b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_filter_bar/_job_filter_bar.scss deleted file mode 100644 index ecea309314dce..0000000000000 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_filter_bar/_job_filter_bar.scss +++ /dev/null @@ -1,23 +0,0 @@ -.mlJobFilterBar { - // SASSTODO: Dangerou EUI overwrites - .euiFilterGroup { - .euiPopover .euiPanel { - .group-item { - padding: $euiSizeS $euiSize; - } - - .inline-group { - border: 1px solid $euiColorEmptyShade; - border-radius: $euiBorderRadius; - } - - .euiFilterSelectItem:hover, .euiFilterSelectItem:focus { - text-decoration: none; - .inline-group { - border: 1px solid $euiColorDarkShade; - box-shadow: 0 1px 2px $euiColorMediumShade; - } - } - } - } -} diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_filter_bar/job_filter_bar.tsx b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_filter_bar/job_filter_bar.tsx index dbc64d082f117..0384b8895dd6c 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_filter_bar/job_filter_bar.tsx +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_filter_bar/job_filter_bar.tsx @@ -155,7 +155,6 @@ export const JobFilterBar: FC = ({ queryText, setFilters }) = query={queryInstance} filters={filters} onChange={onChange} - className="mlJobFilterBar" /> diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_group/_index.scss b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_group/_index.scss deleted file mode 100644 index 361f78b08fcc9..0000000000000 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_group/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import 'job_group'; \ No newline at end of file diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_group/_job_group.scss b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_group/_job_group.scss deleted file mode 100644 index fc5bce54afb6a..0000000000000 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_group/_job_group.scss +++ /dev/null @@ -1,10 +0,0 @@ -.inline-group { - font-size: 12px; - background-color: $euiColorLightShade; - padding: 2px 5px; - border-radius: 2px; - display: inline-block; - margin: 0 3px; - color: $euiColorEmptyShade; - vertical-align: text-top; -} diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/_index.scss b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/_index.scss deleted file mode 100644 index c1d8b70a054a2..0000000000000 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import 'jobs_list_view'; \ No newline at end of file diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/_jobs_list_view.scss b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/_jobs_list_view.scss deleted file mode 100644 index ef0fbc358193e..0000000000000 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/_jobs_list_view.scss +++ /dev/null @@ -1,10 +0,0 @@ -// SASSTODO: Proper calcs -.actions-bar { - min-height: 60px; - display: flex; - align-items: center; -} - -.job-management { - padding: 20px; -} diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js index 179d5d48b3fe5..75ada48dbdc4c 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js @@ -418,23 +418,33 @@ export class JobsListView extends Component {
      -
      - this.refreshJobSummaryList()} - /> - -
      + + + this.refreshJobSummaryList()} + /> + + + + + + {groups.map((g, index) => (
      this.closePopover()} > -
      +
      0; + return (
      - {jobsSelected && ( + {jobsSelected ? ( -
      +
      @@ -74,7 +83,7 @@ export class MultiJobActions extends Component { /> - )} + ) : null}
      ); } diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/job_from_lens/utils.ts b/x-pack/plugins/ml/public/application/jobs/new_job/job_from_lens/utils.ts index ed0c4639c47c2..aab22b84f8fc6 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/job_from_lens/utils.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/job_from_lens/utils.ts @@ -186,7 +186,7 @@ export async function getVisTypeFactory(lens: LensPublicStart) { export async function isCompatibleVisualizationType(chartInfo: ChartInfo) { return ( chartInfo.visualizationType === COMPATIBLE_VISUALIZATION && - chartInfo.layers.some((l) => l.layerType === layerTypes.DATA) + chartInfo.layers.some((l) => l.layerType === layerTypes.DATA && l.dataView !== undefined) ); } diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/job_from_lens/visualization_extractor.ts b/x-pack/plugins/ml/public/application/jobs/new_job/job_from_lens/visualization_extractor.ts index 1128bd5918814..c7f5a02e75d5b 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/job_from_lens/visualization_extractor.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/job_from_lens/visualization_extractor.ts @@ -55,7 +55,9 @@ export class VisualizationExtractor { ); } - const timeField = layer.dimensions.find(({ operation }) => operation.dataType === 'date'); + const timeField = layer.dimensions.find( + (dimension) => dimension.operation?.dataType === 'date' + ); if (timeField === undefined || !timeField.operation.fields?.length) { throw Error( i18n.translate('xpack.ml.newJob.fromLens.createJob.error.noDateField', { diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/custom_urls/description.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/custom_urls/description.tsx index cd2156b059f47..f0043ca3ea47b 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/custom_urls/description.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/custom_urls/description.tsx @@ -9,6 +9,7 @@ import React, { memo, FC } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiDescribedFormGroup, EuiFormRow, EuiLink } from '@elastic/eui'; +import { css } from '@emotion/react'; import { useMlKibana } from '../../../../../../../../../contexts/kibana'; export const Description: FC = memo(({ children }) => { @@ -22,10 +23,21 @@ export const Description: FC = memo(({ children }) => { defaultMessage: 'Custom URLs', } ); + + const cssOverride = css({ + '> .euiFlexGroup': { + '> .euiFlexItem': { + '&:last-child': { + flexBasis: '50%', + }, + }, + }, + }); + return ( {title}} description={ .euiFlexGroup { - > .euiFlexItem { - &:first-child { - max-width: 388px; - } - &:last-child { - flex-basis: 50%; - } - } - } -} diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/advanced_section/components/annotations/description.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/advanced_section/components/annotations/description.tsx index 0f8a7c70fe207..9cdd208fbfccd 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/advanced_section/components/annotations/description.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/advanced_section/components/annotations/description.tsx @@ -8,7 +8,7 @@ import React, { memo, FC } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { EuiDescribedFormGroup } from '@elastic/eui'; +import { EuiDescribedFormGroup, EuiFormRow } from '@elastic/eui'; export const Description: FC = memo(({ children }) => { const title = i18n.translate( @@ -27,7 +27,9 @@ export const Description: FC = memo(({ children }) => { /> } > - <>{children} + + <>{children} + ); }); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/advanced_section/components/dedicated_index/description.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/advanced_section/components/dedicated_index/description.tsx index 6b207928b4c99..daa9ac0ddbc53 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/advanced_section/components/dedicated_index/description.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/advanced_section/components/dedicated_index/description.tsx @@ -8,7 +8,7 @@ import React, { memo, FC } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { EuiDescribedFormGroup } from '@elastic/eui'; +import { EuiDescribedFormGroup, EuiFormRow } from '@elastic/eui'; export const Description: FC = memo(({ children }) => { const title = i18n.translate( @@ -27,7 +27,9 @@ export const Description: FC = memo(({ children }) => { /> } > - <>{children} + + <>{children} + ); }); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/advanced_section/components/model_plot/description.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/advanced_section/components/model_plot/description.tsx index 31b9821292758..7cfabf25bad88 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/advanced_section/components/model_plot/description.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/advanced_section/components/model_plot/description.tsx @@ -8,7 +8,7 @@ import React, { memo, FC } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { EuiDescribedFormGroup } from '@elastic/eui'; +import { EuiDescribedFormGroup, EuiFormRow } from '@elastic/eui'; export const Description: FC = memo(({ children }) => { const title = i18n.translate( @@ -27,7 +27,9 @@ export const Description: FC = memo(({ children }) => { /> } > - <>{children} + + <>{children} + ); }); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_partition_field/description.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_partition_field/description.tsx index 3c329d1c2f092..18c4e419243a9 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_partition_field/description.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_partition_field/description.tsx @@ -8,7 +8,7 @@ import React, { memo, FC } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { EuiDescribedFormGroup } from '@elastic/eui'; +import { EuiDescribedFormGroup, EuiFormRow } from '@elastic/eui'; interface Props { children: React.ReactNode; @@ -27,7 +27,9 @@ export const Description: FC = memo(({ children }) => { /> } > - <>{children} + + <>{children} + ); }); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/geo_field/description.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/geo_field/description.tsx index 25b73ed2f5e1c..287a909911605 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/geo_field/description.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/geo_field/description.tsx @@ -8,7 +8,7 @@ import React, { memo, FC } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { EuiDescribedFormGroup } from '@elastic/eui'; +import { EuiDescribedFormGroup, EuiFormRow } from '@elastic/eui'; export const Description: FC = memo(({ children }) => { const title = i18n.translate('xpack.ml.newJob.wizard.pickFieldsStep.geoField.title', { @@ -24,7 +24,9 @@ export const Description: FC = memo(({ children }) => { /> } > - <>{children} + + <>{children} + ); }); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_field/description.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_field/description.tsx index d27953d2e8e10..48e62fc62051b 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_field/description.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_field/description.tsx @@ -8,7 +8,7 @@ import React, { memo, FC } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { EuiDescribedFormGroup } from '@elastic/eui'; +import { EuiDescribedFormGroup, EuiFormRow } from '@elastic/eui'; export const Description: FC = memo(({ children }) => { const title = i18n.translate('xpack.ml.newJob.wizard.pickFieldsStep.populationField.title', { @@ -24,7 +24,9 @@ export const Description: FC = memo(({ children }) => { /> } > - <>{children} + + <>{children} + ); }); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_field/description.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_field/description.tsx index 78f0f62294da5..15b618386538a 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_field/description.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_field/description.tsx @@ -8,7 +8,7 @@ import React, { memo, FC } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { EuiDescribedFormGroup } from '@elastic/eui'; +import { EuiDescribedFormGroup, EuiFormRow } from '@elastic/eui'; export const Description: FC = memo(({ children }) => { const title = i18n.translate('xpack.ml.newJob.wizard.pickFieldsStep.splitRareField.title', { @@ -24,7 +24,9 @@ export const Description: FC = memo(({ children }) => { /> } > - <>{children} + + <>{children} + ); }); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/sparse_data/description.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/sparse_data/description.tsx index cf75b9c60852e..8471aba98c636 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/sparse_data/description.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/sparse_data/description.tsx @@ -8,7 +8,7 @@ import React, { memo, FC } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { EuiDescribedFormGroup } from '@elastic/eui'; +import { EuiDescribedFormGroup, EuiFormRow } from '@elastic/eui'; export const Description: FC = memo(({ children }) => { const title = i18n.translate('xpack.ml.newJob.wizard.pickFieldsStep.sparseData.title', { @@ -24,7 +24,9 @@ export const Description: FC = memo(({ children }) => { /> } > - {children} + + <>{children} + ); }); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/time_range_step/time_range.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/time_range_step/time_range.tsx index 4dc7b498144cf..309b751a0da5d 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/time_range_step/time_range.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/time_range_step/time_range.tsx @@ -135,6 +135,7 @@ export const TimeRangeStep: FC = ({ setCurrentStep, isCurrentStep }) disabled={false} callback={fullTimeRangeCallback} timefilter={timefilter} + apiPath="/api/ml/fields_service/time_field_range" /> diff --git a/x-pack/plugins/ml/public/application/overview/_index.scss b/x-pack/plugins/ml/public/application/overview/_index.scss deleted file mode 100644 index 841415620d691..0000000000000 --- a/x-pack/plugins/ml/public/application/overview/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import 'components/index'; diff --git a/x-pack/plugins/ml/public/application/overview/components/_index.scss b/x-pack/plugins/ml/public/application/overview/components/_index.scss deleted file mode 100644 index 10abd3d6a25d5..0000000000000 --- a/x-pack/plugins/ml/public/application/overview/components/_index.scss +++ /dev/null @@ -1,8 +0,0 @@ -.mlOverviewPanel__isLoading { - text-align: center; - padding: 10%; -} - -.mlOverviewPanel__spinner { - display: inline-block; -} diff --git a/x-pack/plugins/ml/public/application/overview/components/analytics_panel/analytics_panel.tsx b/x-pack/plugins/ml/public/application/overview/components/analytics_panel/analytics_panel.tsx index e5c1bb867bbd9..094693be8787e 100644 --- a/x-pack/plugins/ml/public/application/overview/components/analytics_panel/analytics_panel.tsx +++ b/x-pack/plugins/ml/public/application/overview/components/analytics_panel/analytics_panel.tsx @@ -75,8 +75,6 @@ export const AnalyticsPanel: FC = ({ setLazyJobCount }) => { ); - const panelClass = isInitialized === false ? 'mlOverviewPanel__isLoading' : 'mlOverviewPanel'; - const noDFAJobs = errorMessage === undefined && isInitialized === true && analytics.length === 0; return ( @@ -84,10 +82,14 @@ export const AnalyticsPanel: FC = ({ setLazyJobCount }) => { {noDFAJobs ? ( ) : ( - + {typeof errorMessage !== 'undefined' ? errorDisplay : null} {isInitialized === false && ( - + )} {isInitialized === true && analytics.length > 0 && ( diff --git a/x-pack/plugins/ml/public/application/routing/routes/new_job/wizard.tsx b/x-pack/plugins/ml/public/application/routing/routes/new_job/wizard.tsx index 4300d73ae010e..a60fbae514e7f 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/new_job/wizard.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/new_job/wizard.tsx @@ -127,14 +127,13 @@ export const multiMetricRouteFactory = ( // redirect route to reset the job wizard when converting to multi metric job export const multiMetricRouteFactoryRedirect = (): MlRoute => ({ path: createPath(ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_CONVERT_TO_MULTI_METRIC), - render: (props) => ( - - ), + render: (props) => { + return ( + + ); + }, breadcrumbs: [], }); diff --git a/x-pack/plugins/ml/public/embeddables/job_creation/common/create_flyout.tsx b/x-pack/plugins/ml/public/embeddables/job_creation/common/create_flyout.tsx index c9163b71b057c..1bbf8c18cf25c 100644 --- a/x-pack/plugins/ml/public/embeddables/job_creation/common/create_flyout.tsx +++ b/x-pack/plugins/ml/public/embeddables/job_creation/common/create_flyout.tsx @@ -70,7 +70,6 @@ export function createFlyout( { 'data-test-subj': 'mlFlyoutLayerSelector', ownFocus: true, - closeButtonAriaLabel: 'jobSelectorFlyout', onClose: onFlyoutClose, // @ts-expect-error should take any number/string compatible with the CSS width attribute size: '35vw', diff --git a/x-pack/plugins/observability/e2e/journeys/step_duration.journey.ts b/x-pack/plugins/observability/e2e/journeys/step_duration.journey.ts index 1e24fbb120f53..13cde8e940e13 100644 --- a/x-pack/plugins/observability/e2e/journeys/step_duration.journey.ts +++ b/x-pack/plugins/observability/e2e/journeys/step_duration.journey.ts @@ -56,10 +56,7 @@ journey('Exploratory view', async ({ page, params }) => { }); step('Open exploratory view with monitor duration', async () => { - await Promise.all([ - page.waitForNavigation(TIMEOUT_60_SEC), - page.click('text=Explore data', TIMEOUT_60_SEC), - ]); + await page.waitForNavigation(TIMEOUT_60_SEC); await waitForLoadingToFinish({ page }); await page.click('text=browser', TIMEOUT_60_SEC); diff --git a/x-pack/plugins/observability/kibana.jsonc b/x-pack/plugins/observability/kibana.jsonc index ad5fac050d671..4026d9f41088d 100644 --- a/x-pack/plugins/observability/kibana.jsonc +++ b/x-pack/plugins/observability/kibana.jsonc @@ -17,6 +17,7 @@ "data", "dataViews", "features", + "files", "inspector", "ruleRegistry", "triggersActionsUi", @@ -25,7 +26,6 @@ "security", "guidedOnboarding", "share", - "spaces" ], "optionalPlugins": [ "discover", diff --git a/x-pack/plugins/observability/public/components/app/burn_rate_rule_editor/burn_rate_rule_editor.stories.tsx b/x-pack/plugins/observability/public/components/app/burn_rate_rule_editor/burn_rate_rule_editor.stories.tsx index 95ec0d73aa6ae..0c53fae9af248 100644 --- a/x-pack/plugins/observability/public/components/app/burn_rate_rule_editor/burn_rate_rule_editor.stories.tsx +++ b/x-pack/plugins/observability/public/components/app/burn_rate_rule_editor/burn_rate_rule_editor.stories.tsx @@ -8,13 +8,14 @@ import React from 'react'; import { ComponentStory } from '@storybook/react'; +import { KibanaReactStorybookDecorator } from '../../../utils/kibana_react.storybook_decorator'; import { BurnRateRuleParams } from '../../../typings'; import { BurnRateRuleEditor as Component } from './burn_rate_rule_editor'; export default { component: Component, title: 'app/SLO/BurnRateRule', - argTypes: {}, + decorators: [KibanaReactStorybookDecorator], }; const Template: ComponentStory = () => ( diff --git a/x-pack/plugins/observability/public/components/app/burn_rate_rule_editor/slo_selector.stories.tsx b/x-pack/plugins/observability/public/components/app/burn_rate_rule_editor/slo_selector.stories.tsx index fd06b1fef6ab5..1debffc408049 100644 --- a/x-pack/plugins/observability/public/components/app/burn_rate_rule_editor/slo_selector.stories.tsx +++ b/x-pack/plugins/observability/public/components/app/burn_rate_rule_editor/slo_selector.stories.tsx @@ -9,11 +9,13 @@ import React from 'react'; import { ComponentStory } from '@storybook/react'; import { SLOResponse } from '@kbn/slo-schema'; +import { KibanaReactStorybookDecorator } from '../../../utils/kibana_react.storybook_decorator'; import { SloSelector as Component } from './slo_selector'; export default { component: Component, title: 'app/SLO/BurnRateRule', + decorators: [KibanaReactStorybookDecorator], }; const Template: ComponentStory = () => ( 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 4d154504ce6ea..aec17127b8dd9 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 @@ -62,7 +62,7 @@ export function ObservabilityAlertSearchBar({ const onSearchBarParamsChange = useCallback< (query: { dateRange: { from: string; to: string; mode?: 'absolute' | 'relative' }; - query: string; + query?: string; }) => void >( ({ dateRange, query }) => { @@ -76,7 +76,7 @@ export function ObservabilityAlertSearchBar({ query, [...getAlertStatusQuery(status), ...defaultSearchQueries] ); - onKueryChange(query); + if (query) onKueryChange(query); timeFilterService.setTime(dateRange); onRangeFromChange(dateRange.from); onRangeToChange(dateRange.to); diff --git a/x-pack/plugins/rule_registry/common/field_map/types.ts b/x-pack/plugins/observability/public/hooks/slo/__storybook_mocks__/use_capabilities.ts similarity index 57% rename from x-pack/plugins/rule_registry/common/field_map/types.ts rename to x-pack/plugins/observability/public/hooks/slo/__storybook_mocks__/use_capabilities.ts index 52ee246375ad0..f34d7a4a7070b 100644 --- a/x-pack/plugins/rule_registry/common/field_map/types.ts +++ b/x-pack/plugins/observability/public/hooks/slo/__storybook_mocks__/use_capabilities.ts @@ -5,13 +5,9 @@ * 2.0. */ -export interface FieldMap { - [key: string]: { - type: string; - required?: boolean; - array?: boolean; - path?: string; - scaling_factor?: number; - dynamic?: 'strict' | boolean; +export function useCapabilities() { + return { + hasReadCapabilities: true, + hasWriteCapabilities: true, }; } diff --git a/x-pack/plugins/observability/public/hooks/slo/__storybook_mocks__/use_fetch_active_alerts.ts b/x-pack/plugins/observability/public/hooks/slo/__storybook_mocks__/use_fetch_active_alerts.ts new file mode 100644 index 0000000000000..f493eadac3806 --- /dev/null +++ b/x-pack/plugins/observability/public/hooks/slo/__storybook_mocks__/use_fetch_active_alerts.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { UseFetchActiveAlerts } from '../use_fetch_active_alerts'; + +export const useFetchActiveAlerts = ({ + sloIds = [], +}: { + sloIds: string[]; +}): UseFetchActiveAlerts => { + return { + isLoading: false, + isSuccess: false, + isError: false, + data: sloIds.reduce( + (acc, sloId, index) => ({ + ...acc, + ...(index % 2 === 0 && { [sloId]: { count: 2, ruleIds: ['rule-1', 'rule-2'] } }), + }), + {} + ), + }; +}; diff --git a/x-pack/plugins/observability/public/hooks/slo/__storybook_mocks__/use_fetch_historical_summary.ts b/x-pack/plugins/observability/public/hooks/slo/__storybook_mocks__/use_fetch_historical_summary.ts index f869e6da47c64..7928dbe51b46d 100644 --- a/x-pack/plugins/observability/public/hooks/slo/__storybook_mocks__/use_fetch_historical_summary.ts +++ b/x-pack/plugins/observability/public/hooks/slo/__storybook_mocks__/use_fetch_historical_summary.ts @@ -20,9 +20,10 @@ export const useFetchHistoricalSummary = ({ return { isLoading: false, + isInitialLoading: false, + isRefetching: false, isSuccess: false, isError: false, sloHistoricalSummaryResponse: data, - refetch: function () {} as UseFetchHistoricalSummaryResponse['refetch'], }; }; diff --git a/x-pack/plugins/observability/public/hooks/slo/use_fetch_active_alerts.ts b/x-pack/plugins/observability/public/hooks/slo/use_fetch_active_alerts.ts new file mode 100644 index 0000000000000..4e90e51060df0 --- /dev/null +++ b/x-pack/plugins/observability/public/hooks/slo/use_fetch_active_alerts.ts @@ -0,0 +1,115 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useQuery } from '@tanstack/react-query'; +import { BASE_RAC_ALERTS_API_PATH } from '@kbn/rule-registry-plugin/common'; + +import { useKibana } from '../../utils/kibana_react'; + +type SloId = string; + +interface Params { + sloIds: SloId[]; +} + +export interface ActiveAlerts { + count: number; + ruleIds: string[]; +} + +type ActiveAlertsMap = Record; + +export interface UseFetchActiveAlerts { + data: ActiveAlertsMap; + isLoading: boolean; + isSuccess: boolean; + isError: boolean; +} + +interface FindApiResponse { + aggregations: { + perSloId: { + buckets: Array<{ + key: string; + doc_count: number; + perRuleId: { buckets: Array<{ key: string; doc_count: number }> }; + }>; + }; + }; +} + +const EMPTY_ACTIVE_ALERTS_MAP = {}; + +export function useFetchActiveAlerts({ sloIds = [] }: Params): UseFetchActiveAlerts { + const { http } = useKibana().services; + + const { isInitialLoading, isLoading, isError, isSuccess, isRefetching, data } = useQuery({ + queryKey: ['fetchActiveAlerts', sloIds], + queryFn: async ({ signal }) => { + try { + const response = await http.post(`${BASE_RAC_ALERTS_API_PATH}/find`, { + body: JSON.stringify({ + feature_ids: ['slo'], + size: 0, + query: { + bool: { + filter: [ + { + term: { + 'kibana.alert.rule.rule_type_id': 'slo.rules.burnRate', + }, + }, + { + term: { + 'kibana.alert.status': 'active', + }, + }, + ], + }, + }, + aggs: { + perSloId: { + terms: { + field: 'kibana.alert.rule.parameters.sloId', + }, + aggs: { + perRuleId: { + terms: { + field: 'kibana.alert.rule.uuid', + }, + }, + }, + }, + }, + }), + signal, + }); + + return response.aggregations.perSloId.buckets.reduce( + (acc, bucket) => ({ + ...acc, + [bucket.key]: { + count: bucket.doc_count ?? 0, + ruleIds: bucket.perRuleId.buckets.map((rule) => rule.key), + } as ActiveAlerts, + }), + {} + ); + } catch (error) { + // ignore error + } + }, + refetchOnWindowFocus: false, + }); + + return { + data: isInitialLoading ? EMPTY_ACTIVE_ALERTS_MAP : data ?? EMPTY_ACTIVE_ALERTS_MAP, + isLoading: isInitialLoading || isLoading || isRefetching, + isSuccess, + isError, + }; +} diff --git a/x-pack/plugins/observability/public/hooks/slo/use_fetch_apm_suggestions.ts b/x-pack/plugins/observability/public/hooks/slo/use_fetch_apm_suggestions.ts index cc7bdb1193d63..63ed913902ee8 100644 --- a/x-pack/plugins/observability/public/hooks/slo/use_fetch_apm_suggestions.ts +++ b/x-pack/plugins/observability/public/hooks/slo/use_fetch_apm_suggestions.ts @@ -11,6 +11,7 @@ import moment from 'moment'; import { useKibana } from '../../utils/kibana_react'; export type Suggestion = string; + export interface UseFetchApmSuggestions { suggestions: Suggestion[]; isLoading: boolean; @@ -28,7 +29,7 @@ interface ApiResponse { terms: string[]; } -const EMPTY_RESPONSE: ApiResponse = { terms: [] }; +const NO_SUGGESTIONS: Suggestion[] = []; export function useFetchApmSuggestions({ fieldName, @@ -61,7 +62,7 @@ export function useFetchApmSuggestions({ }); return { - suggestions: isInitialLoading ? EMPTY_RESPONSE.terms : data ?? EMPTY_RESPONSE.terms, + suggestions: isInitialLoading ? NO_SUGGESTIONS : data ?? NO_SUGGESTIONS, isLoading: isInitialLoading || isLoading || isRefetching, isSuccess, isError, diff --git a/x-pack/plugins/observability/public/hooks/slo/use_fetch_historical_summary.ts b/x-pack/plugins/observability/public/hooks/slo/use_fetch_historical_summary.ts index e1bca23188295..469d98e63538c 100644 --- a/x-pack/plugins/observability/public/hooks/slo/use_fetch_historical_summary.ts +++ b/x-pack/plugins/observability/public/hooks/slo/use_fetch_historical_summary.ts @@ -5,26 +5,18 @@ * 2.0. */ -import { - QueryObserverResult, - RefetchOptions, - RefetchQueryFilters, - useQuery, -} from '@tanstack/react-query'; +import { useQuery } from '@tanstack/react-query'; import { FetchHistoricalSummaryResponse } from '@kbn/slo-schema'; import { useKibana } from '../../utils/kibana_react'; -const EMPTY_RESPONSE: FetchHistoricalSummaryResponse = {}; - export interface UseFetchHistoricalSummaryResponse { - sloHistoricalSummaryResponse: FetchHistoricalSummaryResponse; + sloHistoricalSummaryResponse: FetchHistoricalSummaryResponse | undefined; + isInitialLoading: boolean; + isRefetching: boolean; isLoading: boolean; isSuccess: boolean; isError: boolean; - refetch: ( - options?: (RefetchOptions & RefetchQueryFilters) | undefined - ) => Promise>; } export interface Params { @@ -36,33 +28,32 @@ export function useFetchHistoricalSummary({ }: Params): UseFetchHistoricalSummaryResponse { const { http } = useKibana().services; - const { isInitialLoading, isLoading, isError, isSuccess, isRefetching, data, refetch } = useQuery( - { - queryKey: ['fetchHistoricalSummary', sloIds], - queryFn: async ({ signal }) => { - try { - const response = await http.post( - '/internal/observability/slos/_historical_summary', - { - body: JSON.stringify({ sloIds }), - signal, - } - ); - - return response; - } catch (error) { - // ignore error for retrieving slos - } - }, - refetchOnWindowFocus: false, - } - ); + const { isInitialLoading, isLoading, isError, isSuccess, isRefetching, data } = useQuery({ + queryKey: ['fetchHistoricalSummary', sloIds], + queryFn: async ({ signal }) => { + try { + const response = await http.post( + '/internal/observability/slos/_historical_summary', + { + body: JSON.stringify({ sloIds }), + signal, + } + ); + + return response; + } catch (error) { + // ignore error + } + }, + refetchOnWindowFocus: false, + }); return { - sloHistoricalSummaryResponse: isInitialLoading ? EMPTY_RESPONSE : data ?? EMPTY_RESPONSE, - isLoading: isInitialLoading || isLoading || isRefetching, + sloHistoricalSummaryResponse: data, + isLoading, + isRefetching, + isInitialLoading, isSuccess, isError, - refetch, }; } diff --git a/x-pack/plugins/observability/public/hooks/slo/use_fetch_slo_list.ts b/x-pack/plugins/observability/public/hooks/slo/use_fetch_slo_list.ts index 7864b85181a3f..82e675bcc8437 100644 --- a/x-pack/plugins/observability/public/hooks/slo/use_fetch_slo_list.ts +++ b/x-pack/plugins/observability/public/hooks/slo/use_fetch_slo_list.ts @@ -5,14 +5,16 @@ * 2.0. */ +import { useState } from 'react'; import { QueryObserverResult, RefetchOptions, RefetchQueryFilters, useQuery, + useQueryClient, } from '@tanstack/react-query'; - import { FindSLOResponse } from '@kbn/slo-schema'; + import { useKibana } from '../../utils/kibana_react'; interface SLOListParams { @@ -20,6 +22,7 @@ interface SLOListParams { page?: number; sortBy?: string; indicatorTypes?: string[]; + shouldRefetch?: boolean; } export interface UseFetchSloListResponse { @@ -33,13 +36,20 @@ export interface UseFetchSloListResponse { ) => Promise>; } +const SHORT_REFETCH_INTERVAL = 1000 * 5; // 5 seconds +const LONG_REFETCH_INTERVAL = 1000 * 60; // 1 minute + export function useFetchSloList({ name = '', page = 1, sortBy = 'name', indicatorTypes = [], + shouldRefetch, }: SLOListParams | undefined = {}): UseFetchSloListResponse { const { http } = useKibana().services; + const queryClient = useQueryClient(); + + const [stateRefetchInterval, setStateRefetchInterval] = useState(SHORT_REFETCH_INTERVAL); const { isInitialLoading, isLoading, isError, isSuccess, isRefetching, data, refetch } = useQuery( { @@ -61,12 +71,32 @@ export function useFetchSloList({ return response; } catch (error) { - // ignore error for retrieving slos + // ignore error } }, - refetchOnWindowFocus: false, keepPreviousData: true, + refetchOnWindowFocus: false, + refetchInterval: shouldRefetch ? stateRefetchInterval : undefined, staleTime: 1000, + onSuccess: ({ results }: FindSLOResponse) => { + if (!shouldRefetch) { + return; + } + + if (results.find((slo) => slo.summary.status === 'NO_DATA')) { + setStateRefetchInterval(SHORT_REFETCH_INTERVAL); + } else { + setStateRefetchInterval(LONG_REFETCH_INTERVAL); + } + + queryClient.invalidateQueries(['fetchHistoricalSummary'], { + exact: false, + }); + + queryClient.invalidateQueries(['fetchActiveAlerts'], { + exact: false, + }); + }, } ); diff --git a/x-pack/plugins/observability/public/hooks/use_kibana_space.tsx b/x-pack/plugins/observability/public/hooks/use_kibana_space.tsx index 11716e948d855..8f2ebf187dc77 100644 --- a/x-pack/plugins/observability/public/hooks/use_kibana_space.tsx +++ b/x-pack/plugins/observability/public/hooks/use_kibana_space.tsx @@ -15,7 +15,7 @@ export const useKibanaSpace = () => { data: space, loading, error, - } = useFetcher>(() => { + } = useFetcher | undefined>(() => { return services.spaces?.getActiveSpace(); }, [services.spaces]); diff --git a/x-pack/plugins/observability/public/pages/overview/containers/overview_page/helpers/use_metrics.ts b/x-pack/plugins/observability/public/pages/overview/containers/overview_page/helpers/use_metrics.ts index 78fffde25d629..dd9581bd67867 100644 --- a/x-pack/plugins/observability/public/pages/overview/containers/overview_page/helpers/use_metrics.ts +++ b/x-pack/plugins/observability/public/pages/overview/containers/overview_page/helpers/use_metrics.ts @@ -29,9 +29,22 @@ export const useOverviewMetrics = ({ hasAnyData }: { hasAnyData: boolean | undef } CAPABILITIES_KEYS.forEach((feature) => { - if (capabilities[feature].show === false) { + const name = feature === 'infrastructure' ? 'metrics' : feature; + + // Track metric if the feature has been disabled, either because it + // is missing or has show === false (manual disabling may not be + // possible in all versions of Kibana) + if (!capabilities[feature] || capabilities[feature]?.show === false) { + trackMetric({ + metric: `oblt_disabled_feature_${name}`, + }); + } + + // Track a separate metric if the feature is missing from the capabilities + // (This usually means the plugin was auto-disabled by Kibana) + if (!capabilities[feature]) { trackMetric({ - metric: `oblt_disabled_feature_${feature === 'infrastructure' ? 'metrics' : feature}`, + metric: `oblt_missing_feature_${name}`, }); } }); diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/apm_availability/apm_availability_indicator_type_form.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/apm_availability/apm_availability_indicator_type_form.tsx index c24bf74dc6346..e61e1c7056596 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/components/apm_availability/apm_availability_indicator_type_form.tsx +++ b/x-pack/plugins/observability/public/pages/slo_edit/components/apm_availability/apm_availability_indicator_type_form.tsx @@ -18,10 +18,11 @@ import { i18n } from '@kbn/i18n'; import type { CreateSLOInput } from '@kbn/slo-schema'; import { useFetchApmIndex } from '../../../../hooks/slo/use_fetch_apm_indices'; -import { FieldSelector } from '../common/field_selector'; +import { FieldSelector } from '../apm_common/field_selector'; +import { QueryBuilder } from '../common/query_builder'; export function ApmAvailabilityIndicatorTypeForm() { - const { control, setValue } = useFormContext(); + const { control, setValue, watch } = useFormContext(); const { data: apmIndex } = useFetchApmIndex(); useEffect(() => { setValue('indicator.params.index', apmIndex); @@ -145,7 +146,23 @@ export function ApmAvailabilityIndicatorTypeForm() { )} /> - + + + ); diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/common/field_selector.stories.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/apm_common/field_selector.stories.tsx similarity index 95% rename from x-pack/plugins/observability/public/pages/slo_edit/components/common/field_selector.stories.tsx rename to x-pack/plugins/observability/public/pages/slo_edit/components/apm_common/field_selector.stories.tsx index 748b53ca82d57..47111ee1c565b 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/components/common/field_selector.stories.tsx +++ b/x-pack/plugins/observability/public/pages/slo_edit/components/apm_common/field_selector.stories.tsx @@ -15,7 +15,7 @@ import { SLO_EDIT_FORM_DEFAULT_VALUES } from '../../constants'; export default { component: Component, - title: 'app/SLO/EditPage/Common/FieldSelector', + title: 'app/SLO/EditPage/ApmCommon/FieldSelector', decorators: [KibanaReactStorybookDecorator], }; diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/common/field_selector.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/apm_common/field_selector.tsx similarity index 100% rename from x-pack/plugins/observability/public/pages/slo_edit/components/common/field_selector.tsx rename to x-pack/plugins/observability/public/pages/slo_edit/components/apm_common/field_selector.tsx diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/apm_latency/apm_latency_indicator_type_form.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/apm_latency/apm_latency_indicator_type_form.tsx index 4dcf095571c4a..a5f39a0f8f958 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/components/apm_latency/apm_latency_indicator_type_form.tsx +++ b/x-pack/plugins/observability/public/pages/slo_edit/components/apm_latency/apm_latency_indicator_type_form.tsx @@ -12,10 +12,11 @@ import { i18n } from '@kbn/i18n'; import type { CreateSLOInput } from '@kbn/slo-schema'; import { useFetchApmIndex } from '../../../../hooks/slo/use_fetch_apm_indices'; -import { FieldSelector } from '../common/field_selector'; +import { FieldSelector } from '../apm_common/field_selector'; +import { QueryBuilder } from '../common/query_builder'; export function ApmLatencyIndicatorTypeForm() { - const { control, setValue } = useFormContext(); + const { control, setValue, watch } = useFormContext(); const { data: apmIndex } = useFetchApmIndex(); useEffect(() => { setValue('indicator.params.index', apmIndex); @@ -113,7 +114,23 @@ export function ApmLatencyIndicatorTypeForm() { )} /> - + + + ); diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/custom_kql/query_builder.stories.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/common/query_builder.stories.tsx similarity index 100% rename from x-pack/plugins/observability/public/pages/slo_edit/components/custom_kql/query_builder.stories.tsx rename to x-pack/plugins/observability/public/pages/slo_edit/components/common/query_builder.stories.tsx diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/custom_kql/query_builder.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/common/query_builder.tsx similarity index 100% rename from x-pack/plugins/observability/public/pages/slo_edit/components/custom_kql/query_builder.tsx rename to x-pack/plugins/observability/public/pages/slo_edit/components/common/query_builder.tsx diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/custom_kql/custom_kql_indicator_type_form.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/custom_kql/custom_kql_indicator_type_form.tsx index d9c39b0715be4..a9fb7cd58e480 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/components/custom_kql/custom_kql_indicator_type_form.tsx +++ b/x-pack/plugins/observability/public/pages/slo_edit/components/custom_kql/custom_kql_indicator_type_form.tsx @@ -12,7 +12,7 @@ import { useFormContext } from 'react-hook-form'; import { CreateSLOInput } from '@kbn/slo-schema'; import { IndexSelection } from './index_selection'; -import { QueryBuilder } from './query_builder'; +import { QueryBuilder } from '../common/query_builder'; export function CustomKqlIndicatorTypeForm() { const { control, watch } = useFormContext(); diff --git a/x-pack/plugins/observability/public/pages/slos/components/auto_refresh_button.stories.tsx b/x-pack/plugins/observability/public/pages/slos/components/auto_refresh_button.stories.tsx new file mode 100644 index 0000000000000..5a5b94401253e --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slos/components/auto_refresh_button.stories.tsx @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState } from 'react'; +import { ComponentStory } from '@storybook/react'; + +import { KibanaReactStorybookDecorator } from '../../../utils/kibana_react.storybook_decorator'; +import { AutoRefreshButton as Component } from './auto_refresh_button'; + +export default { + component: Component, + title: 'app/SLO/ListPage/AutoRefreshButton', + decorators: [KibanaReactStorybookDecorator], +}; + +const Template: ComponentStory = () => { + const [isAutoRefreshing, setIsAutoRefreshing] = useState(true); + + const toggleEnabled = () => { + setIsAutoRefreshing(!isAutoRefreshing); + }; + + return ; +}; + +const defaultProps = { + enabled: true, + disabled: false, +}; + +export const AutoRefreshButton = Template.bind({}); +AutoRefreshButton.args = defaultProps; diff --git a/x-pack/plugins/observability/public/pages/slos/components/auto_refresh_button.tsx b/x-pack/plugins/observability/public/pages/slos/components/auto_refresh_button.tsx new file mode 100644 index 0000000000000..5101a5c1d96ef --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slos/components/auto_refresh_button.tsx @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { EuiButtonEmpty } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +interface Props { + isAutoRefreshing: boolean; + dataTestSubj?: string; + disabled?: boolean; + onClick: () => void; +} + +export function AutoRefreshButton({ + dataTestSubj = 'autoRefreshButton', + disabled, + isAutoRefreshing, + onClick, +}: Props) { + return isAutoRefreshing ? ( + + {i18n.translate('xpack.observability.slosPage.stopRefreshingButtonLabel', { + defaultMessage: 'Stop refreshing', + })} + + ) : ( + + {i18n.translate('xpack.observability.slosPage.autoRefreshButtonLabel', { + defaultMessage: 'Auto-refresh', + })} + + ); +} diff --git a/x-pack/plugins/observability/public/pages/slos/components/badges/slo_badges.tsx b/x-pack/plugins/observability/public/pages/slos/components/badges/slo_badges.tsx index 32cdeb785270e..cd4c7d9c2d5d5 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/badges/slo_badges.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/badges/slo_badges.tsx @@ -6,20 +6,38 @@ */ import React from 'react'; -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { EuiBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { SLOWithSummaryResponse } from '@kbn/slo-schema'; +import { i18n } from '@kbn/i18n'; +import { useKibana } from '../../../../utils/kibana_react'; +import { paths } from '../../../../config'; +import { ActiveAlerts } from '../../../../hooks/slo/use_fetch_active_alerts'; import { SloStatusBadge } from './slo_status_badge'; import { SloIndicatorTypeBadge } from './slo_indicator_type_badge'; import { SloTimeWindowBadge } from './slo_time_window_badge'; export interface Props { slo: SLOWithSummaryResponse; + activeAlerts?: ActiveAlerts; } -export function SloBadges({ slo }: Props) { +export function SloBadges({ slo, activeAlerts }: Props) { + const { + application: { navigateToUrl }, + http: { basePath }, + } = useKibana().services; + + const handleClick = () => { + if (activeAlerts) { + navigateToUrl( + `${basePath.prepend(paths.observability.alerts)}?_a=${toAlertsPageQuery(activeAlerts)}` + ); + } + }; + return ( - + @@ -27,6 +45,34 @@ export function SloBadges({ slo }: Props) { + {!!activeAlerts && ( + + + {i18n.translate('xpack.observability.slos.slo.activeAlertsBadge.label', { + defaultMessage: '{count, plural, one {# alert} other {# alerts}}', + values: { count: activeAlerts.count }, + })} + + + )} ); } + +function toAlertsPageQuery(activeAlerts: ActiveAlerts): string { + const kuery = activeAlerts.ruleIds + .map((ruleId) => `kibana.alert.rule.uuid:"${activeAlerts.ruleIds[0]}"`) + .join(' or '); + + const query = `(kuery:'${kuery}',rangeFrom:now-15m,rangeTo:now,status:all)`; + return query; +} diff --git a/x-pack/plugins/observability/public/pages/slos/components/badges/slo_status_badge.tsx b/x-pack/plugins/observability/public/pages/slos/components/badges/slo_status_badge.tsx index a1c89ca857b18..ed69ebae221e5 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/badges/slo_status_badge.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/badges/slo_status_badge.tsx @@ -8,7 +8,6 @@ import React from 'react'; import { EuiBadge, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { euiLightVars } from '@kbn/ui-theme'; import { SLOWithSummaryResponse } from '@kbn/slo-schema'; export interface SloStatusProps { @@ -21,7 +20,7 @@ export function SloStatusBadge({ slo }: SloStatusProps) {
      {slo.summary.status === 'NO_DATA' && ( - + {i18n.translate('xpack.observability.slos.slo.state.noData', { defaultMessage: 'No data', })} @@ -29,7 +28,7 @@ export function SloStatusBadge({ slo }: SloStatusProps) { )} {slo.summary.status === 'HEALTHY' && ( - + {i18n.translate('xpack.observability.slos.slo.state.healthy', { defaultMessage: 'Healthy', })} @@ -37,7 +36,7 @@ export function SloStatusBadge({ slo }: SloStatusProps) { )} {slo.summary.status === 'DEGRADING' && ( - + {i18n.translate('xpack.observability.slos.slo.state.degrading', { defaultMessage: 'Degrading', })} @@ -45,7 +44,7 @@ export function SloStatusBadge({ slo }: SloStatusProps) { )} {slo.summary.status === 'VIOLATED' && ( - + {i18n.translate('xpack.observability.slos.slo.state.violated', { defaultMessage: 'Violated', })} @@ -56,7 +55,7 @@ export function SloStatusBadge({ slo }: SloStatusProps) { {slo.summary.errorBudget.isEstimated && (
      - + {i18n.translate('xpack.observability.slos.slo.state.forecasted', { defaultMessage: 'Forecasted', })} 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 3e6dd1c87d798..244bda63596b7 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 @@ -9,7 +9,7 @@ import React from 'react'; import { ComponentStory } from '@storybook/react'; import { KibanaReactStorybookDecorator } from '../../../utils/kibana_react.storybook_decorator'; -import { SloList as Component } from './slo_list'; +import { SloList as Component, Props } from './slo_list'; export default { component: Component, @@ -18,9 +18,11 @@ export default { decorators: [KibanaReactStorybookDecorator], }; -const Template: ComponentStory = () => ; +const Template: ComponentStory = (props: Props) => ; -const defaultProps = {}; +const defaultProps = { + autoRefresh: true, +}; export const SloList = Template.bind({}); SloList.args = defaultProps; 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 9d5c8515d3403..1ca463c1ff940 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 @@ -18,7 +18,11 @@ import { } from './slo_list_search_filter_sort_bar'; import { SloListItems } from './slo_list_items'; -export function SloList() { +export interface Props { + autoRefresh: boolean; +} + +export function SloList({ autoRefresh }: Props) { const [activePage, setActivePage] = useState(0); const [query, setQuery] = useState(''); @@ -30,6 +34,7 @@ export function SloList() { name: query, sortBy: sort, indicatorTypes: indicatorTypeFilter, + shouldRefetch: autoRefresh, }); const { results = [], total = 0, perPage = 0 } = sloList || {}; diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list_item.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list_item.tsx index a56d48698d540..282b82bf41b7e 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/slo_list_item.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/slo_list_item.tsx @@ -20,6 +20,7 @@ import { import { i18n } from '@kbn/i18n'; import { HistoricalSummaryResponse, SLOWithSummaryResponse } from '@kbn/slo-schema'; +import { ActiveAlerts } from '../../../hooks/slo/use_fetch_active_alerts'; import { useCapabilities } from '../../../hooks/slo/use_capabilities'; import { useKibana } from '../../../utils/kibana_react'; import { useCloneSlo } from '../../../hooks/slo/use_clone_slo'; @@ -36,12 +37,14 @@ export interface SloListItemProps { slo: SLOWithSummaryResponse; historicalSummary?: HistoricalSummaryResponse[]; historicalSummaryLoading: boolean; + activeAlerts?: ActiveAlerts; } export function SloListItem({ slo, historicalSummary = [], historicalSummaryLoading, + activeAlerts, }: SloListItemProps) { const { application: { navigateToUrl }, @@ -101,7 +104,7 @@ export function SloListItem({ {slo.name} - + diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list_items.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list_items.tsx index 2e0bb5b267b7a..cf95af3101c13 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/slo_list_items.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/slo_list_items.tsx @@ -8,6 +8,7 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { SLOWithSummaryResponse } from '@kbn/slo-schema'; +import { useFetchActiveAlerts } from '../../../hooks/slo/use_fetch_active_alerts'; import { useFetchHistoricalSummary } from '../../../hooks/slo/use_fetch_historical_summary'; import { SloListItem } from './slo_list_item'; import { SloListEmpty } from './slo_list_empty'; @@ -23,6 +24,10 @@ export function SloListItems({ sloList, loading, error }: Props) { const { isLoading: historicalSummaryLoading, sloHistoricalSummaryResponse } = useFetchHistoricalSummary({ sloIds: sloList.map((slo) => slo.id) }); + const { data: activeAlertsBySlo } = useFetchActiveAlerts({ + sloIds: sloList.map((slo) => slo.id), + }); + if (!loading && !error && sloList.length === 0) { return ; } @@ -36,8 +41,9 @@ export function SloListItems({ sloList, loading, error }: Props) { ))} 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 c224accc280ad..cfa07011159e6 100644 --- a/x-pack/plugins/observability/public/pages/slos/index.test.tsx +++ b/x-pack/plugins/observability/public/pages/slos/index.test.tsx @@ -155,6 +155,21 @@ describe('SLOs Page', () => { expect(screen.getByText('Create new SLO')).toBeTruthy(); }); + it('should have an Auto Refresh button', async () => { + useFetchSloListMock.mockReturnValue({ isLoading: false, sloList }); + + useFetchHistoricalSummaryMock.mockReturnValue({ + isLoading: false, + sloHistoricalSummaryResponse: historicalSummaryData, + }); + + await act(async () => { + render(, config); + }); + + expect(screen.getByTestId('autoRefreshButton')).toBeTruthy(); + }); + describe('when API has returned results', () => { it('renders the SLO list with SLO items', async () => { useFetchSloListMock.mockReturnValue({ isLoading: false, sloList }); diff --git a/x-pack/plugins/observability/public/pages/slos/index.tsx b/x-pack/plugins/observability/public/pages/slos/index.tsx index 0fd7b51810d5e..25f93561e6ed9 100644 --- a/x-pack/plugins/observability/public/pages/slos/index.tsx +++ b/x-pack/plugins/observability/public/pages/slos/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React from 'react'; +import React, { useState } from 'react'; import { EuiButton } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -17,6 +17,7 @@ import { useCapabilities } from '../../hooks/slo/use_capabilities'; import { useFetchSloList } from '../../hooks/slo/use_fetch_slo_list'; import { SloList } from './components/slo_list'; import { SloListWelcomePrompt } from './components/slo_list_welcome_prompt'; +import { AutoRefreshButton } from './components/auto_refresh_button'; import PageNotFound from '../404'; import { paths } from '../../config'; import { isSloFeatureEnabled } from './helpers/is_slo_feature_enabled'; @@ -35,6 +36,8 @@ export function SlosPage() { const { total } = sloList || {}; + const [isAutoRefreshing, setIsAutoRefreshing] = useState(true); + useBreadcrumbs([ { href: basePath.prepend(paths.observability.slos), @@ -48,6 +51,10 @@ export function SlosPage() { navigateToUrl(basePath.prepend(paths.observability.sloCreate)); }; + const handleToggleAutoRefresh = () => { + setIsAutoRefreshing(!isAutoRefreshing); + }; + if (!isSloFeatureEnabled(config)) { return ; } @@ -78,12 +85,16 @@ export function SlosPage() { defaultMessage: 'Create new SLO', })} , + , ], bottomBorder: false, }} data-test-subj="slosPage" > - + ); } diff --git a/x-pack/plugins/observability/public/plugin.ts b/x-pack/plugins/observability/public/plugin.ts index e43725d0c5681..6af40e9a4345f 100644 --- a/x-pack/plugins/observability/public/plugin.ts +++ b/x-pack/plugins/observability/public/plugin.ts @@ -105,7 +105,7 @@ export interface ObservabilityPublicPluginsStart { ruleTypeRegistry: RuleTypeRegistryContract; security: SecurityPluginStart; share: SharePluginStart; - spaces: SpacesPluginStart; + spaces?: SpacesPluginStart; triggersActionsUi: TriggersAndActionsUIPublicPluginStart; usageCollection: UsageCollectionSetup; unifiedSearch: UnifiedSearchPublicPluginStart; diff --git a/x-pack/plugins/observability/server/lib/rules/slo_burn_rate/register.ts b/x-pack/plugins/observability/server/lib/rules/slo_burn_rate/register.ts index 6a2351435e9aa..4f01d59f70174 100644 --- a/x-pack/plugins/observability/server/lib/rules/slo_burn_rate/register.ts +++ b/x-pack/plugins/observability/server/lib/rules/slo_burn_rate/register.ts @@ -38,7 +38,7 @@ export function sloBurnRateRuleType(createLifecycleRuleExecutor: CreateLifecycle defaultActionGroupId: FIRED_ACTION.id, actionGroups: [FIRED_ACTION], producer: 'slo', - minimumLicenseRequired: 'basic' as LicenseType, + minimumLicenseRequired: 'platinum' as LicenseType, isExportable: true, executor: createLifecycleRuleExecutor(getRuleExecutor()), doesSetRecoveryContext: true, diff --git a/x-pack/plugins/observability/server/plugin.ts b/x-pack/plugins/observability/server/plugin.ts index 98b21abbaefe5..4b2e2c722230b 100644 --- a/x-pack/plugins/observability/server/plugin.ts +++ b/x-pack/plugins/observability/server/plugin.ts @@ -13,15 +13,20 @@ import { DEFAULT_APP_CATEGORIES, Logger, } from '@kbn/core/server'; +import { hiddenTypes as filesSavedObjectTypes } from '@kbn/files-plugin/server/saved_objects'; import { PluginSetupContract } from '@kbn/alerting-plugin/server'; import { Dataset, RuleRegistryPluginSetupContract } from '@kbn/rule-registry-plugin/server'; import { PluginSetupContract as FeaturesSetup } from '@kbn/features-plugin/server'; -import { createUICapabilities } from '@kbn/cases-plugin/common'; +import { + createUICapabilities as createCasesUICapabilities, + getApiTags as getCasesApiTags, +} from '@kbn/cases-plugin/common'; +import { SpacesPluginSetup } from '@kbn/spaces-plugin/server'; import { experimentalRuleFieldMap } from '@kbn/rule-registry-plugin/common/assets/field_maps/experimental_rule_field_map'; -import { mappingFromFieldMap } from '@kbn/rule-registry-plugin/common/mapping_from_field_map'; -import { ECS_COMPONENT_TEMPLATE_NAME } from '@kbn/rule-registry-plugin/common/assets'; +import { ECS_COMPONENT_TEMPLATE_NAME } from '@kbn/alerting-plugin/server'; import type { GuidedOnboardingPluginSetup } from '@kbn/guided-onboarding-plugin/server'; +import { mappingFromFieldMap } from '@kbn/alerting-plugin/common'; import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server'; import { kubernetesGuideId, @@ -50,6 +55,7 @@ interface PluginSetup { features: FeaturesSetup; guidedOnboarding: GuidedOnboardingPluginSetup; ruleRegistry: RuleRegistryPluginSetupContract; + spaces?: SpacesPluginSetup; usageCollection?: UsageCollectionSetup; } @@ -62,7 +68,9 @@ export class ObservabilityPlugin implements Plugin { } public setup(core: CoreSetup, plugins: PluginSetup) { - const casesCapabilities = createUICapabilities(); + const casesCapabilities = createCasesUICapabilities(); + const casesApiTags = getCasesApiTags(observabilityFeatureId); + const config = this.initContext.config.get(); plugins.features.registerKibanaFeature({ @@ -77,7 +85,7 @@ export class ObservabilityPlugin implements Plugin { cases: [observabilityFeatureId], privileges: { all: { - api: ['casesSuggestUserProfiles', 'bulkGetUserProfiles'], + api: casesApiTags.all, app: [casesFeatureId, 'kibana'], catalogue: [observabilityFeatureId], cases: { @@ -87,13 +95,13 @@ export class ObservabilityPlugin implements Plugin { push: [observabilityFeatureId], }, savedObject: { - all: [], - read: [], + all: [...filesSavedObjectTypes], + read: [...filesSavedObjectTypes], }, ui: casesCapabilities.all, }, read: { - api: ['casesSuggestUserProfiles', 'bulkGetUserProfiles'], + api: casesApiTags.read, app: [casesFeatureId, 'kibana'], catalogue: [observabilityFeatureId], cases: { @@ -101,7 +109,7 @@ export class ObservabilityPlugin implements Plugin { }, savedObject: { all: [], - read: [], + read: [...filesSavedObjectTypes], }, ui: casesCapabilities.read, }, @@ -116,7 +124,7 @@ export class ObservabilityPlugin implements Plugin { groupType: 'independent', privileges: [ { - api: [], + api: casesApiTags.delete, id: 'cases_delete', name: i18n.translate( 'xpack.observability.featureRegistry.deleteSubFeatureDetails', diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/common.test.ts b/x-pack/plugins/observability/server/services/slo/transform_generators/common.test.ts index ba9856860ec84..fa34496573338 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_generators/common.test.ts +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/common.test.ts @@ -13,7 +13,9 @@ describe('common', () => { ['foo-*', 'foo-*'], ['foo-*,bar-*', ['foo-*', 'bar-*']], ['remote:foo-*', 'remote:foo-*'], - ['remote:foo*,bar-*', ['remote:foo*', 'remote:bar-*']], + ['remote:foo*,bar-*', ['remote:foo*', 'bar-*']], + ['remote:foo*,remote:bar-*', ['remote:foo*', 'remote:bar-*']], + ['remote:foo*,bar-*,remote:baz-*', ['remote:foo*', 'bar-*', 'remote:baz-*']], ])("parses the index '%s' correctly", (index, expected) => { expect(parseIndex(index)).toEqual(expected); }); diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/common.ts b/x-pack/plugins/observability/server/services/slo/transform_generators/common.ts index 54197076d359f..ea64f23662695 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_generators/common.ts +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/common.ts @@ -17,15 +17,9 @@ export function getElastichsearchQueryOrThrow(kuery: string) { } export function parseIndex(index: string): string | string[] { - if (index.indexOf(',') > -1) { - if (index.indexOf(':') > -1) { - const indexParts = index.split(':'); // "remote_name:foo-*,bar*" - const remoteName = indexParts[0]; - return indexParts[1].split(',').map((idx) => `${remoteName}:${idx}`); // [ "remote_name:foo-*", "remote_name:bar-*"] - } - - return index.split(','); + if (index.indexOf(',') === -1) { + return index; } - return index; + return index.split(','); } diff --git a/x-pack/plugins/observability/server/services/slo/update_slo.ts b/x-pack/plugins/observability/server/services/slo/update_slo.ts index 9b4aa4e7dc213..cf2c0e2e1c89f 100644 --- a/x-pack/plugins/observability/server/services/slo/update_slo.ts +++ b/x-pack/plugins/observability/server/services/slo/update_slo.ts @@ -6,7 +6,6 @@ */ import deepEqual from 'fast-deep-equal'; -import merge from 'lodash/merge'; import { ElasticsearchClient } from '@kbn/core/server'; import { UpdateSLOParams, UpdateSLOResponse, updateSLOResponseSchema } from '@kbn/slo-schema'; @@ -42,7 +41,7 @@ export class UpdateSLO { private updateSLO(originalSlo: SLO, params: UpdateSLOParams) { let hasBreakingChange = false; - const updatedSlo: SLO = merge({}, originalSlo, params, { updatedAt: new Date() }); + const updatedSlo: SLO = Object.assign({}, originalSlo, params, { updatedAt: new Date() }); validateSLO(updatedSlo); if (!deepEqual(originalSlo.indicator, updatedSlo.indicator)) { diff --git a/x-pack/plugins/observability/tsconfig.json b/x-pack/plugins/observability/tsconfig.json index eb85b5fb25b26..eadf340054f52 100644 --- a/x-pack/plugins/observability/tsconfig.json +++ b/x-pack/plugins/observability/tsconfig.json @@ -71,6 +71,7 @@ "@kbn/shared-ux-router", "@kbn/alerts-ui-shared", "@kbn/core-application-browser", + "@kbn/files-plugin", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/osquery/common/utils/replace_params_query.ts b/x-pack/plugins/osquery/common/utils/replace_params_query.ts index 3c99daf5ec3cf..d06be33a87330 100644 --- a/x-pack/plugins/osquery/common/utils/replace_params_query.ts +++ b/x-pack/plugins/osquery/common/utils/replace_params_query.ts @@ -10,6 +10,10 @@ import { each, get } from 'lodash'; const CONTAINS_DYNAMIC_PARAMETER_REGEX = /\{{([^}]+)\}}/g; // when there are 2 opening and 2 closing curly brackets (including brackets) export const replaceParamsQuery = (query: string, data: object) => { + if (!containsDynamicQuery(query)) { + return { result: query, skipped: false }; + } + const matchedBrackets = query.match(new RegExp(CONTAINS_DYNAMIC_PARAMETER_REGEX)); let resultQuery = query; diff --git a/x-pack/plugins/osquery/cypress/e2e/all/add_integration.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/add_integration.cy.ts index 35c5c2aa15503..aaf5fc319be15 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/add_integration.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/add_integration.cy.ts @@ -16,7 +16,7 @@ import { addIntegration, closeModalIfVisible, closeToastIfVisible } from '../../ import { login } from '../../tasks/login'; import { findAndClickButton, findFormFieldByRowsLabelAndType } from '../../tasks/live_query'; import { ArchiverMethod, runKbnArchiverScript } from '../../tasks/archiver'; -import { DEFAULT_POLICY } from '../../screens/fleet'; +import { DEFAULT_POLICY, OSQUERY_POLICY } from '../../screens/fleet'; describe('ALL - Add Integration', () => { const integration = 'Osquery Manager'; @@ -43,11 +43,10 @@ describe('ALL - Add Integration', () => { cy.get('[title="Osquery Manager • Integration"]').should('exist').click(); }); - it('should add the old integration and be able to upgrade it', () => { + it.skip('should add the old integration and be able to upgrade it', () => { const oldVersion = '0.7.4'; cy.visit(OLD_OSQUERY_MANAGER); - cy.contains(integration).click(); addIntegration(); cy.contains('osquery_manager-1'); cy.visit('app/fleet/policies'); @@ -94,11 +93,19 @@ describe('ALL - Add Integration', () => { addIntegration(); cy.contains('osquery_manager-'); closeToastIfVisible(); - cy.getBySel('nav-search-input').type('Osquery'); - cy.get('[title="Osquery • Management"]').should('exist').click(); + cy.visit(OSQUERY); cy.contains('Live queries history'); }); + it(`add integration to ${OSQUERY_POLICY}`, () => { + cy.visit(FLEET_AGENT_POLICIES); + cy.contains(OSQUERY_POLICY).click(); + cy.contains('Add integration').click(); + cy.contains(integration).click(); + addIntegration(OSQUERY_POLICY); + cy.contains('osquery_manager-'); + }); + it('should have integration and packs copied when upgrading integration', () => { const packageName = 'osquery_manager'; const oldVersion = '1.2.0'; @@ -110,12 +117,12 @@ describe('ALL - Add Integration', () => { cy.contains('Upgrade'); cy.contains('Agent policy 1').click(); cy.get('tr') - .should('contain', 'osquery_manager-2') + .should('contain', 'osquery_manager-3') .and('contain', 'Osquery Manager') .and('contain', `v${oldVersion}`); cy.contains('Actions').click(); cy.contains('View policy').click(); - cy.contains('name: osquery_manager-2'); + cy.contains('name: osquery_manager-3'); cy.contains(`version: ${oldVersion}`); cy.get('.euiFlyoutFooter').within(() => { cy.contains('Close').click(); @@ -142,19 +149,19 @@ describe('ALL - Add Integration', () => { cy.contains(/^Advanced$/).click(); cy.contains('"Integration":'); cy.contains(/^Upgrade integration$/).click(); - cy.contains(/^osquery_manager-2$/).click(); + cy.contains(/^osquery_manager-3$/).click(); cy.contains(/^Advanced$/).click(); cy.contains('"Integration":'); cy.contains('Cancel').click(); closeModalIfVisible(); cy.get('tr') - .should('contain', 'osquery_manager-2') + .should('contain', 'osquery_manager-3') .and('contain', 'Osquery Manager') .and('contain', 'v') .and('not.contain', `v${oldVersion}`); cy.contains('Actions').click(); cy.contains('View policy').click(); - cy.contains('name: osquery_manager-2'); + cy.contains('name: osquery_manager-3'); // test list of prebuilt queries navigateTo('/app/osquery/saved_queries'); diff --git a/x-pack/plugins/osquery/cypress/e2e/all/alerts.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/alerts.cy.ts index 416fdfe5b971a..2f313d1bb9979 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/alerts.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/alerts.cy.ts @@ -58,7 +58,7 @@ describe('Alert Event Details', () => { it('should prepare packs and alert rules', () => { const PACK_NAME = 'testpack'; - navigateTo('/app/osquery/packs'); + navigateTo('/app/osquery/live_queries'); preparePack(PACK_NAME); findAndClickButton('Edit'); cy.contains(`Edit ${PACK_NAME}`); @@ -439,6 +439,27 @@ describe('Alert Event Details', () => { }); }); + it('should be able to run take action query against all enrolled agents', () => { + loadAlertsEvents(); + cy.getBySel('expand-event').first().click({ force: true }); + cy.getBySel('take-action-dropdown-btn').click(); + cy.getBySel('osquery-action-item').click(); + cy.getBySel('agentSelection').within(() => { + cy.getBySel('comboBoxClearButton').click(); + cy.getBySel('comboBoxInput').type('All{downArrow}{enter}{esc}'); + cy.contains('All agents'); + }); + inputQuery("SELECT * FROM os_version where name='{{host.os.name}}';", { + parseSpecialCharSequences: false, + }); + cy.wait(1000); + submitQuery(); + cy.getBySel('flyout-body-osquery').within(() => { + // at least 2 agents should have responded + cy.get('[data-grid-row-index]').should('have.length.at.least', 2); + }); + }); + it('should substitute params in osquery ran from timelines alerts', () => { loadAlertsEvents(); cy.getBySel('send-alert-to-timeline-button').first().click({ force: true }); diff --git a/x-pack/plugins/osquery/cypress/e2e/all/custom_space.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/custom_space.cy.ts index e58f6c689eb55..3aaeff293ac47 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/custom_space.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/custom_space.cy.ts @@ -29,6 +29,7 @@ describe('ALL - Custom space', () => { id: CUSTOM_SPACE, name: CUSTOM_SPACE, }, + failOnStatusCode: false, headers: { 'kbn-xsrf': 'create-space' }, }); }); diff --git a/x-pack/plugins/osquery/cypress/e2e/all/live_query.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/live_query.cy.ts index 8991858bd7c26..aa21b6142a9df 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/live_query.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/live_query.cy.ts @@ -68,6 +68,7 @@ describe('ALL - Live Query', () => { checkResults(); cy.react('Cell', { props: { columnIndex: 0 } }) .should('exist') + .first() .click(); cy.url().should('include', 'app/fleet/agents/'); }); diff --git a/x-pack/plugins/osquery/cypress/e2e/all/metrics.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/metrics.cy.ts index 0edefdc24ea42..998d5220e24f4 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/metrics.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/metrics.cy.ts @@ -29,19 +29,20 @@ describe('ALL - Inventory', () => { cy.wait(1000); - cy.getBySel('nodeContainer').click(); + cy.getBySel('nodeContainer').first().click(); cy.contains('Osquery').click(); inputQuery('select * from uptime;'); submitQuery(); checkResults(); }); + it('should be able to run the previously saved query', () => { cy.getBySel('toggleNavButton').click(); cy.getBySel('collapsibleNavAppLink').contains('Infrastructure').click(); cy.wait(500); - cy.getBySel('nodeContainer').click(); + cy.getBySel('nodeContainer').first().click(); cy.contains('Osquery').click(); cy.getBySel('comboBoxInput').first().click(); diff --git a/x-pack/plugins/osquery/cypress/e2e/all/packs.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/packs.cy.ts index d402412dcdbd7..74253324acdfb 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/packs.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/packs.cy.ts @@ -476,9 +476,8 @@ describe('ALL - Packs', () => { navigateTo('/app/osquery/packs'); }); - it('add global packs to polciies', () => { + it('add global packs to policies', () => { const globalPack = 'globalPack'; - cy.contains('Packs').click(); findAndClickButton('Add pack'); findFormFieldByRowsLabelAndType('Name', globalPack); cy.getBySel('policyIdsComboBox').should('exist'); @@ -517,9 +516,9 @@ describe('ALL - Packs', () => { cy.contains('rev. 2').click(); }); }); + it('add proper shard to policies packs config', () => { const shardPack = 'shardPack'; - cy.contains('Packs').click(); cy.getBySel('pagination-button-next').click(); findAndClickButton('Add pack'); diff --git a/x-pack/plugins/osquery/cypress/e2e/all/saved_queries.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/saved_queries.cy.ts index 9605984969c00..8af212661f741 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/saved_queries.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/saved_queries.cy.ts @@ -64,10 +64,12 @@ describe('ALL - Saved queries', () => { cy.contains('Snapshot'); }); }); + describe('prebuilt ', () => { before(() => { runKbnArchiverScript(ArchiverMethod.LOAD, 'pack_with_prebuilt_saved_queries'); }); + beforeEach(() => { navigateTo('/app/osquery/saved_queries'); }); @@ -77,7 +79,6 @@ describe('ALL - Saved queries', () => { }); it('checks result type on prebuilt saved query', () => { - cy.contains('Saved queries').click(); cy.react('CustomItemAction', { props: { index: 1, item: { attributes: { id: 'users_elastic' } } }, }).click(); @@ -85,6 +86,7 @@ describe('ALL - Saved queries', () => { cy.contains('Snapshot'); }); }); + it('user can run prebuilt saved query and add to case', () => { cy.react('PlayButtonComponent', { props: { savedQuery: { attributes: { id: 'users_elastic' } } }, @@ -98,7 +100,6 @@ describe('ALL - Saved queries', () => { }); it('user cant delete prebuilt saved query', () => { - cy.contains('Saved queries').click(); cy.react('CustomItemAction', { props: { index: 1, item: { attributes: { id: 'users_elastic' } } }, }).click(); @@ -114,6 +115,7 @@ describe('ALL - Saved queries', () => { }).click(); deleteAndConfirm('query'); }); + it('user can edit prebuilt saved query under pack', () => { const PACK_NAME = 'pack_with_prebuilt_sq'; preparePack(PACK_NAME); diff --git a/x-pack/plugins/osquery/cypress/screens/fleet.ts b/x-pack/plugins/osquery/cypress/screens/fleet.ts index b7cce6484c405..3f5370126f95f 100644 --- a/x-pack/plugins/osquery/cypress/screens/fleet.ts +++ b/x-pack/plugins/osquery/cypress/screens/fleet.ts @@ -10,3 +10,4 @@ export const ADD_AGENT_BUTTON = 'addAgentButton'; export const AGENT_POLICIES_TAB = 'fleet-agent-policies-tab'; export const ENROLLMENT_TOKENS_TAB = 'fleet-enrollment-tokens-tab'; export const DEFAULT_POLICY = 'Default Fleet Server policy'; +export const OSQUERY_POLICY = 'Osquery policy'; diff --git a/x-pack/plugins/osquery/cypress/tasks/live_query.ts b/x-pack/plugins/osquery/cypress/tasks/live_query.ts index 5e1c474da2d78..c37dbc7766bf4 100644 --- a/x-pack/plugins/osquery/cypress/tasks/live_query.ts +++ b/x-pack/plugins/osquery/cypress/tasks/live_query.ts @@ -17,7 +17,7 @@ export const selectAllAgents = () => { }).click(); cy.react('EuiFilterSelectItem').contains('All agents').should('exist'); cy.react('AgentsTable EuiComboBox').type('{downArrow}{enter}{esc}'); - cy.contains('1 agent selected.'); + cy.contains('2 agents selected.'); }; export const clearInputQuery = () => diff --git a/x-pack/plugins/osquery/cypress/tasks/navigation.ts b/x-pack/plugins/osquery/cypress/tasks/navigation.ts index 7b1505eecd698..9cbd0fac297a2 100644 --- a/x-pack/plugins/osquery/cypress/tasks/navigation.ts +++ b/x-pack/plugins/osquery/cypress/tasks/navigation.ts @@ -6,6 +6,7 @@ */ import { TOGGLE_NAVIGATION_BTN } from '../screens/navigation'; +import { closeToastIfVisible } from './integrations'; export const INTEGRATIONS = 'app/integrations#/'; export const FLEET = 'app/fleet/'; @@ -20,7 +21,7 @@ export const navigateTo = (page: string, opts?: Partial) = cy.contains('Loading Elastic').should('not.exist'); // There's a security warning toast that seemingly makes ui elements in the bottom right unavailable, so we close it - cy.get('[data-test-subj="toastCloseButton"]', { timeout: 30000 }).click(); + closeToastIfVisible(); cy.waitForReact(); }; diff --git a/x-pack/plugins/osquery/cypress/tasks/packs.ts b/x-pack/plugins/osquery/cypress/tasks/packs.ts index 6d74ac2110a0c..80f9a497ef6b4 100644 --- a/x-pack/plugins/osquery/cypress/tasks/packs.ts +++ b/x-pack/plugins/osquery/cypress/tasks/packs.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { closeModalIfVisible } from './integrations'; +import { closeModalIfVisible, closeToastIfVisible } from './integrations'; export const preparePack = (packName: string) => { cy.contains('Packs').click(); @@ -21,7 +21,7 @@ export const deactivatePack = (packName: string) => { cy.contains(`Successfully deactivated "${packName}" pack`).should('not.exist'); cy.contains(`Successfully deactivated "${packName}" pack`).should('exist'); - cy.getBySel('toastCloseButton').click(); + closeToastIfVisible(); }; export const activatePack = (packName: string) => { @@ -32,5 +32,5 @@ export const activatePack = (packName: string) => { cy.contains(`Successfully activated "${packName}" pack`).should('not.exist'); cy.contains(`Successfully activated "${packName}" pack`).should('exist'); - cy.getBySel('toastCloseButton').click(); + closeToastIfVisible(); }; diff --git a/x-pack/plugins/osquery/cypress/tasks/saved_queries.ts b/x-pack/plugins/osquery/cypress/tasks/saved_queries.ts index 7c70d8cdb3a2b..780880b838caf 100644 --- a/x-pack/plugins/osquery/cypress/tasks/saved_queries.ts +++ b/x-pack/plugins/osquery/cypress/tasks/saved_queries.ts @@ -6,6 +6,7 @@ */ import { RESULTS_TABLE_BUTTON } from '../screens/live_query'; +import { closeToastIfVisible } from './integrations'; import { checkResults, BIG_QUERY, @@ -15,6 +16,7 @@ import { selectAllAgents, submitQuery, } from './live_query'; +import { navigateTo } from './navigation'; export const getSavedQueriesComplexTest = (savedQueryId: string, savedQueryDescription: string) => it( @@ -71,7 +73,7 @@ export const getSavedQueriesComplexTest = (savedQueryId: string, savedQueryDescr // visit Status results cy.react('EuiTab', { props: { id: 'status' } }).click(); - cy.react('EuiTableRow').should('have.lengthOf', 1); + cy.react('EuiTableRow').should('have.lengthOf', 2); // save new query cy.contains('Exit full screen').should('not.exist'); @@ -81,10 +83,10 @@ export const getSavedQueriesComplexTest = (savedQueryId: string, savedQueryDescr findFormFieldByRowsLabelAndType('Description (optional)', savedQueryDescription); cy.react('EuiButtonDisplay').contains('Save').click(); cy.contains('Successfully saved'); - cy.getBySel('toastCloseButton').click(); + closeToastIfVisible(); // play saved query - cy.contains('Saved queries').click(); + navigateTo('/app/osquery/saved_queries'); cy.contains(savedQueryId); cy.react('PlayButtonComponent', { props: { savedQuery: { attributes: { id: savedQueryId } } }, diff --git a/x-pack/plugins/osquery/server/handlers/action/create_action_handler.ts b/x-pack/plugins/osquery/server/handlers/action/create_action_handler.ts index 4974fc21b440b..b2f6ca09234eb 100644 --- a/x-pack/plugins/osquery/server/handlers/action/create_action_handler.ts +++ b/x-pack/plugins/osquery/server/handlers/action/create_action_handler.ts @@ -11,11 +11,7 @@ import { filter, flatten, isEmpty, map, omit, pick, pickBy, some } from 'lodash' import { AGENT_ACTIONS_INDEX } from '@kbn/fleet-plugin/common'; import type { SavedObjectsClientContract } from '@kbn/core/server'; import type { ParsedTechnicalFields } from '@kbn/rule-registry-plugin/common'; -import { - containsDynamicQuery, - replaceParamsQuery, -} from '../../../common/utils/replace_params_query'; -import { createDynamicQueries, createQueries } from './create_queries'; +import { createDynamicQueries, replacedQueries } from './create_queries'; import { getInternalSavedObjectsClient } from '../../routes/utils'; import { parseAgentSelection } from '../../lib/parse_agent_groups'; import { packSavedObjectType } from '../../../common/types'; @@ -94,16 +90,13 @@ export const createActionHandler = async ( : undefined, queries: packSO ? map(convertSOQueriesToPack(packSO.attributes.queries), (packQuery, packQueryId) => { - const dynamicQueryPresent = packQuery.query && containsDynamicQuery(packQuery.query); + const replacedQuery = replacedQueries(packQuery.query, alertData); return pickBy( { action_id: uuidv4(), id: packQueryId, - query: - dynamicQueryPresent && alertData - ? replaceParamsQuery(packQuery.query, alertData).result - : packQuery.query, + ...replacedQuery, ecs_mapping: packQuery.ecs_mapping, version: packQuery.version, platform: packQuery.platform, @@ -112,9 +105,7 @@ export const createActionHandler = async ( (value) => !isEmpty(value) ); }) - : alertData - ? await createDynamicQueries(params, alertData, osqueryContext) - : await createQueries(params, selectedAgents, osqueryContext), + : await createDynamicQueries({ params, alertData, agents: selectedAgents, osqueryContext }), }; const fleetActions = map( @@ -130,31 +121,33 @@ export const createActionHandler = async ( data: pick(query, ['id', 'query', 'ecs_mapping', 'version', 'platform']), }) ); + if (fleetActions.length) { + await esClientInternal.bulk({ + refresh: 'wait_for', + body: flatten( + fleetActions.map((action) => [{ index: { _index: AGENT_ACTIONS_INDEX } }, action]) + ), + }); - await esClientInternal.bulk({ - refresh: 'wait_for', - body: flatten( - fleetActions.map((action) => [{ index: { _index: AGENT_ACTIONS_INDEX } }, action]) - ), - }); + const actionsComponentTemplateExists = await esClientInternal.indices.exists({ + index: `${ACTIONS_INDEX}*`, + }); - const actionsComponentTemplateExists = await esClientInternal.indices.exists({ - index: `${ACTIONS_INDEX}*`, - }); + if (actionsComponentTemplateExists) { + await esClientInternal.bulk({ + refresh: 'wait_for', + body: [{ index: { _index: `${ACTIONS_INDEX}-default` } }, osqueryAction], + }); + } - if (actionsComponentTemplateExists) { - await esClientInternal.bulk({ - refresh: 'wait_for', - body: [{ index: { _index: `${ACTIONS_INDEX}-default` } }, osqueryAction], + osqueryContext.telemetryEventsSender.reportEvent(TELEMETRY_EBT_LIVE_QUERY_EVENT, { + ...omit(osqueryAction, ['type', 'input_type', 'user_id']), + agents: osqueryAction.agents.length, }); } - osqueryContext.telemetryEventsSender.reportEvent(TELEMETRY_EBT_LIVE_QUERY_EVENT, { - ...omit(osqueryAction, ['type', 'input_type', 'user_id']), - agents: osqueryAction.agents.length, - }); - return { response: osqueryAction, + fleetActionsCount: fleetActions.length, }; }; diff --git a/x-pack/plugins/osquery/server/handlers/action/create_queries.test.ts b/x-pack/plugins/osquery/server/handlers/action/create_queries.test.ts index 3b88e710fc52b..4956dfd1245bc 100644 --- a/x-pack/plugins/osquery/server/handlers/action/create_queries.test.ts +++ b/x-pack/plugins/osquery/server/handlers/action/create_queries.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { createDynamicQueries, createQueries } from './create_queries'; +import { createDynamicQueries, PARAMETER_NOT_FOUND } from './create_queries'; import type { ParsedTechnicalFields } from '@kbn/rule-registry-plugin/common'; import type { OsqueryAppContext } from '../../lib/osquery_app_context_services'; @@ -18,6 +18,8 @@ describe('create queries', () => { removed: false, snapshot: true, }; + const TEST_AGENT = 'test-agent'; + const mockedQueriesParams = { queries: [ { @@ -49,69 +51,74 @@ describe('create queries', () => { // Info: getting queries by index (eg. [1], [0]) because can't compare whole query object due to unique action_id generated. describe('dynamic', () => { const pid = 123; - it('if queries length it should return replaced list of queries', async () => { - const queries = await createDynamicQueries( - mockedQueriesParams, - { + it('alertData, multi queries, should replace queries and show errors', async () => { + const queries = await createDynamicQueries({ + params: mockedQueriesParams, + agents: [TEST_AGENT], + alertData: { process: { pid, }, } as unknown as ParsedTechnicalFields, - {} as OsqueryAppContext - ); + osqueryContext: {} as OsqueryAppContext, + }); expect(queries[0].query).toBe(`SELECT * FROM processes where pid=${pid};`); + expect(queries[0].error).toBe(undefined); + expect(queries[1].query).toBe('SELECT * FROM processes where pid={{process.not-existing}};'); expect(queries[1].error).toBe( "This query hasn't been called due to parameter used and its value not found in the alert." ); - expect(queries[1].query).toBe('SELECT * FROM processes where pid={{process.not-existing}};'); expect(queries[2].query).toBe('SELECT * FROM processes;'); + expect(queries[2].error).toBe(undefined); }); - it('if single query it should return one replaced query ', async () => { - const queries = await createDynamicQueries( - mockedSingleQueryParams, - { - process: { - pid, - }, + + it('alertData, single query, existing param should return changed query', async () => { + const queries = await createDynamicQueries({ + params: mockedSingleQueryParams, + agents: [TEST_AGENT], + alertData: { + process: { pid }, } as unknown as ParsedTechnicalFields, - {} as OsqueryAppContext - ); + osqueryContext: {} as OsqueryAppContext, + }); expect(queries[0].query).toBe(`SELECT * FROM processes where pid=${pid};`); + expect(queries[0].error).toBe(undefined); }); - it('if single query with not existing parameter it should return query as it is', async () => { - const queries = await createDynamicQueries( - mockedSingleQueryParams, - { + it('alertData, single query, not existing param should return error', async () => { + const queries = await createDynamicQueries({ + params: mockedSingleQueryParams, + agents: [TEST_AGENT], + alertData: { process: {}, } as unknown as ParsedTechnicalFields, - {} as OsqueryAppContext - ); + osqueryContext: {} as OsqueryAppContext, + }); expect(queries[0].query).toBe('SELECT * FROM processes where pid={{process.pid}};'); - expect(queries[0].error).toBe(undefined); + expect(queries[0].error).toBe(PARAMETER_NOT_FOUND); }); - }); - describe('normal', () => { - const TEST_AGENT = 'test-agent'; - it('if queries length it should return not replaced list of queries with agents', async () => { - const queries = await createQueries( - mockedQueriesParams, - [TEST_AGENT], - {} as OsqueryAppContext - ); + it('no alert data, multi query, return unchanged queries no error', async () => { + const queries = await createDynamicQueries({ + params: mockedQueriesParams, + agents: [TEST_AGENT], + osqueryContext: {} as OsqueryAppContext, + }); expect(queries[0].query).toBe('SELECT * FROM processes where pid={{process.pid}};'); expect(queries[0].agents).toContain(TEST_AGENT); + expect(queries[0].error).toBe(undefined); expect(queries[2].query).toBe('SELECT * FROM processes;'); expect(queries[2].agents).toContain(TEST_AGENT); + expect(queries[2].error).toBe(undefined); }); - it('if single query should return not replaced query with agents', async () => { - const queries = await createQueries( - mockedSingleQueryParams, - [TEST_AGENT], - {} as OsqueryAppContext - ); + it('no alert data, single query, return unchanged query and no error', async () => { + const queries = await createDynamicQueries({ + params: mockedSingleQueryParams, + agents: [TEST_AGENT], + osqueryContext: {} as OsqueryAppContext, + }); expect(queries[0].query).toBe('SELECT * FROM processes where pid={{process.pid}};'); expect(queries[0].agents).toContain(TEST_AGENT); + expect(queries[0].error).toBe(undefined); }); }); }); diff --git a/x-pack/plugins/osquery/server/handlers/action/create_queries.ts b/x-pack/plugins/osquery/server/handlers/action/create_queries.ts index 6551b8252e16b..f7d3601722189 100644 --- a/x-pack/plugins/osquery/server/handlers/action/create_queries.ts +++ b/x-pack/plugins/osquery/server/handlers/action/create_queries.ts @@ -15,47 +15,26 @@ import type { CreateLiveQueryRequestBodySchema } from '../../../common/schemas/r import { replaceParamsQuery } from '../../../common/utils/replace_params_query'; import { isSavedQueryPrebuilt } from '../../routes/saved_query/utils'; -export const createQueries = async ( - params: CreateLiveQueryRequestBodySchema, - agents: string[], - osqueryContext: OsqueryAppContext -) => - params.queries?.length - ? map(params.queries, (query) => - pickBy( - { - ...query, - action_id: uuidv4(), - agents, - }, - (value) => !isEmpty(value) || value === true - ) - ) - : [ - pickBy( - { - action_id: uuidv4(), - id: uuidv4(), - query: params.query, - saved_query_id: params.saved_query_id, - saved_query_prebuilt: params.saved_query_id - ? await isSavedQueryPrebuilt( - osqueryContext.service.getPackageService()?.asInternalUser, - params.saved_query_id - ) - : undefined, - ecs_mapping: params.ecs_mapping, - agents, - }, - (value) => !isEmpty(value) - ), - ]; +export const PARAMETER_NOT_FOUND = i18n.translate( + 'xpack.osquery.liveQueryActions.error.notFoundParameters', + { + defaultMessage: + "This query hasn't been called due to parameter used and its value not found in the alert.", + } +); -export const createDynamicQueries = async ( - params: CreateLiveQueryRequestBodySchema, - alertData: ParsedTechnicalFields, - osqueryContext: OsqueryAppContext -) => +interface CreateDynamicQueriesParams { + params: CreateLiveQueryRequestBodySchema; + alertData?: ParsedTechnicalFields; + agents: string[]; + osqueryContext: OsqueryAppContext; +} +export const createDynamicQueries = async ({ + params, + alertData, + agents, + osqueryContext, +}: CreateDynamicQueriesParams) => params.queries?.length ? map(params.queries, ({ query, ...restQuery }) => { const replacedQuery = replacedQueries(query, alertData); @@ -66,7 +45,7 @@ export const createDynamicQueries = async ( ...restQuery, action_id: uuidv4(), alert_ids: params.alert_ids, - agents: params.agent_ids, + agents, }, (value) => !isEmpty(value) || value === true ); @@ -77,8 +56,6 @@ export const createDynamicQueries = async ( action_id: uuidv4(), id: uuidv4(), ...replacedQueries(params.query, alertData), - // just for single queries - we need to overwrite the error property - error: undefined, saved_query_id: params.saved_query_id, saved_query_prebuilt: params.saved_query_id ? await isSavedQueryPrebuilt( @@ -88,13 +65,13 @@ export const createDynamicQueries = async ( : undefined, ecs_mapping: params.ecs_mapping, alert_ids: params.alert_ids, - agents: params.agent_ids, + agents, }, (value) => !isEmpty(value) ), ]; -const replacedQueries = ( +export const replacedQueries = ( query: string | undefined, alertData?: ParsedTechnicalFields ): { query: string | undefined; error?: string } => { @@ -105,10 +82,7 @@ const replacedQueries = ( query: result, ...(skipped ? { - error: i18n.translate('xpack.osquery.liveQueryActions.error.notFoundParameters', { - defaultMessage: - "This query hasn't been called due to parameter used and its value not found in the alert.", - }), + error: PARAMETER_NOT_FOUND, } : {}), }; diff --git a/x-pack/plugins/osquery/server/routes/live_query/create_live_query_route.ts b/x-pack/plugins/osquery/server/routes/live_query/create_live_query_route.ts index 92ebddf7642e7..9d7ad88da88b6 100644 --- a/x-pack/plugins/osquery/server/routes/live_query/create_live_query_route.ts +++ b/x-pack/plugins/osquery/server/routes/live_query/create_live_query_route.ts @@ -11,6 +11,7 @@ import markdown from 'remark-parse-no-trim'; import { some, filter } from 'lodash'; import deepEqual from 'fast-deep-equal'; import type { ECSMappingOrUndefined } from '@kbn/osquery-io-ts-types'; +import { PARAMETER_NOT_FOUND } from '../../handlers/action/create_queries'; import { replaceParamsQuery } from '../../../common/utils/replace_params_query'; import { createLiveQueryRequestBodySchema } from '../../../common/schemas/routes/live_query'; import type { CreateLiveQueryRequestBodySchema } from '../../../common/schemas/routes/live_query'; @@ -93,7 +94,7 @@ export const createLiveQueryRoute = (router: IRouter, osqueryContext: OsqueryApp try { const currentUser = await osqueryContext.security.authc.getCurrentUser(request)?.username; - const { response: osqueryAction } = await createActionHandler( + const { response: osqueryAction, fleetActionsCount } = await createActionHandler( osqueryContext, request.body, { @@ -102,6 +103,11 @@ export const createLiveQueryRoute = (router: IRouter, osqueryContext: OsqueryApp alertData, } ); + if (!fleetActionsCount) { + return response.badRequest({ + body: PARAMETER_NOT_FOUND, + }); + } return response.ok({ body: { data: osqueryAction }, diff --git a/x-pack/plugins/profiling/public/app.tsx b/x-pack/plugins/profiling/public/app.tsx index bf3a35d060f05..bf2f53aed0b0b 100644 --- a/x-pack/plugins/profiling/public/app.tsx +++ b/x-pack/plugins/profiling/public/app.tsx @@ -22,6 +22,7 @@ import { profilingRouter } from './routing'; import { Services } from './services'; import { ProfilingPluginPublicSetupDeps, ProfilingPluginPublicStartDeps } from './types'; import { ProfilingHeaderActionMenu } from './components/profiling_header_action_menu'; +import { RouterErrorBoundary } from './routing/router_error_boundary'; interface Props { profilingFetchServices: Services; @@ -82,21 +83,23 @@ function App({ - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/x-pack/plugins/profiling/public/routing/router_error_boundary.tsx b/x-pack/plugins/profiling/public/routing/router_error_boundary.tsx new file mode 100644 index 0000000000000..355ee1bd42f79 --- /dev/null +++ b/x-pack/plugins/profiling/public/routing/router_error_boundary.tsx @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { NotFoundRouteException } from '@kbn/typed-react-router-config'; +import { EuiErrorBoundary } from '@elastic/eui'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import React from 'react'; +import { NotFoundPrompt } from '@kbn/shared-ux-prompt-not-found'; +import { useLocation } from 'react-router-dom'; +import { i18n } from '@kbn/i18n'; +import { ProfilingPluginPublicStartDeps } from '../types'; + +export function RouterErrorBoundary({ children }: { children?: React.ReactNode }) { + const location = useLocation(); + return {children}; +} + +class ErrorBoundary extends React.Component<{ children?: React.ReactNode }, { error?: Error }, {}> { + public state: { error?: Error } = { + error: undefined, + }; + + static getDerivedStateFromError(error: Error) { + return { error }; + } + + render() { + if (this.state.error) { + return ; + } + + return this.props.children; + } +} + +const pageHeader = { + pageTitle: i18n.translate('xpack.profiling.universalProfiling', { + defaultMessage: 'Universal Profiling', + }), +}; + +function ErrorWithTemplate({ error }: { error: Error }) { + const { services } = useKibana(); + const { observability } = services; + + const ObservabilityPageTemplate = observability.navigation.PageTemplate; + + if (error instanceof NotFoundRouteException) { + return ( + + + + ); + } + + return ( + + + + + + ); +} + +function DummyComponent({ error }: { error: Error }) { + throw error; + return
      ; +} diff --git a/x-pack/plugins/profiling/tsconfig.json b/x-pack/plugins/profiling/tsconfig.json index b1044792b3209..6364139982415 100644 --- a/x-pack/plugins/profiling/tsconfig.json +++ b/x-pack/plugins/profiling/tsconfig.json @@ -40,6 +40,7 @@ "@kbn/core-http-request-handler-context-server", "@kbn/spaces-plugin", "@kbn/cloud-plugin", + "@kbn/shared-ux-prompt-not-found", // add references to other TypeScript projects the plugin depends on // requiredPlugins from ./kibana.json diff --git a/x-pack/plugins/rule_registry/common/assets.ts b/x-pack/plugins/rule_registry/common/assets.ts index a1df09df18a8f..1e8919a3a07e4 100644 --- a/x-pack/plugins/rule_registry/common/assets.ts +++ b/x-pack/plugins/rule_registry/common/assets.ts @@ -5,5 +5,4 @@ * 2.0. */ -export const TECHNICAL_COMPONENT_TEMPLATE_NAME = `technical-mappings`; -export const ECS_COMPONENT_TEMPLATE_NAME = `ecs-mappings`; +export const TECHNICAL_COMPONENT_TEMPLATE_NAME = `.alerts-technical-mappings`; diff --git a/x-pack/plugins/rule_registry/common/assets/component_templates/ecs_component_template.ts b/x-pack/plugins/rule_registry/common/assets/component_templates/ecs_component_template.ts index 8e956ba0004a2..8f30e07a0d9dc 100644 --- a/x-pack/plugins/rule_registry/common/assets/component_templates/ecs_component_template.ts +++ b/x-pack/plugins/rule_registry/common/assets/component_templates/ecs_component_template.ts @@ -5,7 +5,7 @@ * 2.0. */ import { merge } from 'lodash'; -import { mappingFromFieldMap } from '../../mapping_from_field_map'; +import { mappingFromFieldMap } from '@kbn/alerting-plugin/common'; import { ClusterPutComponentTemplateBody } from '../../types'; import { ecsFieldMap } from '../field_maps/ecs_field_map'; import { technicalRuleFieldMap } from '../field_maps/technical_rule_field_map'; diff --git a/x-pack/plugins/rule_registry/common/assets/component_templates/technical_component_template.ts b/x-pack/plugins/rule_registry/common/assets/component_templates/technical_component_template.ts index e110be339d0a0..1315d7f0d1b58 100644 --- a/x-pack/plugins/rule_registry/common/assets/component_templates/technical_component_template.ts +++ b/x-pack/plugins/rule_registry/common/assets/component_templates/technical_component_template.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { mappingFromFieldMap } from '../../mapping_from_field_map'; +import { mappingFromFieldMap } from '@kbn/alerting-plugin/common'; import { ClusterPutComponentTemplateBody } from '../../types'; import { technicalRuleFieldMap } from '../field_maps/technical_rule_field_map'; diff --git a/x-pack/plugins/rule_registry/common/assets/field_maps/experimental_rule_field_map.test.ts b/x-pack/plugins/rule_registry/common/assets/field_maps/experimental_rule_field_map.test.ts index 4e2d591bf88bd..3a6dbc4f20982 100644 --- a/x-pack/plugins/rule_registry/common/assets/field_maps/experimental_rule_field_map.test.ts +++ b/x-pack/plugins/rule_registry/common/assets/field_maps/experimental_rule_field_map.test.ts @@ -13,10 +13,12 @@ it('matches snapshot', () => { expect(experimentalRuleFieldMap).toMatchInlineSnapshot(` Object { "kibana.alert.evaluation.threshold": Object { + "required": false, "scaling_factor": 100, "type": "scaled_float", }, "kibana.alert.evaluation.value": Object { + "required": false, "scaling_factor": 100, "type": "scaled_float", }, diff --git a/x-pack/plugins/rule_registry/common/assets/field_maps/experimental_rule_field_map.ts b/x-pack/plugins/rule_registry/common/assets/field_maps/experimental_rule_field_map.ts index 92f93015309c0..3859ebe6df9b6 100644 --- a/x-pack/plugins/rule_registry/common/assets/field_maps/experimental_rule_field_map.ts +++ b/x-pack/plugins/rule_registry/common/assets/field_maps/experimental_rule_field_map.ts @@ -8,8 +8,12 @@ import * as Fields from '../../technical_rule_data_field_names'; export const experimentalRuleFieldMap = { - [Fields.ALERT_EVALUATION_THRESHOLD]: { type: 'scaled_float', scaling_factor: 100 }, - [Fields.ALERT_EVALUATION_VALUE]: { type: 'scaled_float', scaling_factor: 100 }, + [Fields.ALERT_EVALUATION_THRESHOLD]: { + type: 'scaled_float', + scaling_factor: 100, + required: false, + }, + [Fields.ALERT_EVALUATION_VALUE]: { type: 'scaled_float', scaling_factor: 100, required: false }, } as const; export type ExperimentalRuleFieldMap = typeof experimentalRuleFieldMap; diff --git a/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.test.ts b/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.test.ts index e7d39a71a1d6d..7c2cc7c2a02af 100644 --- a/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.test.ts +++ b/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.test.ts @@ -43,15 +43,27 @@ it('matches snapshot', () => { "type": "keyword", }, "kibana.alert.duration.us": Object { + "array": false, + "required": false, "type": "long", }, "kibana.alert.end": Object { + "array": false, + "required": false, "type": "date", }, "kibana.alert.flapping": Object { + "array": false, + "required": false, + "type": "boolean", + }, + "kibana.alert.flapping_history": Object { + "array": true, + "required": false, "type": "boolean", }, "kibana.alert.instance.id": Object { + "array": false, "required": true, "type": "keyword", }, @@ -81,6 +93,7 @@ it('matches snapshot', () => { "type": "keyword", }, "kibana.alert.rule.consumer": Object { + "array": false, "required": true, "type": "keyword", }, @@ -135,10 +148,13 @@ it('matches snapshot', () => { "type": "keyword", }, "kibana.alert.rule.parameters": Object { + "array": false, "ignore_above": 4096, + "required": false, "type": "flattened", }, "kibana.alert.rule.producer": Object { + "array": false, "required": true, "type": "keyword", }, @@ -158,6 +174,7 @@ it('matches snapshot', () => { "type": "keyword", }, "kibana.alert.rule.rule_type_id": Object { + "array": false, "required": true, "type": "keyword", }, @@ -197,12 +214,17 @@ it('matches snapshot', () => { "type": "keyword", }, "kibana.alert.severity": Object { + "array": false, + "required": false, "type": "keyword", }, "kibana.alert.start": Object { + "array": false, + "required": false, "type": "date", }, "kibana.alert.status": Object { + "array": false, "required": true, "type": "keyword", }, @@ -237,10 +259,13 @@ it('matches snapshot', () => { "type": "keyword", }, "kibana.alert.time_range": Object { + "array": false, "format": "epoch_millis||strict_date_optional_time", + "required": false, "type": "date_range", }, "kibana.alert.uuid": Object { + "array": false, "required": true, "type": "keyword", }, diff --git a/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.ts b/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.ts index 32d1b45e44cad..ef476f468544b 100644 --- a/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.ts +++ b/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.ts @@ -5,225 +5,12 @@ * 2.0. */ +import { alertFieldMap, legacyAlertFieldMap } from '@kbn/alerts-as-data-utils'; import { pickWithPatterns } from '../../pick_with_patterns'; -import * as Fields from '../../technical_rule_data_field_names'; -import { ecsFieldMap } from './ecs_field_map'; export const technicalRuleFieldMap = { - ...pickWithPatterns( - ecsFieldMap, - Fields.TIMESTAMP, - Fields.EVENT_KIND, - Fields.EVENT_ACTION, - Fields.TAGS - ), - [Fields.ALERT_RULE_PARAMETERS]: { type: 'flattened', ignore_above: 4096 }, - [Fields.ALERT_RULE_TYPE_ID]: { type: 'keyword', required: true }, - [Fields.ALERT_RULE_CONSUMER]: { type: 'keyword', required: true }, - [Fields.ALERT_RULE_PRODUCER]: { type: 'keyword', required: true }, - [Fields.SPACE_IDS]: { type: 'keyword', array: true, required: true }, - [Fields.ALERT_UUID]: { type: 'keyword', required: true }, - [Fields.ALERT_INSTANCE_ID]: { type: 'keyword', required: true }, - [Fields.ALERT_START]: { type: 'date' }, - [Fields.ALERT_TIME_RANGE]: { - type: 'date_range', - format: 'epoch_millis||strict_date_optional_time', - }, - [Fields.ALERT_END]: { type: 'date' }, - [Fields.ALERT_DURATION]: { type: 'long' }, - [Fields.ALERT_SEVERITY]: { type: 'keyword' }, - [Fields.ALERT_STATUS]: { type: 'keyword', required: true }, - [Fields.ALERT_FLAPPING]: { type: 'boolean' }, - [Fields.VERSION]: { - type: 'version', - array: false, - required: false, - }, - [Fields.ECS_VERSION]: { - type: 'keyword', - array: false, - required: false, - }, - [Fields.ALERT_RISK_SCORE]: { - type: 'float', - array: false, - required: false, - }, - [Fields.ALERT_WORKFLOW_STATUS]: { - type: 'keyword', - array: false, - required: false, - }, - [Fields.ALERT_WORKFLOW_USER]: { - type: 'keyword', - array: false, - required: false, - }, - [Fields.ALERT_WORKFLOW_REASON]: { - type: 'keyword', - array: false, - required: false, - }, - [Fields.ALERT_SYSTEM_STATUS]: { - type: 'keyword', - array: false, - required: false, - }, - [Fields.ALERT_ACTION_GROUP]: { - type: 'keyword', - array: false, - required: false, - }, - [Fields.ALERT_REASON]: { - type: 'keyword', - array: false, - required: false, - }, - [Fields.ALERT_CASE_IDS]: { - type: 'keyword', - array: true, - required: false, - }, - [Fields.ALERT_RULE_AUTHOR]: { - type: 'keyword', - array: false, - required: false, - }, - [Fields.ALERT_RULE_CATEGORY]: { - type: 'keyword', - array: false, - required: true, - }, - [Fields.ALERT_RULE_UUID]: { - type: 'keyword', - array: false, - required: true, - }, - [Fields.ALERT_RULE_CREATED_AT]: { - type: 'date', - array: false, - required: false, - }, - [Fields.ALERT_RULE_CREATED_BY]: { - type: 'keyword', - array: false, - required: false, - }, - [Fields.ALERT_RULE_DESCRIPTION]: { - type: 'keyword', - array: false, - required: false, - }, - [Fields.ALERT_RULE_ENABLED]: { - type: 'keyword', - array: false, - required: false, - }, - [Fields.ALERT_RULE_EXECUTION_UUID]: { - type: 'keyword', - array: false, - required: false, - }, - [Fields.ALERT_RULE_FROM]: { - type: 'keyword', - array: false, - required: false, - }, - [Fields.ALERT_RULE_INTERVAL]: { - type: 'keyword', - array: false, - required: false, - }, - [Fields.ALERT_RULE_LICENSE]: { - type: 'keyword', - array: false, - required: false, - }, - [Fields.ALERT_RULE_NAME]: { - type: 'keyword', - array: false, - required: true, - }, - [Fields.ALERT_RULE_NOTE]: { - type: 'keyword', - array: false, - required: false, - }, - [Fields.ALERT_RULE_REFERENCES]: { - type: 'keyword', - array: true, - required: false, - }, - [Fields.ALERT_RULE_RULE_ID]: { - type: 'keyword', - array: false, - required: false, - }, - [Fields.ALERT_RULE_RULE_NAME_OVERRIDE]: { - type: 'keyword', - array: false, - required: false, - }, - [Fields.ALERT_RULE_TAGS]: { - type: 'keyword', - array: true, - required: false, - }, - [Fields.ALERT_RULE_TO]: { - type: 'keyword', - array: false, - required: false, - }, - [Fields.ALERT_RULE_TYPE]: { - type: 'keyword', - array: false, - required: false, - }, - [Fields.ALERT_RULE_UPDATED_AT]: { - type: 'date', - array: false, - required: false, - }, - [Fields.ALERT_RULE_UPDATED_BY]: { - type: 'keyword', - array: false, - required: false, - }, - [Fields.ALERT_RULE_VERSION]: { - type: 'keyword', - array: false, - required: false, - }, - [Fields.ALERT_SUPPRESSION_FIELD]: { - type: 'keyword', - array: true, - required: false, - }, - [Fields.ALERT_SUPPRESSION_VALUE]: { - type: 'keyword', - array: true, - required: false, - }, - [Fields.ALERT_SUPPRESSION_START]: { - type: 'date', - array: false, - required: false, - }, - [Fields.ALERT_SUPPRESSION_END]: { - type: 'date', - array: false, - required: false, - }, - [Fields.ALERT_SUPPRESSION_DOCS_COUNT]: { - type: 'long', - array: false, - required: false, - }, - [Fields.ALERT_LAST_DETECTED]: { - type: 'date', - array: false, - required: false, - }, + ...pickWithPatterns(alertFieldMap, '*'), + ...pickWithPatterns(legacyAlertFieldMap, '*'), } as const; export type TechnicalRuleFieldMap = typeof technicalRuleFieldMap; diff --git a/x-pack/plugins/rule_registry/common/field_map/index.ts b/x-pack/plugins/rule_registry/common/field_map/index.ts index fac8575b8af48..e64ba5823e673 100644 --- a/x-pack/plugins/rule_registry/common/field_map/index.ts +++ b/x-pack/plugins/rule_registry/common/field_map/index.ts @@ -7,4 +7,3 @@ export * from './merge_field_maps'; export * from './runtime_type_from_fieldmap'; -export * from './types'; diff --git a/x-pack/plugins/rule_registry/common/field_map/merge_field_maps.ts b/x-pack/plugins/rule_registry/common/field_map/merge_field_maps.ts index 124de243352ea..701bab82855d4 100644 --- a/x-pack/plugins/rule_registry/common/field_map/merge_field_maps.ts +++ b/x-pack/plugins/rule_registry/common/field_map/merge_field_maps.ts @@ -4,7 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { FieldMap } from './types'; + +import type { FieldMap } from '@kbn/alerts-as-data-utils'; export function mergeFieldMaps( first: T1, diff --git a/x-pack/plugins/rule_registry/common/field_map/runtime_type_from_fieldmap.test.ts b/x-pack/plugins/rule_registry/common/field_map/runtime_type_from_fieldmap.test.ts index 8ee71356ef706..0b724150f0dcc 100644 --- a/x-pack/plugins/rule_registry/common/field_map/runtime_type_from_fieldmap.test.ts +++ b/x-pack/plugins/rule_registry/common/field_map/runtime_type_from_fieldmap.test.ts @@ -8,11 +8,11 @@ import { runtimeTypeFromFieldMap } from './runtime_type_from_fieldmap'; describe('runtimeTypeFromFieldMap', () => { const fieldmapRt = runtimeTypeFromFieldMap({ - keywordField: { type: 'keyword' }, - longField: { type: 'long' }, - booleanField: { type: 'boolean' }, + keywordField: { type: 'keyword', required: false }, + longField: { type: 'long', required: false }, + booleanField: { type: 'boolean', required: false }, requiredKeywordField: { type: 'keyword', required: true }, - multiKeywordField: { type: 'keyword', array: true }, + multiKeywordField: { type: 'keyword', array: true, required: false }, } as const); it('accepts both singular and array fields', () => { diff --git a/x-pack/plugins/rule_registry/common/field_map/runtime_type_from_fieldmap.ts b/x-pack/plugins/rule_registry/common/field_map/runtime_type_from_fieldmap.ts index feb59f88abc7b..93e182e53af63 100644 --- a/x-pack/plugins/rule_registry/common/field_map/runtime_type_from_fieldmap.ts +++ b/x-pack/plugins/rule_registry/common/field_map/runtime_type_from_fieldmap.ts @@ -8,7 +8,7 @@ import { Optional } from 'utility-types'; import { mapValues, pickBy } from 'lodash'; import { either } from 'fp-ts/lib/Either'; import * as t from 'io-ts'; -import { FieldMap } from './types'; +import type { FieldMap } from '@kbn/alerts-as-data-utils'; const NumberFromString = new t.Type( 'NumberFromString', diff --git a/x-pack/plugins/rule_registry/common/mapping_from_field_map.ts b/x-pack/plugins/rule_registry/common/mapping_from_field_map.ts deleted file mode 100644 index 1b66496bee19b..0000000000000 --- a/x-pack/plugins/rule_registry/common/mapping_from_field_map.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { set } from '@kbn/safer-lodash-set'; -import { FieldMap } from './field_map/types'; - -export function mappingFromFieldMap( - fieldMap: FieldMap, - dynamic: 'strict' | boolean -): estypes.MappingTypeMapping { - const mappings = { - dynamic, - properties: {}, - }; - - const fields = Object.keys(fieldMap).map((key) => { - const field = fieldMap[key]; - return { - name: key, - ...field, - }; - }); - - fields.forEach((field) => { - const { name, required, array, ...rest } = field; - - set(mappings.properties, field.name.split('.').join('.properties.'), rest); - }); - - return mappings; -} diff --git a/x-pack/plugins/rule_registry/server/rule_data_plugin_service/resource_installer.test.ts b/x-pack/plugins/rule_registry/server/rule_data_plugin_service/resource_installer.test.ts index b63fb2aae83d0..083c4d08d4253 100644 --- a/x-pack/plugins/rule_registry/server/rule_data_plugin_service/resource_installer.test.ts +++ b/x-pack/plugins/rule_registry/server/rule_data_plugin_service/resource_installer.test.ts @@ -12,11 +12,9 @@ import { AlertConsumers } from '@kbn/rule-data-utils'; import { Dataset } from './index_options'; import { IndexInfo } from './index_info'; +import { ECS_COMPONENT_TEMPLATE_NAME } from '@kbn/alerting-plugin/server'; import { elasticsearchServiceMock, ElasticsearchClientMock } from '@kbn/core/server/mocks'; -import { - ECS_COMPONENT_TEMPLATE_NAME, - TECHNICAL_COMPONENT_TEMPLATE_NAME, -} from '../../common/assets'; +import { TECHNICAL_COMPONENT_TEMPLATE_NAME } from '../../common/assets'; describe('resourceInstaller', () => { let pluginStop$: Subject; @@ -82,15 +80,11 @@ describe('resourceInstaller', () => { it('should install common resources', async () => { const mockClusterClient = elasticsearchServiceMock.createElasticsearchClient(); const getClusterClient = jest.fn(() => Promise.resolve(mockClusterClient)); - const getResourceNameMock = jest - .fn() - .mockReturnValueOnce(TECHNICAL_COMPONENT_TEMPLATE_NAME) - .mockReturnValueOnce(ECS_COMPONENT_TEMPLATE_NAME); const installer = new ResourceInstaller({ logger: loggerMock.create(), isWriteEnabled: true, disabledRegistrationContexts: [], - getResourceName: getResourceNameMock, + getResourceName: jest.fn(), getClusterClient, areFrameworkAlertsEnabled: false, pluginStop$, @@ -102,26 +96,22 @@ describe('resourceInstaller', () => { expect(mockClusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(2); expect(mockClusterClient.cluster.putComponentTemplate).toHaveBeenNthCalledWith( 1, - expect.objectContaining({ name: TECHNICAL_COMPONENT_TEMPLATE_NAME }) + expect.objectContaining({ name: ECS_COMPONENT_TEMPLATE_NAME }) ); expect(mockClusterClient.cluster.putComponentTemplate).toHaveBeenNthCalledWith( 2, - expect.objectContaining({ name: ECS_COMPONENT_TEMPLATE_NAME }) + expect.objectContaining({ name: TECHNICAL_COMPONENT_TEMPLATE_NAME }) ); }); - it('should install common resources when framework alerts are enabled', async () => { + it('should install subset of common resources when framework alerts are enabled', async () => { const mockClusterClient = elasticsearchServiceMock.createElasticsearchClient(); const getClusterClient = jest.fn(() => Promise.resolve(mockClusterClient)); - const getResourceNameMock = jest - .fn() - .mockReturnValueOnce(TECHNICAL_COMPONENT_TEMPLATE_NAME) - .mockReturnValueOnce(ECS_COMPONENT_TEMPLATE_NAME); const installer = new ResourceInstaller({ logger: loggerMock.create(), isWriteEnabled: true, disabledRegistrationContexts: [], - getResourceName: getResourceNameMock, + getResourceName: jest.fn(), getClusterClient, areFrameworkAlertsEnabled: true, pluginStop$, @@ -131,15 +121,12 @@ describe('resourceInstaller', () => { // ILM policy should be handled by framework expect(mockClusterClient.ilm.putLifecycle).not.toHaveBeenCalled(); - expect(mockClusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(2); + // ECS component template should be handled by framework + expect(mockClusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(1); expect(mockClusterClient.cluster.putComponentTemplate).toHaveBeenNthCalledWith( 1, expect.objectContaining({ name: TECHNICAL_COMPONENT_TEMPLATE_NAME }) ); - expect(mockClusterClient.cluster.putComponentTemplate).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ name: ECS_COMPONENT_TEMPLATE_NAME }) - ); }); it('should install index level resources', async () => { const mockClusterClient = elasticsearchServiceMock.createElasticsearchClient(); diff --git a/x-pack/plugins/rule_registry/server/rule_data_plugin_service/resource_installer.ts b/x-pack/plugins/rule_registry/server/rule_data_plugin_service/resource_installer.ts index 6af288e57a4a0..59c74b81712d8 100644 --- a/x-pack/plugins/rule_registry/server/rule_data_plugin_service/resource_installer.ts +++ b/x-pack/plugins/rule_registry/server/rule_data_plugin_service/resource_installer.ts @@ -15,18 +15,16 @@ import type { PublicMethodsOf } from '@kbn/utility-types'; import { DEFAULT_ALERTS_ILM_POLICY, DEFAULT_ALERTS_ILM_POLICY_NAME, -} from '@kbn/alerting-plugin/server'; -import { ECS_COMPONENT_TEMPLATE_NAME, - TECHNICAL_COMPONENT_TEMPLATE_NAME, -} from '../../common/assets'; +} from '@kbn/alerting-plugin/server'; +import { TECHNICAL_COMPONENT_TEMPLATE_NAME } from '../../common/assets'; import { technicalComponentTemplate } from '../../common/assets/component_templates/technical_component_template'; import { ecsComponentTemplate } from '../../common/assets/component_templates/ecs_component_template'; import type { IndexInfo } from './index_info'; const INSTALLATION_TIMEOUT = 20 * 60 * 1000; // 20 minutes -const TOTAL_FIELDS_LIMIT = 1900; +const TOTAL_FIELDS_LIMIT = 2500; interface ConstructorOptions { getResourceName(relativeName: string): string; getClusterClient: () => Promise; @@ -98,7 +96,7 @@ export class ResourceInstaller { */ public async installCommonResources(): Promise { await this.installWithTimeout('common resources shared between all indices', async () => { - const { getResourceName, logger, areFrameworkAlertsEnabled } = this.options; + const { logger, areFrameworkAlertsEnabled } = this.options; try { // We can install them in parallel @@ -112,16 +110,15 @@ export class ResourceInstaller { name: DEFAULT_ALERTS_ILM_POLICY_NAME, body: DEFAULT_ALERTS_ILM_POLICY, }), + this.createOrUpdateComponentTemplate({ + name: ECS_COMPONENT_TEMPLATE_NAME, + body: ecsComponentTemplate, + }), ]), this.createOrUpdateComponentTemplate({ - name: getResourceName(TECHNICAL_COMPONENT_TEMPLATE_NAME), + name: TECHNICAL_COMPONENT_TEMPLATE_NAME, body: technicalComponentTemplate, }), - - this.createOrUpdateComponentTemplate({ - name: getResourceName(ECS_COMPONENT_TEMPLATE_NAME), - body: ecsComponentTemplate, - }), ]); } catch (err) { logger.error( @@ -315,7 +312,7 @@ export class ResourceInstaller { } private async installNamespacedIndexTemplate(indexInfo: IndexInfo, namespace: string) { - const { logger, getResourceName } = this.options; + const { logger } = this.options; const { componentTemplateRefs, componentTemplates, @@ -329,8 +326,7 @@ export class ResourceInstaller { logger.debug(`Installing index template for ${primaryNamespacedAlias}`); - const technicalComponentNames = [getResourceName(TECHNICAL_COMPONENT_TEMPLATE_NAME)]; - const referencedComponentNames = componentTemplateRefs.map((ref) => getResourceName(ref)); + const technicalComponentNames = [TECHNICAL_COMPONENT_TEMPLATE_NAME]; const ownComponentNames = componentTemplates.map((template) => indexInfo.getComponentTemplateName(template.name) ); @@ -365,11 +361,7 @@ export class ResourceInstaller { // - then we include own component templates registered with this index // - finally, we include technical component templates to make sure the index gets all the // mappings and settings required by all Kibana plugins using rule registry to work properly - composed_of: [ - ...referencedComponentNames, - ...ownComponentNames, - ...technicalComponentNames, - ], + composed_of: [...componentTemplateRefs, ...ownComponentNames, ...technicalComponentNames], template: { settings: { diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.test.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.test.ts index 2902533c145f1..8019e5bf3254a 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.test.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.test.ts @@ -1015,7 +1015,7 @@ describe('createLifecycleExecutor', () => { return { state }; }); - await executor( + const serializedAlerts = await executor( createDefaultAlertExecutorOptions({ alertId: 'TEST_ALERT_0', params: {}, @@ -1061,6 +1061,43 @@ describe('createLifecycleExecutor', () => { }) ); + expect(serializedAlerts.state.trackedAlerts).toEqual({ + TEST_ALERT_0: { + alertId: 'TEST_ALERT_0', + alertUuid: 'TEST_ALERT_0_UUID', + flapping: true, + flappingHistory: flapping.slice(1).concat([false]), + pendingRecoveredCount: 0, + started: '2020-01-01T12:00:00.000Z', + }, + TEST_ALERT_1: { + alertId: 'TEST_ALERT_1', + alertUuid: 'TEST_ALERT_1_UUID', + flapping: false, + flappingHistory: [false, false, false], + pendingRecoveredCount: 0, + started: '2020-01-02T12:00:00.000Z', + }, + TEST_ALERT_2: { + alertId: 'TEST_ALERT_2', + alertUuid: 'TEST_ALERT_2_UUID', + flapping: true, + flappingHistory: flapping.slice(1).concat([false]), + pendingRecoveredCount: 0, + started: '2020-01-01T12:00:00.000Z', + }, + TEST_ALERT_3: { + alertId: 'TEST_ALERT_3', + alertUuid: 'TEST_ALERT_3_UUID', + flapping: true, + flappingHistory: [false, false, false], + pendingRecoveredCount: 0, + started: '2020-01-02T12:00:00.000Z', + }, + }); + + expect(serializedAlerts.state.trackedAlertsRecovered).toEqual({}); + expect((await ruleDataClientMock.getWriter()).bulk).toHaveBeenCalledWith( expect.objectContaining({ body: [ @@ -1070,7 +1107,7 @@ describe('createLifecycleExecutor', () => { [ALERT_INSTANCE_ID]: 'TEST_ALERT_0', [ALERT_WORKFLOW_STATUS]: 'closed', [ALERT_STATUS]: ALERT_STATUS_ACTIVE, - [ALERT_FLAPPING]: true, + [ALERT_FLAPPING]: false, [EVENT_ACTION]: 'active', [EVENT_KIND]: 'signal', }), @@ -1182,7 +1219,7 @@ describe('createLifecycleExecutor', () => { return { state }; }); - await executor( + const serializedAlerts = await executor( createDefaultAlertExecutorOptions({ alertId: 'TEST_ALERT_0', params: {}, @@ -1228,6 +1265,44 @@ describe('createLifecycleExecutor', () => { }) ); + expect(serializedAlerts.state.trackedAlerts).toEqual({ + TEST_ALERT_2: { + alertId: 'TEST_ALERT_2', + alertUuid: 'TEST_ALERT_2_UUID', + flapping: true, + flappingHistory: [true, true, true], + pendingRecoveredCount: 1, + started: '2020-01-02T12:00:00.000Z', + }, + }); + + expect(serializedAlerts.state.trackedAlertsRecovered).toEqual({ + TEST_ALERT_0: { + alertId: 'TEST_ALERT_0', + alertUuid: 'TEST_ALERT_0_UUID', + flapping: true, + flappingHistory: [true, true, true, true, true], + pendingRecoveredCount: 0, + started: '2020-01-01T12:00:00.000Z', + }, + TEST_ALERT_1: { + alertId: 'TEST_ALERT_1', + alertUuid: 'TEST_ALERT_1_UUID', + flapping: false, + flappingHistory: notFlapping.slice(0, notFlapping.length - 1).concat([true]), + pendingRecoveredCount: 0, + started: '2020-01-02T12:00:00.000Z', + }, + TEST_ALERT_3: { + alertId: 'TEST_ALERT_3', + alertUuid: 'TEST_ALERT_3_UUID', + flapping: false, + flappingHistory: notFlapping.slice(0, notFlapping.length - 1).concat([true]), + pendingRecoveredCount: 0, + started: '2020-01-02T12:00:00.000Z', + }, + }); + expect((await ruleDataClientMock.getWriter()).bulk).toHaveBeenCalledWith( expect.objectContaining({ body: expect.arrayContaining([ @@ -1235,10 +1310,10 @@ describe('createLifecycleExecutor', () => { { index: { _id: 'TEST_ALERT_0_UUID' } }, expect.objectContaining({ [ALERT_INSTANCE_ID]: 'TEST_ALERT_0', - [ALERT_STATUS]: ALERT_STATUS_ACTIVE, - [EVENT_ACTION]: 'active', + [ALERT_STATUS]: ALERT_STATUS_RECOVERED, + [EVENT_ACTION]: 'close', [EVENT_KIND]: 'signal', - [ALERT_FLAPPING]: true, + [ALERT_FLAPPING]: false, }), { index: { _id: 'TEST_ALERT_1_UUID' } }, expect.objectContaining({ diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts index f0eef646a1f28..c5387f66d000b 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts @@ -278,12 +278,7 @@ export const createLifecycleExecutor = trackedAlertRecoveredIds ); - const { - alertUuid, - started, - flapping: isCurrentlyFlapping, - pendingRecoveredCount, - } = !isNew + const { alertUuid, started, flapping, pendingRecoveredCount } = !isNew ? state.trackedAlerts[alertId] : { alertUuid: lifecycleAlertServices.getAlertUuid(alertId), @@ -294,8 +289,6 @@ export const createLifecycleExecutor = pendingRecoveredCount: 0, }; - const flapping = isFlapping(flappingSettings, flappingHistory, isCurrentlyFlapping); - const event: ParsedTechnicalFields & ParsedExperimentalFields = { ...alertData?.fields, ...commonRuleFields, @@ -314,7 +307,9 @@ export const createLifecycleExecutor = [ALERT_WORKFLOW_STATUS]: alertData?.fields[ALERT_WORKFLOW_STATUS] ?? 'open', [EVENT_KIND]: 'signal', [EVENT_ACTION]: isNew ? 'open' : isActive ? 'active' : 'close', - [TAGS]: options.rule.tags, + [TAGS]: Array.from( + new Set([...(currentAlertData?.tags ?? []), ...(options.rule.tags ?? [])]) + ), [VERSION]: ruleDataClient.kibanaVersion, [ALERT_FLAPPING]: flapping, ...(isRecovered ? { [ALERT_END]: commonRuleFields[TIMESTAMP] } : {}), @@ -366,10 +361,11 @@ export const createLifecycleExecutor = const nextTrackedAlerts = Object.fromEntries( allEventsToIndex .filter(({ event }) => event[ALERT_STATUS] !== ALERT_STATUS_RECOVERED) - .map(({ event, flappingHistory, flapping, pendingRecoveredCount }) => { + .map(({ event, flappingHistory, flapping: isCurrentlyFlapping, pendingRecoveredCount }) => { const alertId = event[ALERT_INSTANCE_ID]!; const alertUuid = event[ALERT_UUID]!; const started = new Date(event[ALERT_START]!).toISOString(); + const flapping = isFlapping(flappingSettings, flappingHistory, isCurrentlyFlapping); return [ alertId, { alertId, alertUuid, started, flappingHistory, flapping, pendingRecoveredCount }, @@ -387,10 +383,11 @@ export const createLifecycleExecutor = event[ALERT_STATUS] === ALERT_STATUS_RECOVERED && (flapping || flappingHistory.filter((f: boolean) => f).length > 0) ) - .map(({ event, flappingHistory, flapping, pendingRecoveredCount }) => { + .map(({ event, flappingHistory, flapping: isCurrentlyFlapping, pendingRecoveredCount }) => { const alertId = event[ALERT_INSTANCE_ID]!; const alertUuid = event[ALERT_UUID]!; const started = new Date(event[ALERT_START]!).toISOString(); + const flapping = isFlapping(flappingSettings, flappingHistory, isCurrentlyFlapping); return [ alertId, { alertId, alertUuid, started, flappingHistory, flapping, pendingRecoveredCount }, diff --git a/x-pack/plugins/rule_registry/tsconfig.json b/x-pack/plugins/rule_registry/tsconfig.json index a3a2a6d373b2b..1bb9b96e6aa92 100644 --- a/x-pack/plugins/rule_registry/tsconfig.json +++ b/x-pack/plugins/rule_registry/tsconfig.json @@ -16,7 +16,6 @@ "@kbn/data-plugin", "@kbn/alerting-plugin", "@kbn/security-plugin", - "@kbn/safer-lodash-set", "@kbn/rule-data-utils", "@kbn/es-query", "@kbn/data-views-plugin", @@ -32,6 +31,7 @@ "@kbn/logging", "@kbn/securitysolution-io-ts-utils", "@kbn/share-plugin", + "@kbn/alerts-as-data-utils", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/security/server/__snapshots__/prompt_page.test.tsx.snap b/x-pack/plugins/security/server/__snapshots__/prompt_page.test.tsx.snap index 79709d6cc5573..49d0d83a5e8c6 100644 --- a/x-pack/plugins/security/server/__snapshots__/prompt_page.test.tsx.snap +++ b/x-pack/plugins/security/server/__snapshots__/prompt_page.test.tsx.snap @@ -1,5 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`PromptPage renders as expected with additional scripts 1`] = `"ElasticMockedFonts

      Some Title

      Some Body
      Action#1
      Action#2
      "`; +exports[`PromptPage renders as expected with additional scripts 1`] = `"ElasticMockedFonts

      Some Title

      Some Body
      Action#1
      Action#2
      "`; -exports[`PromptPage renders as expected without additional scripts 1`] = `"ElasticMockedFonts

      Some Title

      Some Body
      Action#1
      Action#2
      "`; +exports[`PromptPage renders as expected without additional scripts 1`] = `"ElasticMockedFonts

      Some Title

      Some Body
      Action#1
      Action#2
      "`; diff --git a/x-pack/plugins/security/server/authentication/__snapshots__/unauthenticated_page.test.tsx.snap b/x-pack/plugins/security/server/authentication/__snapshots__/unauthenticated_page.test.tsx.snap index cd7cf4d96e698..3f01f0ca7c8bf 100644 --- a/x-pack/plugins/security/server/authentication/__snapshots__/unauthenticated_page.test.tsx.snap +++ b/x-pack/plugins/security/server/authentication/__snapshots__/unauthenticated_page.test.tsx.snap @@ -1,5 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`UnauthenticatedPage renders as expected 1`] = `"ElasticMockedFonts

      We hit an authentication error

      Try logging in again, and if the problem persists, contact your system administrator.

      "`; +exports[`UnauthenticatedPage renders as expected 1`] = `"ElasticMockedFonts

      We hit an authentication error

      Try logging in again, and if the problem persists, contact your system administrator.

      "`; -exports[`UnauthenticatedPage renders as expected with custom title 1`] = `"My Company NameMockedFonts

      We hit an authentication error

      Try logging in again, and if the problem persists, contact your system administrator.

      "`; +exports[`UnauthenticatedPage renders as expected with custom title 1`] = `"My Company NameMockedFonts

      We hit an authentication error

      Try logging in again, and if the problem persists, contact your system administrator.

      "`; diff --git a/x-pack/plugins/security/server/authorization/__snapshots__/reset_session_page.test.tsx.snap b/x-pack/plugins/security/server/authorization/__snapshots__/reset_session_page.test.tsx.snap index 060229539b9ad..b8ec6b35fe6b2 100644 --- a/x-pack/plugins/security/server/authorization/__snapshots__/reset_session_page.test.tsx.snap +++ b/x-pack/plugins/security/server/authorization/__snapshots__/reset_session_page.test.tsx.snap @@ -1,5 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`ResetSessionPage renders as expected 1`] = `"ElasticMockedFonts

      You do not have permission to access the requested page

      Either go back to the previous page or log in as a different user.

      "`; +exports[`ResetSessionPage renders as expected 1`] = `"ElasticMockedFonts

      You do not have permission to access the requested page

      Either go back to the previous page or log in as a different user.

      "`; -exports[`ResetSessionPage renders as expected with custom page title 1`] = `"My Company NameMockedFonts

      You do not have permission to access the requested page

      Either go back to the previous page or log in as a different user.

      "`; +exports[`ResetSessionPage renders as expected with custom page title 1`] = `"My Company NameMockedFonts

      You do not have permission to access the requested page

      Either go back to the previous page or log in as a different user.

      "`; diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index 55b802ae06d8d..5c903966bd0cc 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -91,6 +91,11 @@ export enum SecurityPageName { cloudSecurityPostureDashboard = 'cloud_security_posture-dashboard', cloudSecurityPostureFindings = 'cloud_security_posture-findings', cloudSecurityPostureRules = 'cloud_security_posture-rules', + /* + * Warning: Computed values are not permitted in an enum with string valued members + * All cloud defend page names must match `CloudDefendPageId` in x-pack/plugins/cloud_defend/public/common/navigation/types.ts + */ + cloudDefendPolicies = 'cloud_defend-policies', dashboardsLanding = 'dashboards', dataQuality = 'data_quality', detections = 'detections', diff --git a/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_action_generator.ts b/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_action_generator.ts index cb4848d79eb41..2c2a4fb23e859 100644 --- a/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_action_generator.ts +++ b/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_action_generator.ts @@ -220,9 +220,9 @@ export class EndpointActionGenerator extends BaseDataGenerator { ResponseActionsExecuteParameters > ).parameters = { - command: (overrides.parameters as ResponseActionsExecuteParameters).command ?? 'ls -al', + command: (overrides.parameters as ResponseActionsExecuteParameters)?.command ?? 'ls -al', timeout: - (overrides.parameters as ResponseActionsExecuteParameters).timeout ?? + (overrides.parameters as ResponseActionsExecuteParameters)?.timeout ?? DEFAULT_EXECUTE_ACTION_TIMEOUT, // 4hrs }; } diff --git a/x-pack/plugins/security_solution/common/endpoint/types/actions.ts b/x-pack/plugins/security_solution/common/endpoint/types/actions.ts index 0a1f482c8583f..b47b4004f9a5b 100644 --- a/x-pack/plugins/security_solution/common/endpoint/types/actions.ts +++ b/x-pack/plugins/security_solution/common/endpoint/types/actions.ts @@ -203,6 +203,11 @@ export interface EndpointAction extends ActionRequestFields { // wait to send back an action result before it will timeout timeout?: number; data: EndpointActionData; + // signature of the endpoint action + signed?: { + data: string; + signature: string; + }; } export interface EndpointActionResponse { diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts index 5863d525ca0f8..1b6d0bb022f9b 100644 --- a/x-pack/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/plugins/security_solution/common/experimental_features.ts @@ -90,6 +90,10 @@ export const allowedExperimentalValues = Object.freeze({ * Enables top charts on Alerts Page */ alertsPageChartsEnabled: true, + /** + * Enables the new security flyout over the current alert details flyout + */ + securityFlyoutEnabled: false, /** * Keep DEPRECATED experimental flags that are documented to prevent failed upgrades. diff --git a/x-pack/plugins/security_solution/cypress/e2e/cases/connectors.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/cases/connectors.cy.ts index 888b492d28b1d..e85885bb1ab0d 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/cases/connectors.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/cases/connectors.cy.ts @@ -14,7 +14,7 @@ import { cleanKibana, deleteCases } from '../../tasks/common'; import { addServiceNowConnector, openAddNewConnectorOption, - selectLastConnectorCreated, + verifyNewConnectorSelected, } from '../../tasks/configure_cases'; import { login, visitWithoutDateRange } from '../../tasks/login'; @@ -58,7 +58,7 @@ describe('Cases connectors', () => { }); cy.intercept('POST', '/api/actions/connector').as('createConnector'); - cy.intercept('POST', '/api/cases/configure', (req) => { + cy.intercept('PATCH', '/api/cases/configure/*', (req) => { const connector = req.body.connector; req.reply((res) => { res.send(200, { ...configureResult, connector }); @@ -94,14 +94,15 @@ describe('Cases connectors', () => { cy.wait('@createConnector').then(({ response }) => { cy.wrap(response?.statusCode).should('eql', 200); + + verifyNewConnectorSelected(snConnector); + cy.get(TOASTER).should('have.text', "Created 'New connector'"); + cy.get(TOASTER).should('have.text', 'Saved external connection settings'); cy.get(TOASTER).should('not.exist'); - selectLastConnectorCreated(response?.body.id); - - cy.wait('@saveConnector', { timeout: 10000 }).its('response.statusCode').should('eql', 200); + cy.wait('@saveConnector').its('response.statusCode').should('eql', 200); cy.get(SERVICE_NOW_MAPPING).first().should('have.text', 'short_description'); - cy.get(TOASTER).should('have.text', 'Saved external connection settings'); }); }); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/related_integrations.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/related_integrations.cy.ts index f411b5e229590..663317f334751 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/related_integrations.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/related_integrations.cy.ts @@ -117,7 +117,7 @@ describe('Related integrations', () => { const rule = { name: 'Related integrations rule', integrations: [ - { name: 'Amazon CloudFront', installed: true, enabled: true }, + { name: 'AWS Cloudfront', installed: true, enabled: true }, { name: 'AWS CloudTrail', installed: true, enabled: false }, { name: 'Aws Unknown', installed: false, enabled: false }, { name: 'System', installed: true, enabled: true }, diff --git a/x-pack/plugins/security_solution/cypress/e2e/exceptions/alerts_table_flow/add_exception.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/exceptions/alerts_table_flow/add_exception.cy.ts index 213ea64fc4ceb..41b190c8ccc0d 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/exceptions/alerts_table_flow/add_exception.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/exceptions/alerts_table_flow/add_exception.cy.ts @@ -86,7 +86,10 @@ describe('Exceptions Table', () => { expectedExportedExceptionList(this.exceptionListResponse) ); - cy.get(TOASTER).should('have.text', 'Exception list export success'); + cy.get(TOASTER).should( + 'have.text', + `Exception list "${getExceptionList1().name}" exported successfully` + ); }); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/exceptions/exceptions_management_flow/exceptions_table.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/exceptions/exceptions_management_flow/exceptions_table.cy.ts index d571d7cd4fae8..99d38ef3c5819 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/exceptions/exceptions_management_flow/exceptions_table.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/exceptions/exceptions_management_flow/exceptions_table.cy.ts @@ -83,7 +83,10 @@ describe('Exceptions Table', () => { expectedExportedExceptionList(this.exceptionListResponse) ); - cy.get(TOASTER).should('have.text', 'Exception list export success'); + cy.get(TOASTER).should( + 'have.text', + `Exception list "${getExceptionList1().name}" exported successfully` + ); }); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/urls/state.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/urls/state.cy.ts index 1daaa206a67f4..0cbd85ffcba43 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/urls/state.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/urls/state.cy.ts @@ -274,10 +274,8 @@ describe('url state', () => { ); }); - // Failing on `main`, skipping for now, to be addressed by security-detection-rules-area - it.skip('Do not clears kql when navigating to a new page', () => { + it('Do not clears kql when navigating to a new page', () => { visitWithoutDateRange(ABSOLUTE_DATE_RANGE.urlKqlHostsHosts); - kqlSearch('source.ip: "10.142.0.9"{enter}'); navigateFromHeaderTo(NETWORK); cy.get(KQL_INPUT).should('have.text', 'source.ip: "10.142.0.9"'); }); diff --git a/x-pack/plugins/security_solution/cypress/tasks/configure_cases.ts b/x-pack/plugins/security_solution/cypress/tasks/configure_cases.ts index 4019468ddc43e..0b2f722204f5f 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/configure_cases.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/configure_cases.ts @@ -39,6 +39,10 @@ export const openAddNewConnectorOption = () => { }); }; +export const verifyNewConnectorSelected = (connector: Connector) => { + cy.get(CONNECTORS_DROPDOWN).should('have.text', connector.connectorName); +}; + export const selectLastConnectorCreated = (id: string) => { cy.get(CONNECTORS_DROPDOWN).click({ force: true }); cy.get(CONNECTOR(id)).click(); diff --git a/x-pack/plugins/security_solution/kibana.jsonc b/x-pack/plugins/security_solution/kibana.jsonc index 6f6c371f5f27f..85418bdeb31b7 100644 --- a/x-pack/plugins/security_solution/kibana.jsonc +++ b/x-pack/plugins/security_solution/kibana.jsonc @@ -15,6 +15,7 @@ "alerting", "cases", "cloud", + "cloudDefend", "cloudSecurityPosture", "dashboard", "data", diff --git a/x-pack/plugins/security_solution/public/app/deep_links/index.ts b/x-pack/plugins/security_solution/public/app/deep_links/index.ts index 9853c52502fb2..87d43742a9433 100644 --- a/x-pack/plugins/security_solution/public/app/deep_links/index.ts +++ b/x-pack/plugins/security_solution/public/app/deep_links/index.ts @@ -7,7 +7,8 @@ import { i18n } from '@kbn/i18n'; -import { getSecuritySolutionLink } from '@kbn/cloud-security-posture-plugin/public'; +import { getSecuritySolutionLink as getCloudDefendSecuritySolutionLink } from '@kbn/cloud-defend-plugin/public'; +import { getSecuritySolutionLink as getCloudPostureSecuritySolutionLink } from '@kbn/cloud-security-posture-plugin/public'; import { getSecuritySolutionDeepLink } from '@kbn/threat-intelligence-plugin/public'; import type { LicenseType } from '@kbn/licensing-plugin/common/types'; import { getCasesDeepLinks } from '@kbn/cases-plugin/public'; @@ -167,7 +168,7 @@ export const securitySolutionsDeepLinks: SecuritySolutionDeepLink[] = [ ], }, { - ...getSecuritySolutionLink('dashboard'), + ...getCloudPostureSecuritySolutionLink('dashboard'), features: [FEATURE.general], }, { @@ -251,7 +252,7 @@ export const securitySolutionsDeepLinks: SecuritySolutionDeepLink[] = [ ], }, { - ...getSecuritySolutionLink('findings'), + ...getCloudPostureSecuritySolutionLink('findings'), features: [FEATURE.general], navLinkStatus: AppNavLinkStatus.visible, order: 9002, @@ -529,7 +530,10 @@ export const securitySolutionsDeepLinks: SecuritySolutionDeepLink[] = [ path: RESPONSE_ACTIONS_HISTORY_PATH, }, { - ...getSecuritySolutionLink('benchmarks'), + ...getCloudPostureSecuritySolutionLink('benchmarks'), + }, + { + ...getCloudDefendSecuritySolutionLink('policies'), }, ], }, diff --git a/x-pack/plugins/security_solution/public/app/home/home_navigations.ts b/x-pack/plugins/security_solution/public/app/home/home_navigations.ts index bbff6ffa0a6f9..d9a988004ac1a 100644 --- a/x-pack/plugins/security_solution/public/app/home/home_navigations.ts +++ b/x-pack/plugins/security_solution/public/app/home/home_navigations.ts @@ -7,6 +7,7 @@ import { getSecuritySolutionNavTab as getSecuritySolutionCSPNavTab } from '@kbn/cloud-security-posture-plugin/public'; import { getSecuritySolutionNavTab as getSecuritySolutionTINavTab } from '@kbn/threat-intelligence-plugin/public'; +import { getSecuritySolutionNavTab as getSecuritySolutionCloudDefendNavTab } from '@kbn/cloud-defend-plugin/public'; import * as i18n from '../translations'; import type { SecurityNav, SecurityNavGroup } from '../../common/components/navigation/types'; import { SecurityNavGroupKey } from '../../common/components/navigation/types'; @@ -186,6 +187,10 @@ export const navTabs: SecurityNav = { ...getSecuritySolutionCSPNavTab('benchmarks', APP_PATH), urlKey: 'administration', }, + [SecurityPageName.cloudDefendPolicies]: { + ...getSecuritySolutionCloudDefendNavTab('policies', APP_PATH), + urlKey: 'administration', + }, [SecurityPageName.entityAnalytics]: { id: SecurityPageName.entityAnalytics, name: i18n.ENTITY_ANALYTICS, diff --git a/x-pack/plugins/security_solution/public/app/home/template_wrapper/index.tsx b/x-pack/plugins/security_solution/public/app/home/template_wrapper/index.tsx index f194ef0e463eb..bda2e4d8d3629 100644 --- a/x-pack/plugins/security_solution/public/app/home/template_wrapper/index.tsx +++ b/x-pack/plugins/security_solution/public/app/home/template_wrapper/index.tsx @@ -11,6 +11,7 @@ import { EuiThemeProvider, useEuiTheme } from '@elastic/eui'; import { IS_DRAGGING_CLASS_NAME } from '@kbn/securitysolution-t-grid'; import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template'; import type { KibanaPageTemplateProps } from '@kbn/shared-ux-page-kibana-template'; +import { ExpandableFlyout, ExpandableFlyoutProvider } from '@kbn/expandable-flyout'; import { useSecuritySolutionNavigation } from '../../../common/components/navigation/use_security_solution_navigation'; import { TimelineId } from '../../../../common/types/timeline'; import { getTimelineShowStatusByIdSelector } from '../../../timelines/components/flyout/selectors'; @@ -80,34 +81,35 @@ export const SecuritySolutionTemplateWrapper: React.FC - - - + - {children} - - - {isTimelineBottomBarVisible && ( - - - - - - )} - + + + {children} + + {isTimelineBottomBarVisible && ( + + + + + + )} + {}} /> + + ); }); diff --git a/x-pack/plugins/security_solution/public/cloud_defend/index.ts b/x-pack/plugins/security_solution/public/cloud_defend/index.ts new file mode 100644 index 0000000000000..4ec2329d36bd5 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cloud_defend/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { SecuritySubPlugin } from '../app/types'; +import { routes } from './routes'; + +export class CloudDefend { + public setup() {} + + public start(): SecuritySubPlugin { + return { routes }; + } +} diff --git a/x-pack/plugins/security_solution/public/cloud_defend/links.ts b/x-pack/plugins/security_solution/public/cloud_defend/links.ts new file mode 100644 index 0000000000000..652ebe6181151 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cloud_defend/links.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 { getSecuritySolutionLink } from '@kbn/cloud-defend-plugin/public'; +import { i18n } from '@kbn/i18n'; +import type { SecurityPageName } from '../../common/constants'; +import { SERVER_APP_ID } from '../../common/constants'; +import type { LinkItem } from '../common/links/types'; +import { IconCloudDefend } from '../management/icons/cloud_defend'; + +const commonLinkProperties: Partial = { + hideTimeline: true, + capabilities: [`${SERVER_APP_ID}.show`], +}; + +export const manageLinks: LinkItem = { + ...getSecuritySolutionLink('policies'), + description: i18n.translate('xpack.securitySolution.appLinks.cloudDefendPoliciesDescription', { + defaultMessage: 'View drift prevention policies.', + }), + landingIcon: IconCloudDefend, + ...commonLinkProperties, +}; diff --git a/x-pack/plugins/security_solution/public/cloud_defend/routes.tsx b/x-pack/plugins/security_solution/public/cloud_defend/routes.tsx new file mode 100644 index 0000000000000..18bd7641addf3 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cloud_defend/routes.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 type { + CloudDefendPageId, + CloudDefendSecuritySolutionContext, +} from '@kbn/cloud-defend-plugin/public'; +import { CLOUD_DEFEND_BASE_PATH } from '@kbn/cloud-defend-plugin/public'; +import type { SecurityPageName, SecuritySubPluginRoutes } from '../app/types'; +import { useKibana } from '../common/lib/kibana'; +import { SecuritySolutionPageWrapper } from '../common/components/page_wrapper'; +import { SpyRoute } from '../common/utils/route/spy_routes'; +import { FiltersGlobal } from '../common/components/filters_global'; +import { PluginTemplateWrapper } from '../common/components/plugin_template_wrapper'; + +// This exists only for the type signature cast +const CloudDefendSpyRoute = ({ pageName, ...rest }: { pageName?: CloudDefendPageId }) => ( + +); + +const cloudDefendSecuritySolutionContext: CloudDefendSecuritySolutionContext = { + getFiltersGlobalComponent: () => FiltersGlobal, + getSpyRouteComponent: () => CloudDefendSpyRoute, +}; + +const CloudDefend = () => { + const { cloudDefend } = useKibana().services; + const CloudDefendRouter = cloudDefend.getCloudDefendRouter(); + + return ( + + + + + + ); +}; + +CloudDefend.displayName = 'CloudDefend'; + +export const routes: SecuritySubPluginRoutes = [ + { + path: CLOUD_DEFEND_BASE_PATH, + component: CloudDefend, + }, +]; diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/links.ts b/x-pack/plugins/security_solution/public/cloud_security_posture/links.ts index def3b0ed9f5eb..e4c0dfd1f20db 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/links.ts +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/links.ts @@ -51,6 +51,9 @@ export const manageCategories: LinkCategories = [ label: i18n.translate('xpack.securitySolution.appLinks.category.cloudSecurityPosture', { defaultMessage: 'CLOUD SECURITY POSTURE', }), - linkIds: [SecurityPageName.cloudSecurityPostureBenchmarks], + linkIds: [ + SecurityPageName.cloudSecurityPostureBenchmarks, + SecurityPageName.cloudDefendPolicies, + ], }, ]; diff --git a/x-pack/plugins/security_solution/public/common/components/control_columns/row_action/index.tsx b/x-pack/plugins/security_solution/public/common/components/control_columns/row_action/index.tsx index c41eaba862c9c..a7b3395c12c5a 100644 --- a/x-pack/plugins/security_solution/public/common/components/control_columns/row_action/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/control_columns/row_action/index.tsx @@ -8,6 +8,7 @@ import type { EuiDataGridCellValueElementProps } from '@elastic/eui'; import React, { useCallback, useMemo } from 'react'; import { useDispatch } from 'react-redux'; +import { useExpandableFlyoutContext } from '@kbn/expandable-flyout'; import type { SetEventsDeleted, SetEventsLoading, @@ -19,6 +20,7 @@ import { getMappedNonEcsValue } from '../../../../timelines/components/timeline/ import type { TimelineItem, TimelineNonEcsData } from '../../../../../common/search_strategy'; import type { ColumnHeaderOptions, OnRowSelected } from '../../../../../common/types/timeline'; import { dataTableActions } from '../../../store/data_table'; +import { useIsExperimentalFeatureEnabled } from '../../../hooks/use_experimental_features'; type Props = EuiDataGridCellValueElementProps & { columnHeaders: ColumnHeaderOptions[]; @@ -64,7 +66,10 @@ const RowActionComponent = ({ }: Props) => { const { data: timelineNonEcsData, ecs: ecsData, _id: eventId, _index: indexName } = data ?? {}; + const { openFlyout } = useExpandableFlyoutContext(); + const dispatch = useDispatch(); + const isSecurityFlyoutEnabled = useIsExperimentalFeatureEnabled('securityFlyoutEnabled'); const columnValues = useMemo( () => @@ -90,14 +95,18 @@ const RowActionComponent = ({ }, }; - dispatch( - dataTableActions.toggleDetailPanel({ - ...updatedExpandedDetail, - tabType, - id: tableId, - }) - ); - }, [dispatch, eventId, indexName, tabType, tableId]); + if (isSecurityFlyoutEnabled) { + openFlyout({}); + } else { + dispatch( + dataTableActions.toggleDetailPanel({ + ...updatedExpandedDetail, + tabType, + id: tableId, + }) + ); + } + }, [dispatch, eventId, indexName, isSecurityFlyoutEnabled, openFlyout, tabType, tableId]); const Action = controlColumn.rowCellRender; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/insights/related_alerts_by_process_ancestry.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/insights/related_alerts_by_process_ancestry.tsx index 99c842cd48810..43807363dda1f 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/insights/related_alerts_by_process_ancestry.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/insights/related_alerts_by_process_ancestry.tsx @@ -6,7 +6,7 @@ */ import React, { useMemo, useCallback, useEffect, useState } from 'react'; -import { EuiBetaBadge, EuiSpacer, EuiLoadingSpinner } from '@elastic/eui'; +import { EuiSpacer, EuiLoadingSpinner } from '@elastic/eui'; import type { Filter } from '@kbn/es-query'; import { isActiveTimeline } from '../../../../helpers'; @@ -25,7 +25,6 @@ import { PROCESS_ANCESTRY_ERROR, PROCESS_ANCESTRY_FILTER, } from './translations'; -import { BETA } from '../../../translations'; interface Props { data: TimelineEventsDetailsItem; @@ -102,8 +101,6 @@ export const RelatedAlertsByProcessAncestry = React.memo( ); }, [showContent, cache.alertIds, data, index, originalDocumentId, eventId, scopeId]); - const betaBadge = useMemo(() => , []); - return ( ( } renderContent={renderContent} onToggle={onToggle} - extraAction={betaBadge} /> ); } diff --git a/x-pack/plugins/security_solution/public/common/components/grouping/accordion_panel/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/grouping/accordion_panel/index.test.tsx index 3da0aefb35895..db78f6f2e9257 100644 --- a/x-pack/plugins/security_solution/public/common/components/grouping/accordion_panel/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/grouping/accordion_panel/index.test.tsx @@ -16,6 +16,7 @@ const ruleName = 'Rule name'; const ruleDesc = 'Rule description'; const testProps = { + isLoading: false, groupBucket: { key: [ruleName, ruleDesc], key_as_string: `${ruleName}|${ruleDesc}`, diff --git a/x-pack/plugins/security_solution/public/common/components/grouping/accordion_panel/index.tsx b/x-pack/plugins/security_solution/public/common/components/grouping/accordion_panel/index.tsx index 446b872f3d5f1..548aa81272cd9 100644 --- a/x-pack/plugins/security_solution/public/common/components/grouping/accordion_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/grouping/accordion_panel/index.tsx @@ -31,6 +31,7 @@ interface GroupPanelProps { forceState?: 'open' | 'closed'; groupBucket: RawBucket; groupPanelRenderer?: JSX.Element; + isLoading: boolean; level?: number; onToggleGroup?: (isOpen: boolean, groupBucket: RawBucket) => void; renderChildComponent: (groupFilter: Filter[]) => React.ReactNode; @@ -56,6 +57,7 @@ const GroupPanelComponent = ({ forceState, groupBucket, groupPanelRenderer, + isLoading, level = 0, onToggleGroup, renderChildComponent, @@ -89,6 +91,7 @@ const GroupPanelComponent = ({ data-test-subj="grouping-accordion" extraAction={extraAction} forceState={forceState} + isLoading={isLoading} id={`group${level}-${groupFieldValue}`} onToggle={onToggle} paddingSize="m" diff --git a/x-pack/plugins/security_solution/public/common/components/grouping/container/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/grouping/container/index.test.tsx index c74218cf09e80..d9aa2f589c343 100644 --- a/x-pack/plugins/security_solution/public/common/components/grouping/container/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/grouping/container/index.test.tsx @@ -97,6 +97,7 @@ const testProps = { value: 2, }, }, + isLoading: false, pagination: { pageIndex: 0, pageSize: 25, diff --git a/x-pack/plugins/security_solution/public/common/components/grouping/container/index.tsx b/x-pack/plugins/security_solution/public/common/components/grouping/container/index.tsx index e0ddd959d8b8d..9956ba8e40c06 100644 --- a/x-pack/plugins/security_solution/public/common/components/grouping/container/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/grouping/container/index.tsx @@ -5,7 +5,13 @@ * 2.0. */ -import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTablePagination } from '@elastic/eui'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiProgress, + EuiSpacer, + EuiTablePagination, +} from '@elastic/eui'; import type { Filter } from '@kbn/es-query'; import React, { useMemo, useState } from 'react'; import { firstNonNullValue } from '../../../../../common/endpoint/models/ecs_safety_helpers'; @@ -36,6 +42,7 @@ export interface GroupingContainerProps { groupPanelRenderer?: (fieldBucket: RawBucket) => JSX.Element | undefined; groupsSelector?: JSX.Element; inspectButton?: JSX.Element; + isLoading: boolean; pagination: { pageIndex: number; pageSize: number; @@ -55,6 +62,7 @@ const GroupingContainerComponent = ({ groupPanelRenderer, groupsSelector, inspectButton, + isLoading, pagination, renderChildComponent, selectedGroup, @@ -96,6 +104,7 @@ const GroupingContainerComponent = ({ forceState={(trigger[groupKey] && trigger[groupKey].state) ?? 'closed'} groupBucket={groupBucket} groupPanelRenderer={groupPanelRenderer && groupPanelRenderer(groupBucket)} + isLoading={isLoading} onToggleGroup={(isOpen) => { setTrigger({ // ...trigger, -> this change will keep only one group at a time expanded and one table displayed @@ -121,6 +130,7 @@ const GroupingContainerComponent = ({ customMetricStats, data.stackByMultipleFields0?.buckets, groupPanelRenderer, + isLoading, renderChildComponent, selectedGroup, takeActionItems, @@ -134,6 +144,7 @@ const GroupingContainerComponent = ({ return ( <> ) : ( - + <> + {isLoading && ( + + )} + + )} diff --git a/x-pack/plugins/security_solution/public/common/components/grouping/groups_selector/index.tsx b/x-pack/plugins/security_solution/public/common/components/grouping/groups_selector/index.tsx index 389645bf549da..81382406d2af4 100644 --- a/x-pack/plugins/security_solution/public/common/components/grouping/groups_selector/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/grouping/groups_selector/index.tsx @@ -9,18 +9,13 @@ import type { EuiContextMenuPanelDescriptor, EuiContextMenuPanelItemDescriptor, } from '@elastic/eui'; -import { EuiFlexGroup, EuiFlexItem, EuiBetaBadge, EuiPopover } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; +import { EuiBetaBadge, EuiFlexGroup, EuiFlexItem, EuiPopover } from '@elastic/eui'; import React, { useCallback, useMemo, useState } from 'react'; import type { FieldSpec } from '@kbn/data-views-plugin/common'; import { CustomFieldPanel } from './custom_field_panel'; -import { GROUP_BY, TECHNICAL_PREVIEW } from '../translations'; +import * as i18n from '../translations'; import { StyledContextMenu, StyledEuiButtonEmpty } from '../styles'; -const none = i18n.translate('xpack.securitySolution.groupsSelector.noneGroupByOptionName', { - defaultMessage: 'None', -}); - interface GroupSelectorProps { fields: FieldSpec[]; groupSelected: string; @@ -34,17 +29,38 @@ const GroupsSelectorComponent = ({ groupSelected = 'none', onGroupChange, options, - title = '', + title = i18n.GROUP_BY, }: GroupSelectorProps) => { const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const panels: EuiContextMenuPanelDescriptor[] = useMemo( () => [ { id: 'firstPanel', + title: ( + + + {i18n.SELECT_FIELD.toUpperCase()} + + + + + + ), items: [ { 'data-test-subj': 'panel-none', - name: none, + name: i18n.NONE, icon: groupSelected === 'none' ? 'check' : 'empty', onClick: () => onGroupChange('none'), }, @@ -56,9 +72,7 @@ const GroupsSelectorComponent = ({ })), { 'data-test-subj': `panel-custom`, - name: i18n.translate('xpack.securitySolution.groupsSelector.customGroupByOptionName', { - defaultMessage: 'Custom field', - }), + name: i18n.CUSTOM_FIELD, icon: 'empty', panel: 'customPanel', }, @@ -66,9 +80,7 @@ const GroupsSelectorComponent = ({ }, { id: 'customPanel', - title: i18n.translate('xpack.securitySolution.groupsSelector.customGroupByPanelTitle', { - defaultMessage: 'Group By Custom Field', - }), + title: i18n.GROUP_BY_CUSTOM_FIELD, width: 685, content: ( 0 ? selectedOption[0].label : none + groupSelected !== 'none' && selectedOption.length > 0 + ? selectedOption[0].label + : i18n.NONE } size="xs" > - {`${title ?? GROUP_BY}: ${ - groupSelected !== 'none' && selectedOption.length > 0 ? selectedOption[0].label : none + {`${title}: ${ + groupSelected !== 'none' && selectedOption.length > 0 + ? selectedOption[0].label + : i18n.NONE }`} ), @@ -114,25 +130,18 @@ const GroupsSelectorComponent = ({ ); return ( - - - - - - - - - - + + + ); }; diff --git a/x-pack/plugins/security_solution/public/common/components/grouping/translations.ts b/x-pack/plugins/security_solution/public/common/components/grouping/translations.ts index 2e73e95b80de5..a9cd9a7d233dc 100644 --- a/x-pack/plugins/security_solution/public/common/components/grouping/translations.ts +++ b/x-pack/plugins/security_solution/public/common/components/grouping/translations.ts @@ -20,13 +20,40 @@ export const TAKE_ACTION = i18n.translate( } ); -export const TECHNICAL_PREVIEW = i18n.translate( - 'xpack.securitySolution.grouping.technicalPreviewLabel', +export const BETA = i18n.translate('xpack.securitySolution.grouping.betaLabel', { + defaultMessage: 'Beta', +}); + +export const BETA_TOOL_TIP = i18n.translate('xpack.securitySolution.grouping.betaToolTip', { + defaultMessage: + 'Grouping may show only a subset of alerts while in beta. To see all alerts, use the list view by selecting "None"', +}); + +export const GROUP_BY = i18n.translate('xpack.securitySolution.selector.grouping.label', { + defaultMessage: 'Group alerts by', +}); + +export const GROUP_BY_CUSTOM_FIELD = i18n.translate( + 'xpack.securitySolution.groupsSelector.customGroupByPanelTitle', { - defaultMessage: 'Technical Preview', + defaultMessage: 'Group By Custom Field', } ); -export const GROUP_BY = i18n.translate('xpack.securitySolution.selector.grouping.label', { - defaultMessage: 'Group by field', +export const SELECT_FIELD = i18n.translate( + 'xpack.securitySolution.groupsSelector.groupByPanelTitle', + { + defaultMessage: 'Select Field', + } +); + +export const NONE = i18n.translate('xpack.securitySolution.groupsSelector.noneGroupByOptionName', { + defaultMessage: 'None', }); + +export const CUSTOM_FIELD = i18n.translate( + 'xpack.securitySolution.groupsSelector.customGroupByOptionName', + { + defaultMessage: 'Custom field', + } +); diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/types.ts b/x-pack/plugins/security_solution/public/common/components/navigation/types.ts index b0190d40ced78..29f69700afdb7 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/navigation/types.ts @@ -42,6 +42,7 @@ export type UrlStateType = | 'explore' | 'dashboards' | 'indicators' + | 'cloud_defend' | 'cloud_posture' | 'findings' | 'entity_analytics' @@ -84,6 +85,7 @@ export const securityNavKeys = [ SecurityPageName.cloudSecurityPostureDashboard, SecurityPageName.cloudSecurityPostureFindings, SecurityPageName.cloudSecurityPostureBenchmarks, + SecurityPageName.cloudDefendPolicies, SecurityPageName.entityAnalytics, SecurityPageName.dataQuality, ] as const; diff --git a/x-pack/plugins/security_solution/public/common/containers/grouping/hooks/use_get_group_selector.tsx b/x-pack/plugins/security_solution/public/common/containers/grouping/hooks/use_get_group_selector.tsx index aac4305f8518c..0e29b3edba7ab 100644 --- a/x-pack/plugins/security_solution/public/common/containers/grouping/hooks/use_get_group_selector.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/grouping/hooks/use_get_group_selector.tsx @@ -8,6 +8,7 @@ import type { FieldSpec } from '@kbn/data-views-plugin/common'; import React, { useCallback, useEffect, useMemo } from 'react'; import { useDispatch, useSelector } from 'react-redux'; +import { GROUP_BY } from '../../../components/grouping/translations'; import type { TableId } from '../../../../../common/types'; import { getDefaultGroupingOptions } from '../../../../detections/components/alerts_table/grouping_settings'; import type { State } from '../../../store'; @@ -101,7 +102,7 @@ export const useGetGroupingSelector = ({ }} fields={fields} options={options} - title={'Group Alerts Selector'} + title={GROUP_BY} /> ), [ diff --git a/x-pack/plugins/security_solution/public/common/hooks/timeline/use_timeline_save_prompt.ts b/x-pack/plugins/security_solution/public/common/hooks/timeline/use_timeline_save_prompt.ts index 72337661c6c38..f9bfcfc72c346 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/timeline/use_timeline_save_prompt.ts +++ b/x-pack/plugins/security_solution/public/common/hooks/timeline/use_timeline_save_prompt.ts @@ -30,7 +30,7 @@ export const useTimelineSavePrompt = ( onAppLeave: (handler: AppLeaveHandler) => void ) => { const dispatch = useDispatch(); - const { overlays, application } = useKibana().services; + const { overlays, application, http } = useKibana().services; const getIsTimelineVisible = useShowTimelineForGivenPath(); const history = useHistory(); @@ -66,10 +66,12 @@ export const useTimelineSavePrompt = ( if (confirmRes) { unblock(); - - application.navigateToUrl(location.pathname + location.hash + location.search, { - state: location.state, - }); + application.navigateToUrl( + http.basePath.get() + location.pathname + location.hash + location.search, + { + state: location.state, + } + ); } else { showSaveTimelineModal(); } @@ -92,6 +94,7 @@ export const useTimelineSavePrompt = ( }; }, [ history, + http.basePath, application, overlays, showSaveTimelineModal, diff --git a/x-pack/plugins/security_solution/public/common/mock/test_providers.tsx b/x-pack/plugins/security_solution/public/common/mock/test_providers.tsx index 5761e98dbe366..d0901071063ab 100644 --- a/x-pack/plugins/security_solution/public/common/mock/test_providers.tsx +++ b/x-pack/plugins/security_solution/public/common/mock/test_providers.tsx @@ -20,6 +20,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import type { Action } from '@kbn/ui-actions-plugin/public'; import { CellActionsProvider } from '@kbn/cell-actions'; +import { ExpandableFlyoutProvider } from '@kbn/expandable-flyout'; import { ConsoleManager } from '../../management/components/console'; import type { State } from '../store'; import { createStore } from '../store'; @@ -66,13 +67,15 @@ export const TestProvidersComponent: React.FC = ({ ({ eui: euiDarkVars, darkMode: true })}> - - Promise.resolve(cellActions)} - > - {children} - - + + + Promise.resolve(cellActions)} + > + {children} + + + diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_tables.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_tables.tsx index dd173faade654..66d9d4bbf4e50 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_tables.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_tables.tsx @@ -231,6 +231,7 @@ export const RulesTables = React.memo(({ selectedTab }) => { const shouldShowLinearProgress = (isFetched && isRefetching) || isUpgradingSecurityPackages; const shouldShowLoadingOverlay = (!isFetched && isRefetching) || isPreflightInProgress; + const numberOfSelectedRules = isAllSelected ? pagination.total : selectedRuleIds?.length ?? 1; return ( <> @@ -275,7 +276,7 @@ export const RulesTables = React.memo(({ selectedTab }) => { )} {isBulkEditFlyoutVisible && bulkEditActionType !== undefined && ( diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouped_alerts.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouped_alerts.tsx index 2e4576576dc60..6c5f68d546f97 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouped_alerts.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouped_alerts.tsx @@ -14,7 +14,6 @@ import { v4 as uuidv4 } from 'uuid'; import type { Filter } from '@kbn/es-query'; import { buildEsQuery } from '@kbn/es-query'; import { getEsQueryConfig } from '@kbn/data-plugin/common'; -import type { ReactNode } from 'react-markdown'; import { useGetGroupingSelector } from '../../../common/containers/grouping/hooks/use_get_group_selector'; import type { Status } from '../../../../common/detection_engine/schemas/common'; import { defaultGroup } from '../../../common/store/grouping/defaults'; @@ -68,10 +67,10 @@ interface OwnProps { runtimeMappings: MappingRuntimeFields; signalIndexName: string | null; currentAlertStatusFilterValue?: Status; - renderChildComponent: (groupingFilters: Filter[]) => ReactNode; + renderChildComponent: (groupingFilters: Filter[]) => React.ReactElement; } -type AlertsTableComponentProps = OwnProps & PropsFromRedux; +export type AlertsTableComponentProps = OwnProps & PropsFromRedux; export const GroupedAlertsTableComponent: React.FC = ({ defaultFilters = [], @@ -91,10 +90,10 @@ export const GroupedAlertsTableComponent: React.FC = const dispatch = useDispatch(); const groupingId = tableId; - const getGroupbyIdSelector = groupSelectors.getGroupByIdSelector(); + const getGroupByIdSelector = groupSelectors.getGroupByIdSelector(); const { activeGroup: selectedGroup } = - useSelector((state: State) => getGroupbyIdSelector(state, groupingId)) ?? defaultGroup; + useSelector((state: State) => getGroupByIdSelector(state, groupingId)) ?? defaultGroup; const { browserFields, @@ -237,41 +236,50 @@ export const GroupedAlertsTableComponent: React.FC = [defaultFilters, getGlobalQuery, takeActionItems] ); - if (loading || isLoadingGroups || isEmpty(selectedPatterns)) { + const groupedAlerts = useMemo( + () => + isNoneGroup(selectedGroup) ? ( + renderChildComponent([]) + ) : ( + + getSelectedGroupBadgeMetrics(selectedGroup, fieldBucket) + } + customMetricStats={(fieldBucket: RawBucket) => + getSelectedGroupCustomMetrics(selectedGroup, fieldBucket) + } + data={alertsGroupsData?.aggregations ?? {}} + groupPanelRenderer={(fieldBucket: RawBucket) => + getSelectedGroupButtonContent(selectedGroup, fieldBucket) + } + groupsSelector={groupsSelector} + inspectButton={inspect} + isLoading={loading || isLoadingGroups} + pagination={pagination} + renderChildComponent={renderChildComponent} + selectedGroup={selectedGroup} + takeActionItems={getTakeActionItems} + unit={defaultUnit} + /> + ), + [ + alertsGroupsData?.aggregations, + getTakeActionItems, + groupsSelector, + inspect, + isLoadingGroups, + loading, + pagination, + renderChildComponent, + selectedGroup, + ] + ); + + if (isEmpty(selectedPatterns)) { return null; } - const dataTable = renderChildComponent([]); - - return ( - <> - {isNoneGroup(selectedGroup) ? ( - dataTable - ) : ( - <> - - getSelectedGroupButtonContent(selectedGroup, fieldBucket) - } - badgeMetricStats={(fieldBucket: RawBucket) => - getSelectedGroupBadgeMetrics(selectedGroup, fieldBucket) - } - customMetricStats={(fieldBucket: RawBucket) => - getSelectedGroupCustomMetrics(selectedGroup, fieldBucket) - } - /> - - )} - - ); + return groupedAlerts; }; const makeMapStateToProps = () => { diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.test.tsx index f2d1c860bf390..8cb24295f62a4 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.test.tsx @@ -6,9 +6,8 @@ */ import React from 'react'; -import { shallow } from 'enzyme'; import { waitFor, render, fireEvent } from '@testing-library/react'; -import type { Filter, Query } from '@kbn/es-query'; +import type { Filter } from '@kbn/es-query'; import useResizeObserver from 'use-resize-observer/polyfilled'; import '../../../common/mock/match_media'; @@ -19,6 +18,7 @@ import { SUB_PLUGINS_REDUCER, TestProviders, } from '../../../common/mock'; +import type { AlertsTableComponentProps } from './grouped_alerts'; import { GroupedAlertsTableComponent } from './grouped_alerts'; import { TableId } from '../../../../common/types'; import { useSourcererDataView } from '../../../common/containers/sourcerer'; @@ -28,8 +28,8 @@ import { mockTimelines } from '../../../common/mock/mock_timelines_plugin'; import { createFilterManagerMock } from '@kbn/data-plugin/public/query/filter_manager/filter_manager.mock'; import type { State } from '../../../common/store'; import { createStore } from '../../../common/store'; -import { AlertsTableComponent } from '.'; import { createStartServicesMock } from '../../../common/lib/kibana/kibana_react.mock'; +import { defaultGroup } from '../../../common/store/grouping/defaults'; jest.mock('../../../common/containers/sourcerer'); jest.mock('../../../common/containers/use_global_time', () => ({ @@ -149,6 +149,18 @@ const state: State = { const { storage } = createSecuritySolutionStorageMock(); const store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage); +const groupingStore = createStore( + { + ...state, + groups: { + groupById: { [`${TableId.test}`]: { ...defaultGroup, activeGroup: 'host.name' } }, + }, + }, + SUB_PLUGINS_REDUCER, + kibanaObservable, + storage +); + jest.mock('./timeline_actions/use_add_bulk_to_timeline', () => ({ useAddBulkToTimelineAction: jest.fn(() => {}), })); @@ -165,52 +177,64 @@ const sourcererDataView = { }, browserFields: {}, }; +const renderChildComponent = (groupingFilters: Filter[]) =>

      ; -const from = '2020-07-07T08:20:18.966Z'; -const to = '2020-07-08T08:20:18.966Z'; -const renderChildComponent = (groupingFilters: Filter[]) => ( - -); +const testProps: AlertsTableComponentProps = { + defaultFilters: [], + dispatch: jest.fn(), + from: '2020-07-07T08:20:18.966Z', + globalFilters: [], + globalQuery: { + query: 'query', + language: 'language', + }, + hasIndexMaintenance: true, + hasIndexWrite: true, + loading: false, + renderChildComponent, + runtimeMappings: {}, + signalIndexName: 'test', + tableId: TableId.test, + to: '2020-07-08T08:20:18.966Z', +}; describe('GroupedAlertsTable', () => { - (useSourcererDataView as jest.Mock).mockReturnValue({ - ...sourcererDataView, - selectedPatterns: ['myFakebeat-*'], + beforeEach(() => { + jest.clearAllMocks(); + (useSourcererDataView as jest.Mock).mockReturnValue({ + ...sourcererDataView, + selectedPatterns: ['myFakebeat-*'], + }); }); - it('renders correctly', () => { - const wrapper = shallow( + it('renders alerts table when no group selected', () => { + const { getByTestId, queryByTestId } = render( - + ); + expect(getByTestId('alerts-table')).toBeInTheDocument(); + expect(queryByTestId('grouping-table')).not.toBeInTheDocument(); + }); - expect(wrapper.find('[title="Alerts"]')).toBeTruthy(); + it('renders grouped alerts when group selected', () => { + const { getByTestId, queryByTestId } = render( + + + + ); + expect(getByTestId('grouping-table')).toBeInTheDocument(); + expect(queryByTestId('alerts-table')).not.toBeInTheDocument(); + expect(queryByTestId('is-loading-grouping-table')).not.toBeInTheDocument(); + }); + + it('renders loading when expected', () => { + const { getByTestId } = render( + + + + ); + expect(getByTestId('is-loading-grouping-table')).toBeInTheDocument(); }); // Not a valid test as of now.. because, table is used from trigger actions.. diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_resolver.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_resolver.test.tsx index 4695e19d77126..6e5c5472820a6 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_resolver.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_resolver.test.tsx @@ -57,5 +57,27 @@ describe('InvestigateInResolverAction', () => { expect(isInvestigateInResolverActionEnabled(data)).toBeFalsy(); }); + + it('returns true for process event from sysmon via filebeat', () => { + const data: Ecs = { + _id: '1', + agent: { type: ['filebeat'] }, + event: { dataset: ['windows.sysmon_operational'] }, + process: { entity_id: ['always_unique'] }, + }; + + expect(isInvestigateInResolverActionEnabled(data)).toBeTruthy(); + }); + + it('returns false for process event from filebeat but not from sysmon', () => { + const data: Ecs = { + _id: '1', + agent: { type: ['filebeat'] }, + event: { dataset: ['windows.not_sysmon'] }, + process: { entity_id: ['always_unique'] }, + }; + + expect(isInvestigateInResolverActionEnabled(data)).toBeFalsy(); + }); }); }); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_resolver.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_resolver.tsx index 365aac96c8f48..f6b328a63b2c5 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_resolver.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_resolver.tsx @@ -8,9 +8,20 @@ import { get } from 'lodash/fp'; import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; -export const isInvestigateInResolverActionEnabled = (ecsData?: Ecs) => - (get(['agent', 'type', 0], ecsData) === 'endpoint' || - (get(['agent', 'type', 0], ecsData) === 'winlogbeat' && - get(['event', 'module', 0], ecsData) === 'sysmon')) && - get(['process', 'entity_id'], ecsData)?.length === 1 && - get(['process', 'entity_id', 0], ecsData) !== ''; +export const isInvestigateInResolverActionEnabled = (ecsData?: Ecs) => { + const agentType = get(['agent', 'type', 0], ecsData); + const processEntityIds = get(['process', 'entity_id'], ecsData); + const firstProcessEntityId = get(['process', 'entity_id', 0], ecsData); + const eventModule = get(['event', 'module', 0], ecsData); + const eventDataStream = get(['event', 'dataset'], ecsData); + const datasetIncludesSysmon = + Array.isArray(eventDataStream) && + eventDataStream.some((datastream) => datastream.includes('windows.sysmon')); + const agentTypeIsEndpoint = agentType === 'endpoint'; + const agentTypeIsWinlogBeat = agentType === 'winlogbeat' && eventModule === 'sysmon'; + const isEndpointOrSysmonFromWinlogBeat = + agentTypeIsEndpoint || agentTypeIsWinlogBeat || datasetIncludesSysmon; + const hasProcessEntityId = + processEntityIds != null && processEntityIds.length === 1 && firstProcessEntityId !== ''; + return isEndpointOrSysmonFromWinlogBeat && hasProcessEntityId; +}; diff --git a/x-pack/plugins/security_solution/public/detections/components/host_isolation/isolate.tsx b/x-pack/plugins/security_solution/public/detections/components/host_isolation/isolate.tsx index 1b47ec16714f8..044e893572151 100644 --- a/x-pack/plugins/security_solution/public/detections/components/host_isolation/isolate.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/host_isolation/isolate.tsx @@ -38,7 +38,11 @@ export const IsolateHost = React.memo( return caseInfo.id; }); - const { loading, isolateHost } = useHostIsolation({ endpointId, comment, caseIds }); + const { loading, isolateHost } = useHostIsolation({ + endpointId, + comment, + caseIds: caseIds.length > 0 ? caseIds : undefined, + }); const confirmHostIsolation = useCallback(async () => { const hostIsolated = await isolateHost(); diff --git a/x-pack/plugins/security_solution/public/exceptions/components/exceptions_list_card/index.tsx b/x-pack/plugins/security_solution/public/exceptions/components/exceptions_list_card/index.tsx index 54f2b0cf6c5d3..eb945e18e7761 100644 --- a/x-pack/plugins/security_solution/public/exceptions/components/exceptions_list_card/index.tsx +++ b/x-pack/plugins/security_solution/public/exceptions/components/exceptions_list_card/index.tsx @@ -50,11 +50,13 @@ interface ExceptionsListCardProps { id, includeExpiredExceptions, listId, + name, namespaceType, }: { id: string; includeExpiredExceptions: boolean; listId: string; + name: string; namespaceType: NamespaceType; }) => () => Promise; readOnly: boolean; diff --git a/x-pack/plugins/security_solution/public/exceptions/hooks/use_exceptions_list.card/index.tsx b/x-pack/plugins/security_solution/public/exceptions/hooks/use_exceptions_list.card/index.tsx index 5a8912a422f5c..d0ccd384156c7 100644 --- a/x-pack/plugins/security_solution/public/exceptions/hooks/use_exceptions_list.card/index.tsx +++ b/x-pack/plugins/security_solution/public/exceptions/hooks/use_exceptions_list.card/index.tsx @@ -24,6 +24,7 @@ import { checkIfListCannotBeEdited } from '../../utils/list.utils'; interface ExportListAction { id: string; listId: string; + name: string; namespaceType: NamespaceType; includeExpiredExceptions: boolean; } @@ -42,6 +43,7 @@ export const useExceptionsListCard = ({ handleExport: ({ id, listId, + name, namespaceType, includeExpiredExceptions, }: ExportListAction) => () => Promise; @@ -192,6 +194,7 @@ export const useExceptionsListCard = ({ handleExport({ id: exceptionsList.id, listId: exceptionsList.list_id, + name: exceptionsList.name, namespaceType: exceptionsList.namespace_type, includeExpiredExceptions, })(); diff --git a/x-pack/plugins/security_solution/public/exceptions/hooks/use_list_detail_view/index.ts b/x-pack/plugins/security_solution/public/exceptions/hooks/use_list_detail_view/index.ts index 3359ccb760b05..2494a6c989791 100644 --- a/x-pack/plugins/security_solution/public/exceptions/hooks/use_list_detail_view/index.ts +++ b/x-pack/plugins/security_solution/public/exceptions/hooks/use_list_detail_view/index.ts @@ -175,7 +175,7 @@ export const useListDetailsView = (exceptionListId: string) => { onError: (error: Error) => handleErrorStatus(error), onSuccess: (blob) => { setExportedList(blob); - toasts?.addSuccess(i18n.EXCEPTION_LIST_EXPORTED_SUCCESSFULLY(list.list_id)); + toasts?.addSuccess(i18n.EXCEPTION_LIST_EXPORTED_SUCCESSFULLY(list.name)); }, }); } catch (error) { diff --git a/x-pack/plugins/security_solution/public/exceptions/pages/shared_lists/index.tsx b/x-pack/plugins/security_solution/public/exceptions/pages/shared_lists/index.tsx index 533f920a0b40c..fe2f2450a3900 100644 --- a/x-pack/plugins/security_solution/public/exceptions/pages/shared_lists/index.tsx +++ b/x-pack/plugins/security_solution/public/exceptions/pages/shared_lists/index.tsx @@ -183,9 +183,9 @@ export const SharedLists = React.memo(() => { ); const handleExportSuccess = useCallback( - (listId: string) => + (listId: string, name: string) => (blob: Blob): void => { - addSuccess(i18n.EXCEPTION_EXPORT_SUCCESS); + addSuccess(i18n.EXCEPTION_LIST_EXPORTED_SUCCESSFULLY(name)); setExportDownload({ name: listId, blob }); }, [addSuccess] @@ -202,11 +202,13 @@ export const SharedLists = React.memo(() => { ({ id, listId, + name, namespaceType, includeExpiredExceptions, }: { id: string; listId: string; + name: string; namespaceType: NamespaceType; includeExpiredExceptions: boolean; }) => @@ -217,7 +219,7 @@ export const SharedLists = React.memo(() => { listId, namespaceType, onError: handleExportError, - onSuccess: handleExportSuccess(listId), + onSuccess: handleExportSuccess(listId, name), }); }, [exportExceptionList, handleExportError, handleExportSuccess] diff --git a/x-pack/plugins/security_solution/public/exceptions/translations/list_details_view.ts b/x-pack/plugins/security_solution/public/exceptions/translations/list_details_view.ts index c839089d307a9..d37c5e29b9fc2 100644 --- a/x-pack/plugins/security_solution/public/exceptions/translations/list_details_view.ts +++ b/x-pack/plugins/security_solution/public/exceptions/translations/list_details_view.ts @@ -94,12 +94,6 @@ export const EXCEPTION_ITEM_DELETE_TEXT = (itemName: string) => } ); -export const EXCEPTION_LIST_EXPORTED_SUCCESSFULLY = (listName: string) => - i18n.translate('xpack.securitySolution.exceptions.list.exported_successfully', { - values: { listName }, - defaultMessage: '{listName} exported successfully', - }); - export const EXCEPTION_LIST_DELETED_SUCCESSFULLY = (listName: string) => i18n.translate('xpack.securitySolution.exceptions.list.deleted_successfully', { values: { listName }, diff --git a/x-pack/plugins/security_solution/public/exceptions/translations/shared_list.ts b/x-pack/plugins/security_solution/public/exceptions/translations/shared_list.ts index 7addaeb331247..6ab1ca6df8464 100644 --- a/x-pack/plugins/security_solution/public/exceptions/translations/shared_list.ts +++ b/x-pack/plugins/security_solution/public/exceptions/translations/shared_list.ts @@ -112,12 +112,11 @@ export const NO_LISTS_BODY = i18n.translate( } ); -export const EXCEPTION_EXPORT_SUCCESS = i18n.translate( - 'xpack.securitySolution.detectionEngine.rules.all.exceptions.exportSuccess', - { - defaultMessage: 'Exception list export success', - } -); +export const EXCEPTION_LIST_EXPORTED_SUCCESSFULLY = (listName: string) => + i18n.translate('xpack.securitySolution.exceptions.list.export_success', { + values: { listName }, + defaultMessage: 'Exception list "{listName}" exported successfully', + }); export const EXCEPTION_EXPORT_ERROR = i18n.translate( 'xpack.securitySolution.detectionEngine.rules.all.exceptions.exportError', diff --git a/x-pack/plugins/security_solution/public/lazy_sub_plugins.tsx b/x-pack/plugins/security_solution/public/lazy_sub_plugins.tsx index 1d93699ff1b47..66aefc6db3e08 100644 --- a/x-pack/plugins/security_solution/public/lazy_sub_plugins.tsx +++ b/x-pack/plugins/security_solution/public/lazy_sub_plugins.tsx @@ -20,6 +20,7 @@ import { Rules } from './rules'; import { Timelines } from './timelines'; import { Management } from './management'; import { LandingPages } from './landing_pages'; +import { CloudDefend } from './cloud_defend'; import { CloudSecurityPosture } from './cloud_security_posture'; import { ThreatIntelligence } from './threat_intelligence'; @@ -37,6 +38,7 @@ const subPluginClasses = { Timelines, Management, LandingPages, + CloudDefend, CloudSecurityPosture, ThreatIntelligence, }; diff --git a/x-pack/plugins/security_solution/public/management/components/effected_policy_select/effected_policy_select.test.tsx b/x-pack/plugins/security_solution/public/management/components/effected_policy_select/effected_policy_select.test.tsx index 41eb4c3be7912..174496f1f3d21 100644 --- a/x-pack/plugins/security_solution/public/management/components/effected_policy_select/effected_policy_select.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/effected_policy_select/effected_policy_select.test.tsx @@ -13,6 +13,10 @@ import { fireEvent, act } from '@testing-library/react'; import { EndpointDocGenerator } from '../../../../common/endpoint/generate_data'; import type { AppContextTestRender } from '../../../common/mock/endpoint'; import { createAppRootMockRenderer } from '../../../common/mock/endpoint'; +import { useUserPrivileges } from '../../../common/components/user_privileges'; +import { initialUserPrivilegesState } from '../../../common/components/user_privileges/user_privileges_context'; + +jest.mock('../../../common/components/user_privileges'); describe('when using EffectedPolicySelect component', () => { const generator = new EndpointDocGenerator('effected-policy-select'); @@ -161,5 +165,44 @@ describe('when using EffectedPolicySelect component', () => { selectPerPolicy(); expect(queryByTestId('loading-spinner')).not.toBeNull(); }); + + it('should hide policy link when no policy management privileges', () => { + (useUserPrivileges as jest.Mock).mockReturnValue({ + ...initialUserPrivilegesState(), + endpointPrivileges: { + loading: false, + canWritePolicyManagement: false, + canReadPolicyManagement: false, + }, + }); + const { queryByTestId } = render({ isGlobal: false }); + expect(queryByTestId('test-policyLink')).toBeNull(); + }); + + it('should show policy link when all policy management privileges', () => { + (useUserPrivileges as jest.Mock).mockReturnValue({ + ...initialUserPrivilegesState(), + endpointPrivileges: { + loading: false, + canWritePolicyManagement: true, + canReadPolicyManagement: true, + }, + }); + const { getByTestId } = render({ isGlobal: false }); + expect(getByTestId('test-policyLink')); + }); + + it('should show policy link when read policy management privileges', () => { + (useUserPrivileges as jest.Mock).mockReturnValue({ + ...initialUserPrivilegesState(), + endpointPrivileges: { + loading: false, + canWritePolicyManagement: false, + canReadPolicyManagement: true, + }, + }); + const { getByTestId } = render({ isGlobal: false }); + expect(getByTestId('test-policyLink')); + }); }); }); diff --git a/x-pack/plugins/security_solution/public/management/components/effected_policy_select/effected_policy_select.tsx b/x-pack/plugins/security_solution/public/management/components/effected_policy_select/effected_policy_select.tsx index da5c1c1f68535..079ea01492dcc 100644 --- a/x-pack/plugins/security_solution/public/management/components/effected_policy_select/effected_policy_select.tsx +++ b/x-pack/plugins/security_solution/public/management/components/effected_policy_select/effected_policy_select.tsx @@ -22,6 +22,7 @@ import { i18n } from '@kbn/i18n'; import type { EuiSelectableOption } from '@elastic/eui/src/components/selectable/selectable_option'; import { FormattedMessage } from '@kbn/i18n-react'; import styled from 'styled-components'; +import { useUserPrivileges } from '../../../common/components/user_privileges'; import type { PolicyData } from '../../../../common/endpoint/types'; import { LinkToApp } from '../../../common/components/endpoint/link_to_app'; import { getPolicyDetailPath } from '../../common/routing'; @@ -104,6 +105,7 @@ export const EffectedPolicySelect = memo( ...otherSelectableProps }) => { const { getAppUrl } = useAppUrl(); + const { canReadPolicyManagement } = useUserPrivileges().endpointPrivileges; const getTestId = useTestIdGenerator(dataTestSubj); @@ -145,25 +147,35 @@ export const EffectedPolicySelect = memo( data-test-subj={`policy-${policy.id}-checkbox`} /> ), - append: ( + append: canReadPolicyManagement ? ( - ), + ) : null, policy, checked: isPolicySelected.has(policy.id) ? 'on' : undefined, disabled: isGlobal || !isPlatinumPlus || disabled, 'data-test-subj': `policy-${policy.id}`, })) .sort(({ label: labelA }, { label: labelB }) => labelA.localeCompare(labelB)); - }, [disabled, getAppUrl, isGlobal, isPlatinumPlus, options, selected]); + }, [ + canReadPolicyManagement, + disabled, + getAppUrl, + getTestId, + isGlobal, + isPlatinumPlus, + options, + selected, + ]); const handleOnPolicySelectChange = useCallback< Required>['onChange'] diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_execute_action/execute_action_host_response_output.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_execute_action/execute_action_host_response_output.test.tsx new file mode 100644 index 0000000000000..835e816f85f47 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_execute_action/execute_action_host_response_output.test.tsx @@ -0,0 +1,99 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { AppContextTestRender } from '../../../common/mock/endpoint'; +import { createAppRootMockRenderer } from '../../../common/mock/endpoint'; +import type { + ActionDetails, + ResponseActionExecuteOutputContent, + ResponseActionsExecuteParameters, +} from '../../../../common/endpoint/types'; +import React from 'react'; +import { EndpointActionGenerator } from '../../../../common/endpoint/data_generators/endpoint_action_generator'; +import { + ExecuteActionHostResponseOutput, + type ExecuteActionHostResponseOutputProps, +} from './execute_action_host_response_output'; +import { responseActionsHttpMocks } from '../../mocks/response_actions_http_mocks'; +import { getDeferred } from '../../mocks/utils'; +import { waitFor } from '@testing-library/react'; +import type { IHttpFetchError } from '@kbn/core-http-browser'; + +describe('When using the `ExecuteActionHostResponseOutput` component', () => { + let render: () => ReturnType; + let renderResult: ReturnType; + let renderProps: ExecuteActionHostResponseOutputProps; + let apiMocks: ReturnType; + + beforeEach(() => { + const appTestContext = createAppRootMockRenderer(); + + apiMocks = responseActionsHttpMocks(appTestContext.coreStart.http); + + renderProps = { + action: new EndpointActionGenerator('seed').generateActionDetails< + ResponseActionExecuteOutputContent, + ResponseActionsExecuteParameters + >({ command: 'execute' }), + 'data-test-subj': 'test', + }; + + render = () => { + renderResult = appTestContext.render(); + return renderResult; + }; + }); + + it('should show execute output and execute errors', async () => { + render(); + expect(renderResult.getByTestId('test')).toBeTruthy(); + }); + + it('should show loading when details are fetching', async () => { + (renderProps.action as ActionDetails).outputs = {}; + + const deferred = getDeferred(); + + apiMocks.responseProvider.actionDetails.mockDelay.mockReturnValue(deferred.promise); + (renderProps.action as ActionDetails).completedAt = '2021-04-15T16:08:47.449Z'; + + render(); + expect(renderResult.getByTestId('test-loading')).toBeTruthy(); + + // Release the `action details` api + deferred.resolve(); + + await waitFor(() => { + expect(apiMocks.responseProvider.actionDetails).toHaveBeenCalledWith({ + path: '/api/endpoint/action/123', + }); + }); + expect(renderResult.queryByTestId('test-loading')).toBeNull(); + expect(renderResult.getByTestId('test')).toBeTruthy(); + }); + + it('should show nothing when no output in action details', () => { + (renderProps.action as ActionDetails).outputs = {}; + render(); + expect(renderResult.queryByTestId('test')).toBeNull(); + }); + + it('should handle API error', async () => { + (renderProps.action as ActionDetails).outputs = {}; + const error = { message: 'server error', response: { status: 500 } } as IHttpFetchError; + + (renderProps.action as ActionDetails).completedAt = '2021-04-15T16:08:47.449Z'; + apiMocks.responseProvider.actionDetails.mockImplementation(() => { + throw error; + }); + + render(); + + await waitFor(() => { + expect(renderResult.getByTestId('test-apiError')).toHaveTextContent('server error'); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_execute_action/execute_action_host_response_output.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_execute_action/execute_action_host_response_output.tsx index 3624f577716dd..959c6ad0ce5e0 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_execute_action/execute_action_host_response_output.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_execute_action/execute_action_host_response_output.tsx @@ -4,27 +4,24 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React, { memo, useMemo } from 'react'; +import React, { memo, useEffect, useMemo, useState } from 'react'; import { EuiAccordion, - EuiFlexGroup, EuiFlexItem, + EuiSkeletonText, EuiSpacer, EuiText, useGeneratedHtmlId, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { ResponseActionFileDownloadLink } from '../response_action_file_download_link'; +import { useIsMounted } from '@kbn/securitysolution-hook-utils'; +import { useGetActionDetails } from '../../hooks/response_actions/use_get_action_details'; import type { ActionDetails, MaybeImmutable, ResponseActionExecuteOutputContent, } from '../../../../common/endpoint/types'; - -const EXECUTE_FILE_LINK_TITLE = i18n.translate( - 'xpack.securitySolution.responseActionExecuteDownloadLink.downloadButtonLabel', - { defaultMessage: 'Click here to download full output' } -); +import { FormattedError } from '../formatted_error'; const ACCORDION_BUTTON_TEXT = Object.freeze({ output: { @@ -57,14 +54,15 @@ const ACCORDION_BUTTON_TEXT = Object.freeze({ }, }); interface ExecuteActionOutputProps { - content: string; + content?: string; initialIsOpen?: boolean; - isTruncated: boolean; + isTruncated?: boolean; + textSize?: 's' | 'xs'; type: 'error' | 'output'; } const ExecutionActionOutputAccordion = memo( - ({ content, initialIsOpen = false, isTruncated, type }) => { + ({ content, initialIsOpen = false, isTruncated = false, textSize, type }) => { const id = useGeneratedHtmlId({ prefix: 'executeActionOutputAccordions', suffix: type, @@ -77,7 +75,7 @@ const ExecutionActionOutputAccordion = memo( paddingSize="s" > ( ); ExecutionActionOutputAccordion.displayName = 'ExecutionActionOutputAccordion'; -interface ExecuteActionHostResponseOutputProps { +export interface ExecuteActionHostResponseOutputProps { action: MaybeImmutable; agentId?: string; 'data-test-subj'?: string; @@ -99,42 +97,75 @@ interface ExecuteActionHostResponseOutputProps { } export const ExecuteActionHostResponseOutput = memo( - ({ action, agentId = action.agents[0], 'data-test-subj': dataTestSubj, textSize }) => { + ({ action, agentId = action.agents[0], 'data-test-subj': dataTestSubj, textSize = 'xs' }) => { + const isMounted = useIsMounted(); const outputContent = useMemo( () => action.outputs && action.outputs[agentId] && (action.outputs[agentId].content as ResponseActionExecuteOutputContent), - [agentId, action] + [action.outputs, agentId] ); - return ( - - - - + const { + error, + data: actionDetails, + isFetching, + isFetched, + } = useGetActionDetails(action.id, { + enabled: !outputContent, + }); + + const [executeOutputContent, setExecuteOutputContent] = useState< + undefined | ResponseActionExecuteOutputContent + >(outputContent); - {outputContent && ( - - - - - - )} - + useEffect(() => { + if ( + isMounted() && + isFetched && + actionDetails && + actionDetails.data && + actionDetails.data.outputs && + actionDetails.data.outputs[agentId] + ) { + setExecuteOutputContent( + actionDetails.data.outputs[agentId].content as ResponseActionExecuteOutputContent + ); + } + + return () => { + setExecuteOutputContent(undefined); + }; + }, [actionDetails, agentId, isFetched, isMounted]); + + if (isFetching && !executeOutputContent) { + return ( + + ); + } + + if (error) { + return ; + } + + return ( + + + + + ); } ); diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/execute_action.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/execute_action.tsx index fe766f2015516..e5ea73de39cf4 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/execute_action.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/execute_action.tsx @@ -75,6 +75,8 @@ export const ExecuteActionResult = memo< ); diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/get_file_action.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/get_file_action.tsx index b65e13003c263..13ad9da09637a 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/get_file_action.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/get_file_action.tsx @@ -7,6 +7,7 @@ import React, { memo, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; +import { useUserPrivileges } from '../../../../common/components/user_privileges'; import { useSendGetFileRequest } from '../../../hooks/response_actions/use_send_get_file_request'; import type { ResponseActionGetFileRequestBody } from '../../../../../common/endpoint/schema/actions'; import { useConsoleActionSubmitter } from '../hooks/use_console_action_submitter'; @@ -18,6 +19,7 @@ export const GetFileActionResult = memo< path: string[]; }> >(({ command, setStore, store, status, setStatus, ResultComponent }) => { + const { canWriteFileOperations } = useUserPrivileges().endpointPrivileges; const actionCreator = useSendGetFileRequest(); const actionRequestBody = useMemo(() => { @@ -59,7 +61,10 @@ export const GetFileActionResult = memo< { defaultMessage: 'File retrieved from the host.' } )} > - + ); } diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/action_log_expanded_tray.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/action_log_expanded_tray.tsx new file mode 100644 index 0000000000000..5be50866a9130 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/action_log_expanded_tray.tsx @@ -0,0 +1,237 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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, useMemo } from 'react'; +import { EuiCodeBlock, EuiFlexGroup, EuiFlexItem, EuiDescriptionList } from '@elastic/eui'; +import { css, euiStyled } from '@kbn/kibana-react-plugin/common'; +import { i18n } from '@kbn/i18n'; +import { useUserPrivileges } from '../../../../common/components/user_privileges'; +import { OUTPUT_MESSAGES } from '../translations'; +import { getUiCommand } from './hooks'; +import { useTestIdGenerator } from '../../../hooks/use_test_id_generator'; +import { ResponseActionFileDownloadLink } from '../../response_action_file_download_link'; +import { ExecuteActionHostResponseOutput } from '../../endpoint_execute_action'; +import { getEmptyValue } from '../../../../common/components/empty_value'; + +import { type ActionDetails, type MaybeImmutable } from '../../../../../common/endpoint/types'; +const EXECUTE_FILE_LINK_TITLE = i18n.translate( + 'xpack.securitySolution.responseActionExecuteDownloadLink.downloadButtonLabel', + { defaultMessage: 'Click here to download full output' } +); +const emptyValue = getEmptyValue(); + +const customDescriptionListCss = css` + &.euiDescriptionList { + > .euiDescriptionList__title { + color: ${(props) => props.theme.eui.euiColorDarkShade}; + font-size: ${(props) => props.theme.eui.euiFontSizeXS}; + } + + > .euiDescriptionList__title, + > .euiDescriptionList__description { + font-weight: ${(props) => props.theme.eui.euiFontWeightRegular}; + margin-top: ${(props) => props.theme.eui.euiSizeS}; + } + } +`; +const topSpacingCss = css` + ${(props) => `${props.theme.eui.euiSize} 0`} +`; +const dashedBorderCss = css` + ${(props) => `1px dashed ${props.theme.eui.euiColorDisabled}`}; +`; +const StyledDescriptionListOutput = euiStyled(EuiDescriptionList).attrs({ compressed: true })` + ${customDescriptionListCss} + dd { + margin: ${topSpacingCss}; + padding: ${topSpacingCss}; + border-top: ${dashedBorderCss}; + border-bottom: ${dashedBorderCss}; + } +`; + +const StyledDescriptionList = euiStyled(EuiDescriptionList).attrs({ + compressed: true, + type: 'column', +})` + ${customDescriptionListCss} +`; + +const StyledEuiCodeBlock = euiStyled(EuiCodeBlock).attrs({ + transparentBackground: true, + paddingSize: 'none', +})` + code { + color: ${(props) => props.theme.eui.euiColorDarkShade} !important; + } +`; + +const StyledEuiFlexGroup = euiStyled(EuiFlexGroup).attrs({ + direction: 'column', + className: 'eui-yScrollWithShadows', + gutterSize: 's', +})` + max-height: 270px; + overflow-y: auto; +`; + +const OutputContent = memo<{ action: MaybeImmutable; 'data-test-subj'?: string }>( + ({ action, 'data-test-subj': dataTestSubj }) => { + const getTestId = useTestIdGenerator(dataTestSubj); + + const { canWriteFileOperations, canWriteExecuteOperations } = + useUserPrivileges().endpointPrivileges; + + const { command, isCompleted, isExpired, wasSuccessful } = action; + + if (isExpired) { + return <>{OUTPUT_MESSAGES.hasExpired(command)}; + } + + if (!isCompleted) { + return <>{OUTPUT_MESSAGES.isPending(command)}; + } + + if (!wasSuccessful) { + return <>{OUTPUT_MESSAGES.hasFailed(command)}; + } + + if (command === 'get-file') { + return ( + <> + {OUTPUT_MESSAGES.wasSuccessful(command)} + + + ); + } + + if (command === 'execute') { + return ( + + {action.agents.map((agentId) => ( +

      + {OUTPUT_MESSAGES.wasSuccessful(command)} + + + + +
      + ))} +
      + ); + } + + return <>{OUTPUT_MESSAGES.wasSuccessful(command)}; + } +); + +OutputContent.displayName = 'OutputContent'; + +export const ActionsLogExpandedTray = memo<{ + action: MaybeImmutable; + 'data-test-subj'?: string; +}>(({ action, 'data-test-subj': dataTestSubj }) => { + const getTestId = useTestIdGenerator(dataTestSubj); + + const { startedAt, completedAt, command: _command, comment, parameters } = action; + + const parametersList = useMemo( + () => + parameters + ? Object.entries(parameters).map(([key, value]) => { + return `${key}:${value}`; + }) + : undefined, + [parameters] + ); + + const command = getUiCommand(_command); + + const dataList = useMemo( + () => + [ + { + title: OUTPUT_MESSAGES.expandSection.placedAt, + description: `${startedAt}`, + }, + { + title: OUTPUT_MESSAGES.expandSection.startedAt, + description: `${startedAt}`, + }, + { + title: OUTPUT_MESSAGES.expandSection.completedAt, + description: `${completedAt ?? emptyValue}`, + }, + { + title: OUTPUT_MESSAGES.expandSection.input, + description: `${command}`, + }, + { + title: OUTPUT_MESSAGES.expandSection.parameters, + description: parametersList ? parametersList : emptyValue, + }, + { + title: OUTPUT_MESSAGES.expandSection.comment, + description: comment ? comment : emptyValue, + }, + ].map(({ title, description }) => { + return { + title: {title}, + description: {description}, + }; + }), + [command, comment, completedAt, parametersList, startedAt] + ); + + const outputList = useMemo( + () => [ + { + title: ( + {`${OUTPUT_MESSAGES.expandSection.output}:`} + ), + description: ( + // codeblock for output + + + + ), + }, + ], + [action, dataTestSubj, getTestId] + ); + + return ( + <> + + + + + + + + + + ); +}); + +ActionsLogExpandedTray.displayName = 'ActionsLogExpandedTray'; diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_date_range_picker.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_date_range_picker.tsx index af0988040f80e..fef40433c6848 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_date_range_picker.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_date_range_picker.tsx @@ -41,6 +41,7 @@ export const ActionLogDateRangePicker = memo( onRefresh, onRefreshChange, onTimeChange, + 'data-test-subj': dataTestSubj, }: { dateRangePickerState: DateRangePickerValues; isDataLoading: boolean; @@ -48,9 +49,10 @@ export const ActionLogDateRangePicker = memo( onRefresh: () => void; onRefreshChange: (evt: OnRefreshChangeProps) => void; onTimeChange: ({ start, end }: DurationRange) => void; + 'data-test-subj'?: string; }) => { const { startDate: startDateFromUrl, endDate: endDateFromUrl } = useActionHistoryUrlParams(); - const getTestId = useTestIdGenerator('response-actions-list'); + const getTestId = useTestIdGenerator(dataTestSubj); const kibana = useKibana(); const { uiSettings } = kibana.services; const [commonlyUsedRanges] = useState(() => { diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_filter.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_filter.tsx index 8482e275f7811..8b77b8152a177 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_filter.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_filter.tsx @@ -20,12 +20,14 @@ export const ActionsLogFilter = memo( filterName, isFlyout, onChangeFilterOptions, + 'data-test-subj': dataTestSubj, }: { filterName: FilterName; isFlyout: boolean; onChangeFilterOptions: (selectedOptions: string[]) => void; + 'data-test-subj'?: string; }) => { - const getTestId = useTestIdGenerator('response-actions-list'); + const getTestId = useTestIdGenerator(dataTestSubj); // popover states and handlers const [isPopoverOpen, setIsPopoverOpen] = useState(false); @@ -177,6 +179,7 @@ export const ActionsLogFilter = memo( numActiveFilters={numActiveFilters} numFilters={numFilters} onButtonClick={onPopoverButtonClick} + data-test-subj={dataTestSubj} > void; @@ -30,8 +31,9 @@ export const ActionsLogFilterPopover = memo( numActiveFilters: number; numFilters: number; onButtonClick: () => void; + 'data-test-subj'?: string; }) => { - const getTestId = useTestIdGenerator('response-actions-list'); + const getTestId = useTestIdGenerator(dataTestSubj); const filterGroupPopoverId = useGeneratedHtmlId({ prefix: 'filterGroupPopover', diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_filters.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_filters.tsx index 9fb8cce0f291a..9215fd5fa3cee 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_filters.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_filters.tsx @@ -33,6 +33,7 @@ export const ActionsLogFilters = memo( onRefreshChange, onTimeChange, showHostsFilter, + 'data-test-subj': dataTestSubj, }: { dateRangePickerState: DateRangePickerValues; isDataLoading: boolean; @@ -46,8 +47,9 @@ export const ActionsLogFilters = memo( onTimeChange: ({ start, end }: DurationRange) => void; onClick: ReturnType['refetch']; showHostsFilter: boolean; + 'data-test-subj'?: string; }) => { - const getTestId = useTestIdGenerator('response-actions-list'); + const getTestId = useTestIdGenerator(dataTestSubj); const filters = useMemo(() => { return ( <> @@ -56,21 +58,25 @@ export const ActionsLogFilters = memo( filterName={'hosts'} isFlyout={isFlyout} onChangeFilterOptions={onChangeHostsFilter} + data-test-subj={dataTestSubj} /> )} ); }, [ + dataTestSubj, isFlyout, onChangeCommandsFilter, onChangeHostsFilter, @@ -83,7 +89,11 @@ export const ActionsLogFilters = memo( return ( - + {filters} @@ -96,6 +106,7 @@ export const ActionsLogFilters = memo( onRefresh={onRefresh} onRefreshChange={onRefreshChange} onTimeChange={onTimeChange} + data-test-subj={dataTestSubj} /> diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_table.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_table.tsx new file mode 100644 index 0000000000000..09767754dc7fb --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_table.tsx @@ -0,0 +1,387 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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, useMemo, useState } from 'react'; + +import type { CriteriaWithPagination } from '@elastic/eui'; +import { + EuiI18nNumber, + EuiAvatar, + EuiBasicTable, + EuiButtonIcon, + EuiFacetButton, + EuiHorizontalRule, + RIGHT_ALIGNMENT, + EuiScreenReaderOnly, + EuiText, + EuiToolTip, + type HorizontalAlignment, +} from '@elastic/eui'; +import { euiStyled } from '@kbn/kibana-react-plugin/common'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import type { ActionListApiResponse } from '../../../../../common/endpoint/types'; +import type { EndpointActionListRequestQuery } from '../../../../../common/endpoint/schema/actions'; +import { FormattedDate } from '../../../../common/components/formatted_date'; +import { TABLE_COLUMN_NAMES, UX_MESSAGES, ARIA_LABELS } from '../translations'; +import { getActionStatus, getUiCommand } from './hooks'; +import { getEmptyValue } from '../../../../common/components/empty_value'; +import { StatusBadge } from './status_badge'; +import { ActionsLogExpandedTray } from './action_log_expanded_tray'; +import { useTestIdGenerator } from '../../../hooks/use_test_id_generator'; +import { MANAGEMENT_PAGE_SIZE_OPTIONS } from '../../../common/constants'; +import { useActionHistoryUrlParams } from './use_action_history_url_params'; +import { useUrlPagination } from '../../../hooks/use_url_pagination'; + +const emptyValue = getEmptyValue(); + +// Truncated usernames +const StyledFacetButton = euiStyled(EuiFacetButton)` + .euiText { + margin-top: 0.38rem; + overflow-y: visible !important; + } +`; + +interface ActionsLogTableProps { + error?: string; + 'data-test-subj'?: string; + items: ActionListApiResponse['data']; + isFlyout: boolean; + loading: boolean; + onChange: ({ + page: _page, + }: CriteriaWithPagination) => void; + queryParams: EndpointActionListRequestQuery; + showHostNames: boolean; + totalItemCount: number; +} +interface ExpandedRowMapType { + [k: ActionListApiResponse['data'][number]['id']]: React.ReactNode; +} + +export const ActionsLogTable = memo( + ({ + 'data-test-subj': dataTestSubj, + error, + items, + isFlyout, + loading, + onChange, + queryParams, + showHostNames, + totalItemCount, + }) => { + const getTestId = useTestIdGenerator(dataTestSubj); + + const { pagination: paginationFromUrlParams } = useUrlPagination(); + const { withOutputs, setUrlWithOutputs } = useActionHistoryUrlParams(); + + const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState( + withOutputs + ? withOutputs.reduce((idToRowMap, actionId) => { + if (actionId.length) { + idToRowMap[actionId] = ( + item.id === actionId)[0]} + data-test-subj={dataTestSubj} + /> + ); + } + return idToRowMap; + }, {}) + : {} + ); + + const toggleDetails = useCallback( + (item: ActionListApiResponse['data'][number]) => { + const itemIdToExpandedRowMapValues = { ...itemIdToExpandedRowMap }; + if (itemIdToExpandedRowMapValues[item.id]) { + // close tray + delete itemIdToExpandedRowMapValues[item.id]; + } else { + // expanded tray contents + itemIdToExpandedRowMapValues[item.id] = ( + + ); + } + const expandedActionIds = Object.keys(itemIdToExpandedRowMapValues); + if (!isFlyout) { + // set and show `withOutputs` URL param on history page + setUrlWithOutputs(expandedActionIds.join()); + } + setItemIdToExpandedRowMap(itemIdToExpandedRowMapValues); + }, + [dataTestSubj, isFlyout, itemIdToExpandedRowMap, setUrlWithOutputs] + ); + // memoized callback for toggleDetails + const onClickCallback = useCallback( + (actionListDataItem: ActionListApiResponse['data'][number]) => () => + toggleDetails(actionListDataItem), + [toggleDetails] + ); + + const responseActionListColumns = useMemo(() => { + const columns = [ + { + field: 'startedAt', + name: TABLE_COLUMN_NAMES.time, + width: !showHostNames ? '21%' : '15%', + truncateText: true, + render: (startedAt: ActionListApiResponse['data'][number]['startedAt']) => { + return ( + + ); + }, + }, + { + field: 'command', + name: TABLE_COLUMN_NAMES.command, + width: !showHostNames ? '21%' : '10%', + truncateText: true, + render: (_command: ActionListApiResponse['data'][number]['command']) => { + const command = getUiCommand(_command); + return ( + + + {command} + + + ); + }, + }, + { + field: 'createdBy', + name: TABLE_COLUMN_NAMES.user, + width: !showHostNames ? '21%' : '14%', + truncateText: true, + render: (userId: ActionListApiResponse['data'][number]['createdBy']) => { + return ( + + } + > + + + {userId} + + + + ); + }, + }, + // conditional hostnames column + { + field: 'hosts', + name: TABLE_COLUMN_NAMES.hosts, + width: '20%', + truncateText: true, + render: (_hosts: ActionListApiResponse['data'][number]['hosts']) => { + const hosts = _hosts && Object.values(_hosts); + // join hostnames if the action is for multiple agents + // and skip empty strings for names if any + const _hostnames = hosts + .reduce((acc, host) => { + if (host.name.trim()) { + acc.push(host.name); + } + return acc; + }, []) + .join(', '); + + let hostnames = _hostnames; + if (!_hostnames) { + if (hosts.length > 1) { + // when action was for a single agent and no host name + hostnames = UX_MESSAGES.unenrolled.hosts; + } else if (hosts.length === 1) { + // when action was for a multiple agents + // and none of them have a host name + hostnames = UX_MESSAGES.unenrolled.host; + } + } + return ( + + + {hostnames} + + + ); + }, + }, + { + field: 'comment', + name: TABLE_COLUMN_NAMES.comments, + width: !showHostNames ? '21%' : '30%', + truncateText: true, + render: (comment: ActionListApiResponse['data'][number]['comment']) => { + return ( + + + {comment ?? emptyValue} + + + ); + }, + }, + { + field: 'status', + name: TABLE_COLUMN_NAMES.status, + width: !showHostNames ? '15%' : '10%', + render: (_status: ActionListApiResponse['data'][number]['status']) => { + const status = getActionStatus(_status); + + return ( + + + + ); + }, + }, + { + field: '', + align: RIGHT_ALIGNMENT as HorizontalAlignment, + width: '40px', + isExpander: true, + name: ( + + {UX_MESSAGES.screenReaderExpand} + + ), + render: (actionListDataItem: ActionListApiResponse['data'][number]) => { + return ( + + ); + }, + }, + ]; + // filter out the `hosts` column + // if showHostNames is FALSE + if (!showHostNames) { + return columns.filter((column) => column.field !== 'hosts'); + } + return columns; + }, [showHostNames, getTestId, itemIdToExpandedRowMap, onClickCallback]); + + // table pagination + const tablePagination = useMemo(() => { + return { + pageIndex: isFlyout ? (queryParams.page || 1) - 1 : paginationFromUrlParams.page - 1, + pageSize: isFlyout ? queryParams.pageSize || 10 : paginationFromUrlParams.pageSize, + totalItemCount, + pageSizeOptions: MANAGEMENT_PAGE_SIZE_OPTIONS as number[], + }; + }, [ + isFlyout, + paginationFromUrlParams.page, + paginationFromUrlParams.pageSize, + queryParams.page, + queryParams.pageSize, + totalItemCount, + ]); + + // compute record ranges + const pagedResultsCount = useMemo(() => { + const page = queryParams.page ?? 1; + const perPage = queryParams?.pageSize ?? 10; + + const totalPages = Math.ceil(totalItemCount / perPage); + const fromCount = perPage * page - perPage + 1; + const toCount = + page === totalPages || totalPages === 1 ? totalItemCount : fromCount + perPage - 1; + return { fromCount, toCount }; + }, [queryParams.page, queryParams.pageSize, totalItemCount]); + + // create range label to display + const recordRangeLabel = useMemo( + () => ( + + + + {'-'} + + + ), + total: , + recordsLabel: {UX_MESSAGES.recordsLabel(totalItemCount)}, + }} + /> + + ), + [getTestId, pagedResultsCount.fromCount, pagedResultsCount.toCount, totalItemCount] + ); + + return ( + <> + {recordRangeLabel} + + + + ); + } +); + +ActionsLogTable.displayName = 'ActionsLogTable'; diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_users_filter.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_users_filter.test.tsx index 60b5f5368dafc..e6a4c4c242576 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_users_filter.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_users_filter.test.tsx @@ -23,7 +23,8 @@ describe('Users filter', () => { let history: AppContextTestRender['history']; let mockedContext: AppContextTestRender; - const testPrefix = 'response-actions-list-users-filter'; + const testPrefix = 'test'; + const filterPrefix = 'users-filter'; let onChangeUsersFilter: jest.Mock; beforeEach(async () => { @@ -32,7 +33,11 @@ describe('Users filter', () => { ({ history } = mockedContext); render = (props?: React.ComponentProps) => (renderResult = mockedContext.render( - + )); reactTestingLibrary.act(() => { history.push(`${MANAGEMENT_PATH}/response_actions`); @@ -42,7 +47,7 @@ describe('Users filter', () => { it('should show a search input for users', () => { render(); - const searchInput = renderResult.getByTestId(`${testPrefix}-search`); + const searchInput = renderResult.getByTestId(`${testPrefix}-${filterPrefix}-search`); expect(searchInput).toBeTruthy(); expect(searchInput.getAttribute('placeholder')).toEqual('Filter by username'); }); @@ -50,7 +55,7 @@ describe('Users filter', () => { it('should search on given search string on enter', () => { render(); - const searchInput = renderResult.getByTestId(`${testPrefix}-search`); + const searchInput = renderResult.getByTestId(`${testPrefix}-${filterPrefix}-search`); userEvent.type(searchInput, 'usernameX'); userEvent.type(searchInput, '{enter}'); expect(onChangeUsersFilter).toHaveBeenCalledWith(['usernameX']); @@ -59,7 +64,7 @@ describe('Users filter', () => { it('should search comma separated strings as multiple users', () => { render(); - const searchInput = renderResult.getByTestId(`${testPrefix}-search`); + const searchInput = renderResult.getByTestId(`${testPrefix}-${filterPrefix}-search`); userEvent.type(searchInput, 'usernameX,usernameY,usernameZ'); userEvent.type(searchInput, '{enter}'); expect(onChangeUsersFilter).toHaveBeenCalledWith(['usernameX', 'usernameY', 'usernameZ']); @@ -68,7 +73,7 @@ describe('Users filter', () => { it('should ignore white spaces in a given username when updating the API params', () => { render(); - const searchInput = renderResult.getByTestId(`${testPrefix}-search`); + const searchInput = renderResult.getByTestId(`${testPrefix}-${filterPrefix}-search`); userEvent.type(searchInput, ' usernameX '); userEvent.type(searchInput, '{enter}'); expect(onChangeUsersFilter).toHaveBeenCalledWith(['usernameX']); @@ -77,7 +82,7 @@ describe('Users filter', () => { it('should ignore white spaces in comma separated usernames when updating the API params', () => { render(); - const searchInput = renderResult.getByTestId(`${testPrefix}-search`); + const searchInput = renderResult.getByTestId(`${testPrefix}-${filterPrefix}-search`); userEvent.type(searchInput, ' , usernameX ,usernameY , '); userEvent.type(searchInput, '{enter}'); expect(onChangeUsersFilter).toHaveBeenCalledWith(['usernameX', 'usernameY']); diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_users_filter.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_users_filter.tsx index d9de7986c5e80..68cbfbffeb261 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_users_filter.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_users_filter.tsx @@ -15,11 +15,13 @@ export const ActionsLogUsersFilter = memo( ({ isFlyout, onChangeUsersFilter, + 'data-test-subj': dataTestSubj, }: { isFlyout: boolean; onChangeUsersFilter: (selectedUserIds: string[]) => void; + 'data-test-subj'?: string; }) => { - const getTestId = useTestIdGenerator('response-actions-list'); + const getTestId = useTestIdGenerator(dataTestSubj); const { users: usersFromUrlParams, setUrlUsersFilters } = useActionHistoryUrlParams(); const [searchValue, setSearchValue] = useState(''); diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/status_badge.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/status_badge.tsx index fd8138a3f08e5..046811160c57d 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/status_badge.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/status_badge.tsx @@ -6,14 +6,19 @@ */ import React, { memo } from 'react'; import { EuiBadge, type EuiBadgeProps } from '@elastic/eui'; -import { useTestIdGenerator } from '../../../hooks/use_test_id_generator'; export const StatusBadge = memo( - ({ color, status }: { color: EuiBadgeProps['color']; status: string }) => { - const getTestId = useTestIdGenerator('response-actions-list'); - + ({ + color, + status, + 'data-test-subj': dataTestSubj, + }: { + color: EuiBadgeProps['color']; + 'data-test-subj'?: string; + status: string; + }) => { return ( - + {status} ); diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/use_action_history_url_params.ts b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/use_action_history_url_params.ts index a0e1623055f26..b483ed8a132cc 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/use_action_history_url_params.ts +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/use_action_history_url_params.ts @@ -22,11 +22,13 @@ interface UrlParamsActionsLogFilters { startDate: string; endDate: string; users: string; + withOutputs: string; } interface ActionsLogFiltersFromUrlParams { commands?: ConsoleResponseActionCommands[]; hosts?: string[]; + withOutputs?: string[]; statuses?: ResponseActionStatus[]; startDate?: string; endDate?: string; @@ -35,12 +37,13 @@ interface ActionsLogFiltersFromUrlParams { setUrlHostsFilters: (agentIds: UrlParamsActionsLogFilters['hosts']) => void; setUrlStatusesFilters: (statuses: UrlParamsActionsLogFilters['statuses']) => void; setUrlUsersFilters: (users: UrlParamsActionsLogFilters['users']) => void; + setUrlWithOutputs: (outputs: UrlParamsActionsLogFilters['withOutputs']) => void; users?: string[]; } type FiltersFromUrl = Pick< ActionsLogFiltersFromUrlParams, - 'commands' | 'hosts' | 'statuses' | 'users' | 'startDate' | 'endDate' + 'commands' | 'hosts' | 'withOutputs' | 'statuses' | 'users' | 'startDate' | 'endDate' >; export const actionsLogFiltersFromUrlParams = ( @@ -53,6 +56,7 @@ export const actionsLogFiltersFromUrlParams = ( startDate: 'now-24h/h', endDate: 'now', users: [], + withOutputs: [], }; const urlCommands = urlParams.commands @@ -72,6 +76,10 @@ export const actionsLogFiltersFromUrlParams = ( const urlHosts = urlParams.hosts ? String(urlParams.hosts).split(',').sort() : []; + const urlWithOutputs = urlParams.withOutputs + ? String(urlParams.withOutputs).split(',').sort() + : []; + const urlStatuses = urlParams.statuses ? (String(urlParams.statuses).split(',') as ResponseActionStatus[]).reduce< ResponseActionStatus[] @@ -91,6 +99,7 @@ export const actionsLogFiltersFromUrlParams = ( actionsLogFilters.startDate = urlParams.startDate ? String(urlParams.startDate) : undefined; actionsLogFilters.endDate = urlParams.endDate ? String(urlParams.endDate) : undefined; actionsLogFilters.users = urlUsers.length ? urlUsers : undefined; + actionsLogFilters.withOutputs = urlWithOutputs.length ? urlWithOutputs : undefined; return actionsLogFilters; }; @@ -133,6 +142,19 @@ export const useActionHistoryUrlParams = (): ActionsLogFiltersFromUrlParams => { [history, location, toUrlParams, urlParams] ); + const setUrlWithOutputs = useCallback( + (actionIds: string) => { + history.push({ + ...location, + search: toUrlParams({ + ...urlParams, + withOutputs: actionIds.length ? actionIds : undefined, + }), + }); + }, + [history, location, toUrlParams, urlParams] + ); + const setUrlStatusesFilters = useCallback( (statuses: string) => { history.push({ @@ -187,6 +209,7 @@ export const useActionHistoryUrlParams = (): ActionsLogFiltersFromUrlParams => { setUrlActionsFilters, setUrlDateRangeFilters, setUrlHostsFilters, + setUrlWithOutputs, setUrlStatusesFilters, setUrlUsersFilters, }; diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.test.tsx index ae8d652aeaeb9..39ef4f40211b8 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.test.tsx @@ -16,6 +16,7 @@ import { } from '../../../common/mock/endpoint'; import { ResponseActionsLog } from './response_actions_log'; import type { + ActionDetailsApiResponse, ActionFileInfoApiResponse, ActionListApiResponse, } from '../../../../common/endpoint/types'; @@ -138,6 +139,20 @@ jest.mock('../../hooks/response_actions/use_get_file_info', () => { }; }); +let mockUseGetActionDetails: { + isFetching?: boolean; + isFetched?: boolean; + error?: Partial | null; + data?: ActionDetailsApiResponse; +}; +jest.mock('../../hooks/response_actions/use_get_action_details', () => { + const original = jest.requireActual('../../hooks/response_actions/use_get_action_details'); + return { + ...original, + useGetActionDetails: () => mockUseGetActionDetails, + }; +}); + const mockUseGetEndpointsList = useGetEndpointsList as jest.Mock; const getBaseMockedActionList = () => ({ @@ -147,7 +162,7 @@ const getBaseMockedActionList = () => ({ refetch: jest.fn(), }); describe('Response actions history', () => { - const testPrefix = 'response-actions-list'; + const testPrefix = 'test'; let render: ( props?: React.ComponentProps @@ -161,7 +176,9 @@ describe('Response actions history', () => { mockedContext = createAppRootMockRenderer(); ({ history } = mockedContext); render = (props?: React.ComponentProps) => - (renderResult = mockedContext.render()); + (renderResult = mockedContext.render( + + )); reactTestingLibrary.act(() => { history.push(`${MANAGEMENT_PATH}/response_actions`); }); @@ -246,7 +263,7 @@ describe('Response actions history', () => { const { getByTestId } = renderResult; - expect(getByTestId(`${testPrefix}-table-view`)).toBeTruthy(); + expect(getByTestId(`${testPrefix}`)).toBeTruthy(); expect(getByTestId(`${testPrefix}-endpointListTableTotal`)).toHaveTextContent( 'Showing 1-10 of 13 response actions' ); @@ -256,9 +273,7 @@ describe('Response actions history', () => { render({ agentIds: 'agent-a' }); expect( - Array.from( - renderResult.getByTestId(`${testPrefix}-table-view`).querySelectorAll('thead th') - ) + Array.from(renderResult.getByTestId(`${testPrefix}`).querySelectorAll('thead th')) .slice(0, 6) .map((col) => col.textContent) ).toEqual(['Time', 'Command', 'User', 'Comments', 'Status', 'Expand rows']); @@ -268,9 +283,7 @@ describe('Response actions history', () => { render({ showHostNames: true }); expect( - Array.from( - renderResult.getByTestId(`${testPrefix}-table-view`).querySelectorAll('thead th') - ) + Array.from(renderResult.getByTestId(`${testPrefix}`).querySelectorAll('thead th')) .slice(0, 7) .map((col) => col.textContent) ).toEqual(['Time', 'Command', 'User', 'Hosts', 'Comments', 'Status', 'Expand rows']); @@ -347,7 +360,7 @@ describe('Response actions history', () => { render(); const { getByTestId } = renderResult; - expect(getByTestId(`${testPrefix}-table-view`)).toBeTruthy(); + expect(getByTestId(`${testPrefix}`)).toBeTruthy(); expect(getByTestId(`${testPrefix}-endpointListTableTotal`)).toHaveTextContent( 'Showing 1-10 of 13 response actions' ); @@ -368,7 +381,7 @@ describe('Response actions history', () => { render(); const { getByTestId } = renderResult; - expect(getByTestId(`${testPrefix}-table-view`)).toBeTruthy(); + expect(getByTestId(`${testPrefix}`)).toBeTruthy(); expect(getByTestId(`${testPrefix}-endpointListTableTotal`)).toHaveTextContent( 'Showing 1-10 of 33 response actions' ); @@ -438,91 +451,6 @@ describe('Response actions history', () => { ); }); - it('should contain download link in expanded row for `get-file` action WITH file operation permission', async () => { - mockUseGetEndpointActionList = { - ...getBaseMockedActionList(), - data: await getActionListMock({ actionCount: 1, commands: ['get-file'] }), - }; - - mockUseGetFileInfo = { - isFetching: false, - error: null, - data: apiMocks.responseProvider.fileInfo(), - }; - - render(); - - const { getByTestId } = renderResult; - const expandButton = getByTestId(`${testPrefix}-expand-button`); - userEvent.click(expandButton); - - await waitFor(() => { - expect(apiMocks.responseProvider.fileInfo).toHaveBeenCalled(); - }); - - const downloadLink = getByTestId(`${testPrefix}-getFileDownloadLink`); - expect(downloadLink).toBeTruthy(); - expect(downloadLink.textContent).toEqual( - 'Click here to download(ZIP file passcode: elastic).Files are periodically deleted to clear storage space. Download and save file locally if needed.' - ); - }); - - it('should show file unavailable for download for `get-file` action WITH file operation permission when file is deleted', async () => { - mockUseGetEndpointActionList = { - ...getBaseMockedActionList(), - data: await getActionListMock({ actionCount: 1, commands: ['get-file'] }), - }; - - const fileInfo = apiMocks.responseProvider.fileInfo(); - fileInfo.data.status = 'DELETED'; - - apiMocks.responseProvider.fileInfo.mockReturnValue(fileInfo); - - mockUseGetFileInfo = { - isFetching: false, - error: null, - data: apiMocks.responseProvider.fileInfo(), - }; - - render(); - - const { getByTestId } = renderResult; - const expandButton = getByTestId(`${testPrefix}-expand-button`); - userEvent.click(expandButton); - - await waitFor(() => { - expect(apiMocks.responseProvider.fileInfo).toHaveBeenCalled(); - }); - - const unavailableText = getByTestId( - `${testPrefix}-getFileDownloadLink-fileNoLongerAvailable` - ); - expect(unavailableText).toBeTruthy(); - }); - - it('should not contain download link in expanded row for `get-file` action when NO file operation permission', async () => { - useUserPrivilegesMock.mockReturnValue({ - endpointPrivileges: getEndpointAuthzInitialStateMock({ - canWriteFileOperations: false, - }), - }); - - mockUseGetEndpointActionList = { - ...getBaseMockedActionList(), - data: await getActionListMock({ actionCount: 1, commands: ['get-file'] }), - }; - - render(); - const { getByTestId, queryByTestId } = renderResult; - - const expandButton = getByTestId(`${testPrefix}-expand-button`); - userEvent.click(expandButton); - const output = getByTestId(`${testPrefix}-details-tray-output`); - expect(output).toBeTruthy(); - expect(output.textContent).toEqual('get-file completed successfully'); - expect(queryByTestId(`${testPrefix}-getFileDownloadLink`)).toBeNull(); - }); - it('should refresh data when autoRefresh is toggled on', async () => { mockUseGetEndpointActionList = getBaseMockedActionList(); render(); @@ -570,6 +498,217 @@ describe('Response actions history', () => { userEvent.click(getByTestId('superDatePickerCommonlyUsed_Last_15 minutes')); expect(startDatePopoverButton).toHaveTextContent('Last 15 minutes'); }); + + describe('`get-file` action', () => { + it('should contain download link in expanded row for `get-file` action WITH file operation permission', async () => { + useUserPrivilegesMock.mockReturnValue({ + endpointPrivileges: getEndpointAuthzInitialStateMock({ + canWriteExecuteOperations: false, + }), + }); + mockUseGetEndpointActionList = { + ...getBaseMockedActionList(), + data: await getActionListMock({ actionCount: 1, commands: ['get-file'] }), + }; + + mockUseGetFileInfo = { + isFetching: false, + error: null, + data: apiMocks.responseProvider.fileInfo(), + }; + + render(); + + const { getByTestId } = renderResult; + const expandButton = getByTestId(`${testPrefix}-expand-button`); + userEvent.click(expandButton); + + await waitFor(() => { + expect(apiMocks.responseProvider.fileInfo).toHaveBeenCalled(); + }); + + const downloadLink = getByTestId(`${testPrefix}-getFileDownloadLink`); + expect(downloadLink).toBeTruthy(); + expect(downloadLink.textContent).toEqual( + 'Click here to download(ZIP file passcode: elastic).Files are periodically deleted to clear storage space. Download and save file locally if needed.' + ); + }); + + it('should not contain download link in expanded row for `get-file` action when NO file operation permission', async () => { + useUserPrivilegesMock.mockReturnValue({ + endpointPrivileges: getEndpointAuthzInitialStateMock({ + canWriteFileOperations: false, + }), + }); + + mockUseGetEndpointActionList = { + ...getBaseMockedActionList(), + data: await getActionListMock({ actionCount: 1, commands: ['get-file'] }), + }; + + render(); + const { getByTestId, queryByTestId } = renderResult; + + const expandButton = getByTestId(`${testPrefix}-expand-button`); + userEvent.click(expandButton); + const output = getByTestId(`${testPrefix}-details-tray-output`); + expect(output).toBeTruthy(); + expect(output.textContent).toEqual('get-file completed successfully'); + expect(queryByTestId(`${testPrefix}-getFileDownloadLink`)).toBeNull(); + }); + }); + + describe('`execute` action', () => { + it('should contain full output download link in expanded row for `execute` action WITH execute operation privilege', async () => { + useUserPrivilegesMock.mockReturnValue({ + endpointPrivileges: getEndpointAuthzInitialStateMock({ + canWriteExecuteOperations: true, + canWriteFileOperations: false, + }), + }); + const actionDetails = await getActionListMock({ actionCount: 1, commands: ['execute'] }); + mockUseGetEndpointActionList = { + ...getBaseMockedActionList(), + data: actionDetails, + }; + + mockUseGetFileInfo = { + isFetching: false, + error: null, + data: apiMocks.responseProvider.fileInfo(), + }; + + mockUseGetActionDetails = { + isFetching: false, + isFetched: true, + error: null, + data: { + ...apiMocks.responseProvider.actionDetails({ + path: `/api/endpoint/action/${actionDetails.data[0].id}`, + }), + data: { + ...apiMocks.responseProvider.actionDetails({ + path: `/api/endpoint/action/${actionDetails.data[0].id}`, + }).data, + outputs: { + [actionDetails.data[0].agents[0]]: { + content: {}, + type: 'json', + }, + }, + }, + }, + }; + + render(); + + const { getByTestId } = renderResult; + const expandButton = getByTestId(`${testPrefix}-expand-button`); + userEvent.click(expandButton); + + await waitFor(() => { + expect(apiMocks.responseProvider.fileInfo).toHaveBeenCalled(); + }); + + const downloadExecuteLink = getByTestId(`${testPrefix}-getExecuteLink`); + expect(downloadExecuteLink).toBeTruthy(); + expect(downloadExecuteLink.textContent).toEqual( + 'Click here to download full output(ZIP file passcode: elastic).Files are periodically deleted to clear storage space. Download and save file locally if needed.' + ); + }); + + it('should contain execute output and error for `execute` action WITH execute operation privilege', async () => { + const actionDetails = await getActionListMock({ actionCount: 1, commands: ['execute'] }); + mockUseGetEndpointActionList = { + ...getBaseMockedActionList(), + data: actionDetails, + }; + + mockUseGetFileInfo = { + isFetching: false, + error: null, + data: apiMocks.responseProvider.fileInfo(), + }; + + mockUseGetActionDetails = { + isFetching: false, + isFetched: true, + error: null, + data: { + data: { + ...apiMocks.responseProvider.actionDetails({ + path: `/api/endpoint/action/${actionDetails.data[0].id}`, + }).data, + outputs: { + [actionDetails.data[0].agents[0]]: { + content: {}, + type: 'json', + }, + }, + }, + }, + }; + + render(); + + const { getByTestId } = renderResult; + const expandButton = getByTestId(`${testPrefix}-expand-button`); + userEvent.click(expandButton); + + await waitFor(() => { + expect(apiMocks.responseProvider.fileInfo).toHaveBeenCalled(); + }); + + const executeAccordions = getByTestId(`${testPrefix}-executeResponseOutput`); + expect(executeAccordions).toBeTruthy(); + expect(executeAccordions).toHaveTextContent('Execution outputExecution error'); + }); + + it('should contain execute output for `execute` action WITHOUT execute operation privilege', async () => { + useUserPrivilegesMock.mockReturnValue({ + endpointPrivileges: getEndpointAuthzInitialStateMock({ + canWriteExecuteOperations: false, + }), + }); + mockUseGetEndpointActionList = { + ...getBaseMockedActionList(), + data: await getActionListMock({ actionCount: 1, commands: ['execute'] }), + }; + + render(); + + const { getByTestId } = renderResult; + const expandButton = getByTestId(`${testPrefix}-expand-button`); + userEvent.click(expandButton); + + const executeAccordions = getByTestId(`${testPrefix}-executeResponseOutput`); + expect(executeAccordions).toBeTruthy(); + }); + + it('should not contain full output download link in expanded row for `execute` action WITHOUT execute operation privilege', async () => { + useUserPrivilegesMock.mockReturnValue({ + endpointPrivileges: getEndpointAuthzInitialStateMock({ + canWriteExecuteOperations: false, + }), + }); + + mockUseGetEndpointActionList = { + ...getBaseMockedActionList(), + data: await getActionListMock({ actionCount: 1, commands: ['execute'] }), + }; + + render(); + const { getByTestId, queryByTestId } = renderResult; + + const expandButton = getByTestId(`${testPrefix}-expand-button`); + userEvent.click(expandButton); + expect(queryByTestId(`${testPrefix}-getExecuteLink`)).toBeNull(); + + const output = getByTestId(`${testPrefix}-details-tray-output`); + expect(output).toBeTruthy(); + expect(output.textContent).toContain('execute completed successfully'); + }); + }); }); describe('Action status ', () => { diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.tsx index fc046ff574685..39601035250e9 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiBasicTable, EuiEmptyPrompt, EuiFlexItem, EuiHorizontalRule } from '@elastic/eui'; +import { EuiEmptyPrompt, EuiFlexItem } from '@elastic/eui'; import type { CriteriaWithPagination } from '@elastic/eui'; import React, { memo, useCallback, useEffect, useMemo, useState } from 'react'; @@ -27,247 +27,244 @@ import { useActionHistoryUrlParams } from './components/use_action_history_url_p import { useUrlPagination } from '../../hooks/use_url_pagination'; import { ManagementPageLoader } from '../management_page_loader'; import { ActionsLogEmptyState } from './components/actions_log_empty_state'; -import { useResponseActionsLogTable } from './use_response_actions_log_table'; +import { ActionsLogTable } from './components/actions_log_table'; export const ResponseActionsLog = memo< Pick & { showHostNames?: boolean; isFlyout?: boolean; setIsDataInResponse?: (isData: boolean) => void; + 'data-test-subj'?: string; } ->(({ agentIds, showHostNames = false, isFlyout = true, setIsDataInResponse }) => { - const { pagination: paginationFromUrlParams, setPagination: setPaginationOnUrlParams } = - useUrlPagination(); - const { - commands: commandsFromUrl, - hosts: agentIdsFromUrl, - statuses: statusesFromUrl, - startDate: startDateFromUrl, - endDate: endDateFromUrl, - users: usersFromUrl, - } = useActionHistoryUrlParams(); +>( + ({ + agentIds, + showHostNames = false, + isFlyout = true, + setIsDataInResponse, + 'data-test-subj': dataTestSubj = 'response-actions-list', + }) => { + const { pagination: paginationFromUrlParams, setPagination: setPaginationOnUrlParams } = + useUrlPagination(); + const { + commands: commandsFromUrl, + hosts: agentIdsFromUrl, + statuses: statusesFromUrl, + startDate: startDateFromUrl, + endDate: endDateFromUrl, + users: usersFromUrl, + } = useActionHistoryUrlParams(); - const getTestId = useTestIdGenerator('response-actions-list'); + const getTestId = useTestIdGenerator(dataTestSubj); - // Used to decide if display global loader or not (only the fist time tha page loads) - const [isFirstAttempt, setIsFirstAttempt] = useState(true); + // Used to decide if display global loader or not (only the fist time tha page loads) + const [isFirstAttempt, setIsFirstAttempt] = useState(true); - const [queryParams, setQueryParams] = useState({ - page: isFlyout ? 1 : paginationFromUrlParams.page, - pageSize: isFlyout ? 10 : paginationFromUrlParams.pageSize, - agentIds: isFlyout ? agentIds : agentIdsFromUrl?.length ? agentIdsFromUrl : agentIds, - commands: [], - statuses: [], - userIds: [], - }); - - // update query state from URL params - useEffect(() => { - if (!isFlyout) { - setQueryParams((prevState) => ({ - ...prevState, - commands: commandsFromUrl?.length - ? commandsFromUrl.map((commandFromUrl) => getCommandKey(commandFromUrl)) - : prevState.commands, - hosts: agentIdsFromUrl?.length ? agentIdsFromUrl : prevState.agentIds, - statuses: statusesFromUrl?.length - ? (statusesFromUrl as ResponseActionStatus[]) - : prevState.statuses, - userIds: usersFromUrl?.length ? usersFromUrl : prevState.userIds, - })); - } - }, [commandsFromUrl, agentIdsFromUrl, isFlyout, statusesFromUrl, setQueryParams, usersFromUrl]); + const [queryParams, setQueryParams] = useState({ + page: isFlyout ? 1 : paginationFromUrlParams.page, + pageSize: isFlyout ? 10 : paginationFromUrlParams.pageSize, + agentIds: isFlyout ? agentIds : agentIdsFromUrl?.length ? agentIdsFromUrl : agentIds, + commands: [], + statuses: [], + userIds: [], + }); - // date range picker state and handlers - const { dateRangePickerState, onRefreshChange, onTimeChange } = useDateRangePicker(isFlyout); + // update query state from URL params + useEffect(() => { + if (!isFlyout) { + setQueryParams((prevState) => ({ + ...prevState, + commands: commandsFromUrl?.length + ? commandsFromUrl.map((commandFromUrl) => getCommandKey(commandFromUrl)) + : prevState.commands, + hosts: agentIdsFromUrl?.length ? agentIdsFromUrl : prevState.agentIds, + statuses: statusesFromUrl?.length + ? (statusesFromUrl as ResponseActionStatus[]) + : prevState.statuses, + userIds: usersFromUrl?.length ? usersFromUrl : prevState.userIds, + })); + } + }, [commandsFromUrl, agentIdsFromUrl, isFlyout, statusesFromUrl, setQueryParams, usersFromUrl]); - // initial fetch of list data - const { - error, - data: actionList, - isFetching, - isFetched, - refetch: reFetchEndpointActionList, - } = useGetEndpointActionList( - { - ...queryParams, - startDate: isFlyout ? dateRangePickerState.startDate : startDateFromUrl, - endDate: isFlyout ? dateRangePickerState.endDate : endDateFromUrl, - }, - { retry: false } - ); + // date range picker state and handlers + const { dateRangePickerState, onRefreshChange, onTimeChange } = useDateRangePicker(isFlyout); - // total actions - const totalItemCount = useMemo(() => actionList?.total ?? 0, [actionList]); + // initial fetch of list data + const { + error, + data: actionList, + isFetching, + isFetched, + refetch: reFetchEndpointActionList, + } = useGetEndpointActionList( + { + ...queryParams, + startDate: isFlyout ? dateRangePickerState.startDate : startDateFromUrl, + endDate: isFlyout ? dateRangePickerState.endDate : endDateFromUrl, + }, + { retry: false } + ); - // table columns and expanded row state - const { itemIdToExpandedRowMap, recordRangeLabel, responseActionListColumns, tablePagination } = - useResponseActionsLogTable({ - showHostNames, - pageIndex: isFlyout ? (queryParams.page || 1) - 1 : paginationFromUrlParams.page - 1, - pageSize: isFlyout ? queryParams.pageSize || 10 : paginationFromUrlParams.pageSize, - queryParams, - totalItemCount, - }); + // total actions + const totalItemCount = useMemo(() => actionList?.total ?? 0, [actionList]); + // table items + const tableItems = useMemo(() => actionList?.data ?? [], [actionList?.data]); - // Hide page header when there is no actions index calling the setIsDataInResponse with false value. - // Otherwise, it shows the page header calling the setIsDataInResponse with true value and it also keeps track - // if the API request was done for the first time. - useEffect(() => { - if ( - !isFetching && - error?.body?.statusCode === 404 && - error?.body?.message === 'index_not_found_exception' - ) { - if (setIsDataInResponse) { - setIsDataInResponse(false); - } - } else if (!isFetching && actionList) { - setIsFirstAttempt(false); - if (setIsDataInResponse) { - setIsDataInResponse(true); + // Hide page header when there is no actions index calling the setIsDataInResponse with false value. + // Otherwise, it shows the page header calling the setIsDataInResponse with true value and it also keeps track + // if the API request was done for the first time. + useEffect(() => { + if ( + !isFetching && + error?.body?.statusCode === 404 && + error?.body?.message === 'index_not_found_exception' + ) { + if (setIsDataInResponse) { + setIsDataInResponse(false); + } + } else if (!isFetching && actionList) { + setIsFirstAttempt(false); + if (setIsDataInResponse) { + setIsDataInResponse(true); + } } - } - }, [actionList, error, isFetching, setIsDataInResponse]); + }, [actionList, error, isFetching, setIsDataInResponse]); - // handle auto refresh data - const onRefresh = useCallback(() => { - if (dateRangePickerState.autoRefreshOptions.enabled) { - reFetchEndpointActionList(); - } - }, [dateRangePickerState.autoRefreshOptions.enabled, reFetchEndpointActionList]); + // handle auto refresh data + const onRefresh = useCallback(() => { + if (dateRangePickerState.autoRefreshOptions.enabled) { + reFetchEndpointActionList(); + } + }, [dateRangePickerState.autoRefreshOptions.enabled, reFetchEndpointActionList]); - // handle on change actions filter - const onChangeCommandsFilter = useCallback( - (selectedCommands: string[]) => { - setQueryParams((prevState) => ({ - ...prevState, - commands: selectedCommands as ResponseActionsApiCommandNames[], - })); - }, - [setQueryParams] - ); + // handle on change actions filter + const onChangeCommandsFilter = useCallback( + (selectedCommands: string[]) => { + setQueryParams((prevState) => ({ + ...prevState, + commands: selectedCommands as ResponseActionsApiCommandNames[], + })); + }, + [setQueryParams] + ); - // handle on change actions filter - const onChangeStatusesFilter = useCallback( - (selectedStatuses: string[]) => { - setQueryParams((prevState) => ({ - ...prevState, - statuses: selectedStatuses as ResponseActionStatus[], - })); - }, - [setQueryParams] - ); + // handle on change actions filter + const onChangeStatusesFilter = useCallback( + (selectedStatuses: string[]) => { + setQueryParams((prevState) => ({ + ...prevState, + statuses: selectedStatuses as ResponseActionStatus[], + })); + }, + [setQueryParams] + ); - // handle on change hosts filter - const onChangeHostsFilter = useCallback( - (selectedAgentIds: string[]) => { - setQueryParams((prevState) => ({ ...prevState, agentIds: selectedAgentIds })); - }, - [setQueryParams] - ); + // handle on change hosts filter + const onChangeHostsFilter = useCallback( + (selectedAgentIds: string[]) => { + setQueryParams((prevState) => ({ ...prevState, agentIds: selectedAgentIds })); + }, + [setQueryParams] + ); - // handle on change users filter - const onChangeUsersFilter = useCallback( - (selectedUserIds: string[]) => { - setQueryParams((prevState) => ({ ...prevState, userIds: selectedUserIds })); - }, - [setQueryParams] - ); + // handle on change users filter + const onChangeUsersFilter = useCallback( + (selectedUserIds: string[]) => { + setQueryParams((prevState) => ({ ...prevState, userIds: selectedUserIds })); + }, + [setQueryParams] + ); - // handle onChange - const handleTableOnChange = useCallback( - ({ page: _page }: CriteriaWithPagination) => { - // table paging is 0 based - const { index, size } = _page; - // adjust the page to conform to - // 1-based API page - const pagingArgs = { - page: index + 1, - pageSize: size, - }; + // handle onChange + const handleTableOnChange = useCallback( + ({ page: _page }: CriteriaWithPagination) => { + // table paging is 0 based + const { index, size } = _page; + // adjust the page to conform to + // 1-based API page + const pagingArgs = { + page: index + 1, + pageSize: size, + }; - setQueryParams((prevState) => ({ - ...prevState, - ...pagingArgs, - })); - if (!isFlyout) { - setPaginationOnUrlParams({ + setQueryParams((prevState) => ({ + ...prevState, ...pagingArgs, - }); - } - reFetchEndpointActionList(); - }, - [isFlyout, reFetchEndpointActionList, setQueryParams, setPaginationOnUrlParams] - ); + })); + if (!isFlyout) { + setPaginationOnUrlParams({ + ...pagingArgs, + }); + } + reFetchEndpointActionList(); + }, + [isFlyout, reFetchEndpointActionList, setQueryParams, setPaginationOnUrlParams] + ); - if (error?.body?.statusCode === 404 && error?.body?.message === 'index_not_found_exception') { - return ; - } else if (isFetching && isFirstAttempt) { - return ; - } - return ( - <> - - {isFetched && !totalItemCount ? ( - - - - - - } - body={ -

      - -

      - } - data-test-subj="responseActions-empty" - /> -
      -
      - ) : ( - <> - {recordRangeLabel} - - ; + } else if (isFetching && isFirstAttempt) { + return ; + } + return ( + <> + + {isFetched && !totalItemCount ? ( + + + + + + } + body={ +

      + +

      + } + data-test-subj="responseActions-empty" + /> +
      +
      + ) : ( + - - )} - - ); -}); + )} + + ); + } +); ResponseActionsLog.displayName = 'ResponseActionsLog'; diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/translations.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/translations.tsx index 363da92a025bf..d185500612deb 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/translations.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/translations.tsx @@ -180,3 +180,15 @@ export const FILTER_NAMES = Object.freeze({ defaultMessage: 'Filter by username', }), }); + +export const ARIA_LABELS = Object.freeze({ + collapse: i18n.translate( + 'xpack.securitySolution.responseActionsList.list.expandButton.collapse', + { + defaultMessage: 'Collapse', + } + ), + expand: i18n.translate('xpack.securitySolution.responseActionsList.list.expandButton.expand', { + defaultMessage: 'Expand', + }), +}); diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/use_response_actions_log_table.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/use_response_actions_log_table.tsx deleted file mode 100644 index 4ecd860a4876e..0000000000000 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/use_response_actions_log_table.tsx +++ /dev/null @@ -1,471 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import React, { useCallback, useMemo, useState } from 'react'; - -import { - EuiI18nNumber, - EuiAvatar, - EuiButtonIcon, - EuiCodeBlock, - EuiDescriptionList, - EuiFacetButton, - EuiFlexGroup, - EuiFlexItem, - RIGHT_ALIGNMENT, - EuiScreenReaderOnly, - EuiText, - EuiToolTip, - type HorizontalAlignment, -} from '@elastic/eui'; -import { css, euiStyled } from '@kbn/kibana-react-plugin/common'; -import { FormattedMessage } from '@kbn/i18n-react'; - -import type { ActionListApiResponse } from '../../../../common/endpoint/types'; -import type { EndpointActionListRequestQuery } from '../../../../common/endpoint/schema/actions'; -import { FormattedDate } from '../../../common/components/formatted_date'; -import { OUTPUT_MESSAGES, TABLE_COLUMN_NAMES, UX_MESSAGES } from './translations'; -import { getActionStatus, getUiCommand } from './components/hooks'; -import { getEmptyValue } from '../../../common/components/empty_value'; -import { StatusBadge } from './components/status_badge'; -import { useTestIdGenerator } from '../../hooks/use_test_id_generator'; -import { MANAGEMENT_PAGE_SIZE_OPTIONS } from '../../common/constants'; -import { ResponseActionFileDownloadLink } from '../response_action_file_download_link'; - -const emptyValue = getEmptyValue(); - -// Truncated usernames -const StyledFacetButton = euiStyled(EuiFacetButton)` - .euiText { - margin-top: 0.38rem; - overflow-y: visible !important; - } -`; - -const customDescriptionListCss = css` - &.euiDescriptionList { - > .euiDescriptionList__title { - color: ${(props) => props.theme.eui.euiColorDarkShade}; - font-size: ${(props) => props.theme.eui.euiFontSizeXS}; - } - - > .euiDescriptionList__title, - > .euiDescriptionList__description { - font-weight: ${(props) => props.theme.eui.euiFontWeightRegular}; - margin-top: ${(props) => props.theme.eui.euiSizeS}; - } - } -`; - -const StyledDescriptionList = euiStyled(EuiDescriptionList).attrs({ - compressed: true, - type: 'column', -})` - ${customDescriptionListCss} -`; - -// output section styles -const topSpacingCss = css` - ${(props) => `${props.theme.eui.euiSize} 0`} -`; -const dashedBorderCss = css` - ${(props) => `1px dashed ${props.theme.eui.euiColorDisabled}`}; -`; -const StyledDescriptionListOutput = euiStyled(EuiDescriptionList).attrs({ compressed: true })` - ${customDescriptionListCss} - dd { - margin: ${topSpacingCss}; - padding: ${topSpacingCss}; - border-top: ${dashedBorderCss}; - border-bottom: ${dashedBorderCss}; - } -`; - -// code block styles -const StyledEuiCodeBlock = euiStyled(EuiCodeBlock).attrs({ - transparentBackground: true, - paddingSize: 'none', -})` - code { - color: ${(props) => props.theme.eui.euiColorDarkShade} !important; - } -`; - -export const useResponseActionsLogTable = ({ - pageIndex, - pageSize, - queryParams, - showHostNames, - totalItemCount, -}: { - pageIndex: number; - pageSize: number; - queryParams: EndpointActionListRequestQuery; - showHostNames: boolean; - totalItemCount: number; -}) => { - const getTestId = useTestIdGenerator('response-actions-list'); - - const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState<{ - [k: ActionListApiResponse['data'][number]['id']]: React.ReactNode; - }>({}); - - // expanded tray contents - const toggleDetails = useCallback( - (item: ActionListApiResponse['data'][number]) => { - const itemIdToExpandedRowMapValues = { ...itemIdToExpandedRowMap }; - if (itemIdToExpandedRowMapValues[item.id]) { - delete itemIdToExpandedRowMapValues[item.id]; - } else { - const { - startedAt, - completedAt, - isCompleted, - wasSuccessful, - isExpired, - command: _command, - comment, - parameters, - } = item; - - const parametersList = parameters - ? Object.entries(parameters).map(([key, value]) => { - return `${key}:${value}`; - }) - : undefined; - - const command = getUiCommand(_command); - const isGetFileCommand = command === 'get-file'; - const dataList = [ - { - title: OUTPUT_MESSAGES.expandSection.placedAt, - description: `${startedAt}`, - }, - { - title: OUTPUT_MESSAGES.expandSection.startedAt, - description: `${startedAt}`, - }, - { - title: OUTPUT_MESSAGES.expandSection.completedAt, - description: `${completedAt ?? emptyValue}`, - }, - { - title: OUTPUT_MESSAGES.expandSection.input, - description: `${command}`, - }, - { - title: OUTPUT_MESSAGES.expandSection.parameters, - description: parametersList ? parametersList : emptyValue, - }, - { - title: OUTPUT_MESSAGES.expandSection.comment, - description: comment ? comment : emptyValue, - }, - ].map(({ title, description }) => { - return { - title: {title}, - description: {description}, - }; - }); - - const getOutputContent = () => { - if (isExpired) { - return OUTPUT_MESSAGES.hasExpired(command); - } - - if (!isCompleted) { - return OUTPUT_MESSAGES.isPending(command); - } - - if (!wasSuccessful) { - return OUTPUT_MESSAGES.hasFailed(command); - } - - if (isGetFileCommand) { - return ( - <> - {OUTPUT_MESSAGES.wasSuccessful(command)} - - - ); - } - - return OUTPUT_MESSAGES.wasSuccessful(command); - }; - - const outputList = [ - { - title: ( - {`${OUTPUT_MESSAGES.expandSection.output}:`} - ), - description: ( - // codeblock for output - - {getOutputContent()} - - ), - }, - ]; - - itemIdToExpandedRowMapValues[item.id] = ( - <> - - - - - - - - - - ); - } - setItemIdToExpandedRowMap(itemIdToExpandedRowMapValues); - }, - [getTestId, itemIdToExpandedRowMap] - ); - // memoized callback for toggleDetails - const onClickCallback = useCallback( - (actionListDataItem: ActionListApiResponse['data'][number]) => () => - toggleDetails(actionListDataItem), - [toggleDetails] - ); - - const responseActionListColumns = useMemo(() => { - const columns = [ - { - field: 'startedAt', - name: TABLE_COLUMN_NAMES.time, - width: !showHostNames ? '21%' : '15%', - truncateText: true, - render: (startedAt: ActionListApiResponse['data'][number]['startedAt']) => { - return ( - - ); - }, - }, - { - field: 'command', - name: TABLE_COLUMN_NAMES.command, - width: !showHostNames ? '21%' : '10%', - truncateText: true, - render: (_command: ActionListApiResponse['data'][number]['command']) => { - const command = getUiCommand(_command); - return ( - - - {command} - - - ); - }, - }, - { - field: 'createdBy', - name: TABLE_COLUMN_NAMES.user, - width: !showHostNames ? '21%' : '14%', - truncateText: true, - render: (userId: ActionListApiResponse['data'][number]['createdBy']) => { - return ( - - } - > - - - {userId} - - - - ); - }, - }, - // conditional hostnames column - { - field: 'hosts', - name: TABLE_COLUMN_NAMES.hosts, - width: '20%', - truncateText: true, - render: (_hosts: ActionListApiResponse['data'][number]['hosts']) => { - const hosts = _hosts && Object.values(_hosts); - // join hostnames if the action is for multiple agents - // and skip empty strings for names if any - const _hostnames = hosts - .reduce((acc, host) => { - if (host.name.trim()) { - acc.push(host.name); - } - return acc; - }, []) - .join(', '); - - let hostnames = _hostnames; - if (!_hostnames) { - if (hosts.length > 1) { - // when action was for a single agent and no host name - hostnames = UX_MESSAGES.unenrolled.hosts; - } else if (hosts.length === 1) { - // when action was for a multiple agents - // and none of them have a host name - hostnames = UX_MESSAGES.unenrolled.host; - } - } - return ( - - - {hostnames} - - - ); - }, - }, - { - field: 'comment', - name: TABLE_COLUMN_NAMES.comments, - width: !showHostNames ? '21%' : '30%', - truncateText: true, - render: (comment: ActionListApiResponse['data'][number]['comment']) => { - return ( - - - {comment ?? emptyValue} - - - ); - }, - }, - { - field: 'status', - name: TABLE_COLUMN_NAMES.status, - width: !showHostNames ? '15%' : '10%', - render: (_status: ActionListApiResponse['data'][number]['status']) => { - const status = getActionStatus(_status); - - return ( - - - - ); - }, - }, - { - field: '', - align: RIGHT_ALIGNMENT as HorizontalAlignment, - width: '40px', - isExpander: true, - name: ( - - {UX_MESSAGES.screenReaderExpand} - - ), - render: (actionListDataItem: ActionListApiResponse['data'][number]) => { - return ( - - ); - }, - }, - ]; - // filter out the `hosts` column - // if showHostNames is FALSE - if (!showHostNames) { - return columns.filter((column) => column.field !== 'hosts'); - } - return columns; - }, [showHostNames, getTestId, itemIdToExpandedRowMap, onClickCallback]); - - // table pagination - const tablePagination = useMemo(() => { - return { - pageIndex, - pageSize, - totalItemCount, - pageSizeOptions: MANAGEMENT_PAGE_SIZE_OPTIONS as number[], - }; - }, [pageIndex, pageSize, totalItemCount]); - - // compute record ranges - const pagedResultsCount = useMemo(() => { - const page = queryParams.page ?? 1; - const perPage = queryParams?.pageSize ?? 10; - - const totalPages = Math.ceil(totalItemCount / perPage); - const fromCount = perPage * page - perPage + 1; - const toCount = - page === totalPages || totalPages === 1 ? totalItemCount : fromCount + perPage - 1; - return { fromCount, toCount }; - }, [queryParams.page, queryParams.pageSize, totalItemCount]); - - // create range label to display - const recordRangeLabel = useMemo( - () => ( - - - - {'-'} - - - ), - total: , - recordsLabel: {UX_MESSAGES.recordsLabel(totalItemCount)}, - }} - /> - - ), - [getTestId, pagedResultsCount.fromCount, pagedResultsCount.toCount, totalItemCount] - ); - - return { itemIdToExpandedRowMap, responseActionListColumns, recordRangeLabel, tablePagination }; -}; diff --git a/x-pack/plugins/security_solution/public/management/components/response_action_file_download_link/response_action_file_download_link.test.tsx b/x-pack/plugins/security_solution/public/management/components/response_action_file_download_link/response_action_file_download_link.test.tsx index b212b884f77b8..e8a7d387ca0ba 100644 --- a/x-pack/plugins/security_solution/public/management/components/response_action_file_download_link/response_action_file_download_link.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/response_action_file_download_link/response_action_file_download_link.test.tsx @@ -20,18 +20,11 @@ import { type ResponseActionFileDownloadLinkProps, } from './response_action_file_download_link'; import { responseActionsHttpMocks } from '../../mocks/response_actions_http_mocks'; -import { useUserPrivileges as _useUserPrivileges } from '../../../common/components/user_privileges'; import { getDeferred } from '../../mocks/utils'; import { waitFor } from '@testing-library/react'; import type { IHttpFetchError } from '@kbn/core-http-browser'; -jest.mock('../../../common/components/user_privileges'); - describe('When using the `ResponseActionFileDownloadLink` component', () => { - const useUserPrivilegesMock = _useUserPrivileges as jest.Mock< - ReturnType - >; - let render: () => ReturnType; let renderResult: ReturnType; let renderProps: ResponseActionFileDownloadLinkProps; @@ -48,6 +41,7 @@ describe('When using the `ResponseActionFileDownloadLink` component', () => { ResponseActionGetFileParameters >({ command: 'get-file' }), 'data-test-subj': 'test', + canAccessFileDownloadLink: true, }; render = () => { @@ -152,18 +146,8 @@ describe('When using the `ResponseActionFileDownloadLink` component', () => { }); }); - it('should show nothing if user does not have authz', () => { - const privileges = useUserPrivilegesMock(); - - useUserPrivilegesMock.mockImplementationOnce(() => { - return { - ...privileges, - endpointPrivileges: { - ...privileges.endpointPrivileges, - canWriteFileOperations: false, - }, - }; - }); + it('should show nothing if user does not have permission', () => { + renderProps.canAccessFileDownloadLink = false; render(); diff --git a/x-pack/plugins/security_solution/public/management/components/response_action_file_download_link/response_action_file_download_link.tsx b/x-pack/plugins/security_solution/public/management/components/response_action_file_download_link/response_action_file_download_link.tsx index f17ac1a4b205f..c795bfc9bc191 100644 --- a/x-pack/plugins/security_solution/public/management/components/response_action_file_download_link/response_action_file_download_link.tsx +++ b/x-pack/plugins/security_solution/public/management/components/response_action_file_download_link/response_action_file_download_link.tsx @@ -6,7 +6,7 @@ */ import React, { memo, useMemo, type CSSProperties } from 'react'; -import { EuiButtonEmpty, EuiLoadingContent, EuiText } from '@elastic/eui'; +import { EuiButtonEmpty, EuiSkeletonText, EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import styled from 'styled-components'; @@ -14,7 +14,6 @@ import { getFileDownloadId } from '../../../../common/endpoint/service/response_ import { resolvePathVariables } from '../../../common/utils/resolve_path_variables'; import { FormattedError } from '../formatted_error'; import { useGetFileInfo } from '../../hooks/response_actions/use_get_file_info'; -import { useUserPrivileges } from '../../../common/components/user_privileges'; import { useTestIdGenerator } from '../../hooks/use_test_id_generator'; import type { MaybeImmutable } from '../../../../common/endpoint/types'; import type { ActionDetails } from '../../../../common/endpoint/types/actions'; @@ -45,6 +44,7 @@ export interface ResponseActionFileDownloadLinkProps { /** If left undefined, the first agent that the action was sent to will be used */ agentId?: string; buttonTitle?: string; + canAccessFileDownloadLink: boolean; 'data-test-subj'?: string; textSize?: 's' | 'xs'; } @@ -60,12 +60,12 @@ export const ResponseActionFileDownloadLink = memo { const action = _action as ActionDetails; // cast to remove `Immutable` const getTestId = useTestIdGenerator(dataTestSubj); - const { canWriteFileOperations } = useUserPrivileges().endpointPrivileges; const shouldFetchFileInfo: boolean = useMemo(() => { return action.isCompleted && action.wasSuccessful; @@ -83,15 +83,15 @@ export const ResponseActionFileDownloadLink = memo; + return ; } // Check if file is no longer available diff --git a/x-pack/plugins/security_solution/public/management/icons/cloud_defend.tsx b/x-pack/plugins/security_solution/public/management/icons/cloud_defend.tsx new file mode 100644 index 0000000000000..1eb5a656b3628 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/icons/cloud_defend.tsx @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { SVGProps } from 'react'; +import React from 'react'; +export const IconCloudDefend: React.FC> = ({ ...props }) => ( + + + + + + + + + + + + + +); diff --git a/x-pack/plugins/security_solution/public/management/links.ts b/x-pack/plugins/security_solution/public/management/links.ts index 1db32eae3d5f2..57f2c53a7be19 100644 --- a/x-pack/plugins/security_solution/public/management/links.ts +++ b/x-pack/plugins/security_solution/public/management/links.ts @@ -49,6 +49,7 @@ import { manageCategories as cloudSecurityPostureCategories, manageLinks as cloudSecurityPostureLinks, } from '../cloud_security_posture/links'; +import { manageLinks as cloudDefendLinks } from '../cloud_defend/links'; import { IconActionHistory } from './icons/action_history'; import { IconBlocklist } from './icons/blocklist'; import { IconEndpoints } from './icons/endpoints'; @@ -226,6 +227,7 @@ export const links: LinkItem = { hideTimeline: true, }, cloudSecurityPostureLinks, + cloudDefendLinks, ], }; diff --git a/x-pack/plugins/security_solution/public/plugin.tsx b/x-pack/plugins/security_solution/public/plugin.tsx index abc9c41101e25..78680086d45c0 100644 --- a/x-pack/plugins/security_solution/public/plugin.tsx +++ b/x-pack/plugins/security_solution/public/plugin.tsx @@ -395,6 +395,7 @@ export class Plugin implements IPlugin; management: ReturnType; landingPages: ReturnType; + cloudDefend: ReturnType; cloudSecurityPosture: ReturnType; threatIntelligence: ReturnType; } diff --git a/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts b/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts index d1e236f897ade..0f29e1d46801d 100644 --- a/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts +++ b/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts @@ -12,7 +12,7 @@ import type { PluginStartContract as CasesPluginStartContract, } from '@kbn/cases-plugin/server'; import type { SecurityPluginStart } from '@kbn/security-plugin/server'; -import type { FleetStartContract } from '@kbn/fleet-plugin/server'; +import type { FleetStartContract, MessageSigningServiceInterface } from '@kbn/fleet-plugin/server'; import type { PluginStartContract as AlertsPluginStartContract } from '@kbn/alerting-plugin/server'; import { ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID } from '@kbn/securitysolution-list-constants'; import { @@ -61,6 +61,7 @@ export interface EndpointAppContextServiceStartContract { cases: CasesPluginStartContract | undefined; featureUsageService: FeatureUsageService; experimentalFeatures: ExperimentalFeatures; + messageSigningService: MessageSigningServiceInterface | undefined; } /** @@ -223,4 +224,12 @@ export class EndpointAppContextService { return this.startDependencies.exceptionListsClient; } + + public getMessageSigningService(): MessageSigningServiceInterface { + if (!this.startDependencies?.messageSigningService) { + throw new EndpointAppContentServicesNotStartedError(); + } + + return this.startDependencies.messageSigningService; + } } diff --git a/x-pack/plugins/security_solution/server/endpoint/mocks.ts b/x-pack/plugins/security_solution/server/endpoint/mocks.ts index be0738c5f0288..7b57a384e0e10 100644 --- a/x-pack/plugins/security_solution/server/endpoint/mocks.ts +++ b/x-pack/plugins/security_solution/server/endpoint/mocks.ts @@ -33,6 +33,7 @@ import { createMockAgentPolicyService, createMockAgentService, createMockPackageService, + createMessageSigningServiceMock, } from '@kbn/fleet-plugin/server/mocks'; // A TS error (TS2403) is thrown when attempting to export the mock function below from Cases // plugin server `index.ts`. Its unclear what is actually causing the error. Since this is a Mock @@ -176,6 +177,7 @@ export const createMockEndpointAppContextServiceStartContract = }, featureUsageService: createFeatureUsageServiceMock(), experimentalFeatures: createMockConfig().experimentalFeatures, + messageSigningService: createMessageSigningServiceMock(), }; }; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts index 0c89c183f0835..42e337efba2d9 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts @@ -108,6 +108,12 @@ describe('Response actions', () => { const routerMock = httpServiceMock.createRouter(); mockResponse = httpServerMock.createResponseFactory(); const startContract = createMockEndpointAppContextServiceStartContract(); + (startContract.messageSigningService?.sign as jest.Mock).mockImplementation(() => { + return { + data: 'thisisthedata', + signature: 'thisisasignature', + }; + }); endpointAppContextService = new EndpointAppContextService(); const mockSavedObjectClient = savedObjectsClientMock.create(); @@ -637,6 +643,35 @@ describe('Response actions', () => { expect(responseBody.action).toBeUndefined(); }); + it('signs the action', async () => { + const ctx = await callRoute( + ISOLATE_HOST_ROUTE_V2, + { + body: { endpoint_ids: ['XYZ'] }, + }, + { endpointDsExists: true } + ); + + const indexDoc = ctx.core.elasticsearch.client.asInternalUser.index; + const actionDocs: [ + { index: string; body?: LogsEndpointAction }, + { index: string; body?: EndpointAction } + ] = [ + indexDoc.mock.calls[0][0] as estypes.IndexRequest, + indexDoc.mock.calls[1][0] as estypes.IndexRequest, + ]; + + expect(actionDocs[1].index).toEqual(AGENT_ACTIONS_INDEX); + expect(actionDocs[1].body?.signed).toEqual({ + data: 'thisisthedata', + signature: 'thisisasignature', + }); + + expect(mockResponse.ok).toBeCalled(); + const responseBody = mockResponse.ok.mock.calls[0][0]?.body as ResponseActionApiResponse; + expect(responseBody.action).toBeTruthy(); + }); + it('handles errors', async () => { const ErrMessage = 'Uh oh!'; await callRoute( diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.ts index 559cd0009a4e2..b4197bb947d93 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.ts @@ -335,21 +335,36 @@ function responseActionRequestHandler( { index: AGENT_ACTIONS_INDEX, - body: { - ...doc.EndpointActions, - '@timestamp': doc['@timestamp'], - agents, - timeout: 300, // 5 minutes - user_id: doc.user.id, - }, + body: signedFleetActionDoc, refresh: 'wait_for', }, - { meta: true } + { + meta: true, + } ); if (fleetActionIndexResult.statusCode !== 201) { diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/entity/utils/supported_schemas.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/entity/utils/supported_schemas.ts index 38f03c987ea34..788e7f3ae7713 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/entity/utils/supported_schemas.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/entity/utils/supported_schemas.ts @@ -63,6 +63,24 @@ export const supportedSchemas: SupportedSchema[] = [ name: 'process.name', }, }, + { + name: 'sysmonViaFilebeat', + constraints: [ + { + field: 'agent.type', + value: 'filebeat', + }, + { + field: 'event.dataset', + value: 'windows.sysmon_operational', + }, + ], + schema: { + id: 'process.entity_id', + parent: 'process.parent.entity_id', + name: 'process.name', + }, + }, ]; export function getFieldAsString(doc: unknown, field: string): string | undefined { diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/queries/events.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/queries/events.ts index 869ae911ad890..bd0642702fc11 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/queries/events.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/queries/events.ts @@ -86,6 +86,7 @@ export class EventsQuery extends BaseResolverQuery { return { body: this.query(filters), index: this.indexPatterns, + allow_partial_search_results: true, }; } diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/pagination.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/pagination.test.ts index a44be24ef2fca..c0b50798a822c 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/pagination.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/pagination.test.ts @@ -61,7 +61,7 @@ describe('Pagination', () => { const builder = PaginationBuilder.createBuilder(100); expect(builder.buildQueryFields('a', 'desc').sort).toStrictEqual([ { '@timestamp': 'desc' }, - { a: 'asc' }, + { a: { order: 'asc', unmapped_type: 'long' } }, ]); }); }); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/pagination.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/pagination.ts index 96086f1312f85..295d930ff24f5 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/pagination.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/pagination.ts @@ -31,7 +31,7 @@ export type SortFields = [ { '@timestamp': string; }, - { [x: string]: string } + { [x: string]: { order: string; unmapped_type?: string } } ]; /** @@ -177,7 +177,10 @@ export class PaginationBuilder { tiebreaker: string, timeSort: TimeSortDirection = 'asc' ): PaginationFields { - const sort: SortFields = [{ '@timestamp': timeSort }, { [tiebreaker]: 'asc' }]; + const sort: SortFields = [ + { '@timestamp': timeSort }, + { [tiebreaker]: { order: 'asc', unmapped_type: 'long' } }, + ]; let searchAfter: SearchAfterFields | undefined; if (this.timestamp && this.eventID) { searchAfter = [this.timestamp, this.eventID]; diff --git a/x-pack/plugins/security_solution/server/features.ts b/x-pack/plugins/security_solution/server/features.ts index 197b9fc926cc7..5f838f76d3bf5 100644 --- a/x-pack/plugins/security_solution/server/features.ts +++ b/x-pack/plugins/security_solution/server/features.ts @@ -7,10 +7,14 @@ import { i18n } from '@kbn/i18n'; +import { hiddenTypes as filesSavedObjectTypes } from '@kbn/files-plugin/server/saved_objects'; import type { KibanaFeatureConfig, SubFeatureConfig } from '@kbn/features-plugin/common'; import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server'; import { DATA_VIEW_SAVED_OBJECT_TYPE } from '@kbn/data-views-plugin/common'; -import { createUICapabilities } from '@kbn/cases-plugin/common'; +import { + createUICapabilities as createCasesUICapabilities, + getApiTags as getCasesApiTags, +} from '@kbn/cases-plugin/common'; import { EXCEPTION_LIST_NAMESPACE_AGNOSTIC } from '@kbn/securitysolution-list-constants'; import { APP_ID, CASES_FEATURE_ID, SERVER_APP_ID } from '../common/constants'; @@ -18,7 +22,8 @@ import { savedObjectTypes } from './saved_objects'; import type { ConfigType } from './config'; export const getCasesKibanaFeature = (): KibanaFeatureConfig => { - const casesCapabilities = createUICapabilities(); + const casesCapabilities = createCasesUICapabilities(); + const casesApiTags = getCasesApiTags(APP_ID); return { id: CASES_FEATURE_ID, @@ -32,7 +37,7 @@ export const getCasesKibanaFeature = (): KibanaFeatureConfig => { cases: [APP_ID], privileges: { all: { - api: ['casesSuggestUserProfiles', 'bulkGetUserProfiles'], + api: casesApiTags.all, app: [CASES_FEATURE_ID, 'kibana'], catalogue: [APP_ID], cases: { @@ -42,13 +47,13 @@ export const getCasesKibanaFeature = (): KibanaFeatureConfig => { push: [APP_ID], }, savedObject: { - all: [], - read: [], + all: [...filesSavedObjectTypes], + read: [...filesSavedObjectTypes], }, ui: casesCapabilities.all, }, read: { - api: ['casesSuggestUserProfiles', 'bulkGetUserProfiles'], + api: casesApiTags.read, app: [CASES_FEATURE_ID, 'kibana'], catalogue: [APP_ID], cases: { @@ -56,7 +61,7 @@ export const getCasesKibanaFeature = (): KibanaFeatureConfig => { }, savedObject: { all: [], - read: [], + read: [...filesSavedObjectTypes], }, ui: casesCapabilities.read, }, @@ -71,7 +76,7 @@ export const getCasesKibanaFeature = (): KibanaFeatureConfig => { groupType: 'independent', privileges: [ { - api: [], + api: casesApiTags.delete, id: 'cases_delete', name: i18n.translate( 'xpack.securitySolution.featureRegistry.deleteSubFeatureDetails', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_installed_integrations/installed_integration_set.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_installed_integrations/installed_integration_set.ts index af35f7881bd00..dcf9d006c4315 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_installed_integrations/installed_integration_set.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_installed_integrations/installed_integration_set.ts @@ -5,8 +5,8 @@ * 2.0. */ +import type { PackageListItem, PackagePolicy } from '@kbn/fleet-plugin/common'; import { capitalize, flatten } from 'lodash'; -import type { PackagePolicy, ArchivePackage } from '@kbn/fleet-plugin/common'; import type { InstalledIntegration, InstalledIntegrationArray, @@ -17,8 +17,8 @@ import type { } from '../../../../../../common/detection_engine/fleet_integrations'; export interface IInstalledIntegrationSet { + addPackage(fleetPackage: PackageListItem): void; addPackagePolicy(policy: PackagePolicy): void; - addRegistryPackage(registryPackage: ArchivePackage): void; getPackages(): InstalledPackageArray; getIntegrations(): InstalledIntegrationArray; @@ -33,10 +33,57 @@ interface PackageInfo extends InstalledPackageBasicInfo { export const createInstalledIntegrationSet = (): IInstalledIntegrationSet => { const packageMap: PackageMap = new Map([]); + const addPackage = (fleetPackage: PackageListItem): void => { + if (fleetPackage.type !== 'integration') { + return; + } + if (fleetPackage.status !== 'installed') { + return; + } + + const packageKey = `${fleetPackage.name}`; + const existingPackageInfo = packageMap.get(packageKey); + + if (existingPackageInfo != null) { + return; + } + + // Actual `installed_version` is buried in SO, root `version` is latest package version available + const installedPackageVersion = fleetPackage.savedObject.attributes.install_version; + + // Policy templates correspond to package's integrations. + const packagePolicyTemplates = fleetPackage.policy_templates ?? []; + + const packageInfo: PackageInfo = { + package_name: fleetPackage.name, + package_title: fleetPackage.title, + package_version: installedPackageVersion, + + integrations: new Map( + packagePolicyTemplates.map((pt) => { + const integrationTitle: string = + packagePolicyTemplates.length === 1 && pt.name === fleetPackage.name + ? fleetPackage.title + : pt.title; + + const integrationInfo: InstalledIntegrationBasicInfo = { + integration_name: pt.name, + integration_title: integrationTitle, + is_enabled: false, // There might not be an integration policy, so default false and later update in addPackagePolicy() + }; + + return [integrationInfo.integration_name, integrationInfo]; + }) + ), + }; + + packageMap.set(packageKey, packageInfo); + }; + const addPackagePolicy = (policy: PackagePolicy): void => { const packageInfo = getPackageInfoFromPolicy(policy); const integrationsInfo = getIntegrationsInfoFromPolicy(policy, packageInfo); - const packageKey = `${packageInfo.package_name}:${packageInfo.package_version}`; + const packageKey = `${packageInfo.package_name}`; const existingPackageInfo = packageMap.get(packageKey); if (existingPackageInfo == null) { @@ -56,21 +103,6 @@ export const createInstalledIntegrationSet = (): IInstalledIntegrationSet => { } }; - const addRegistryPackage = (registryPackage: ArchivePackage): void => { - const policyTemplates = registryPackage.policy_templates ?? []; - const packageKey = `${registryPackage.name}:${registryPackage.version}`; - const existingPackageInfo = packageMap.get(packageKey); - - if (existingPackageInfo != null) { - for (const integration of existingPackageInfo.integrations.values()) { - const policyTemplate = policyTemplates.find((t) => t.name === integration.integration_name); - if (policyTemplate != null) { - integration.integration_title = policyTemplate.title; - } - } - } - }; - const getPackages = (): InstalledPackageArray => { const packages = Array.from(packageMap.values()); return packages.map((packageInfo): InstalledPackage => { @@ -106,8 +138,8 @@ export const createInstalledIntegrationSet = (): IInstalledIntegrationSet => { }; return { + addPackage, addPackagePolicy, - addRegistryPackage, getPackages, getIntegrations, }; @@ -125,15 +157,30 @@ const getIntegrationsInfoFromPolicy = ( policy: PackagePolicy, packageInfo: InstalledPackageBasicInfo ): InstalledIntegrationBasicInfo[] => { - return policy.inputs.map((input) => { + // Construct integration info from the available policy_templates + const integrationInfos = policy.inputs.map((input) => { const integrationName = normalizeString(input.policy_template ?? input.type); // e.g. 'cloudtrail' const integrationTitle = `${packageInfo.package_title} ${capitalize(integrationName)}`; // e.g. 'AWS Cloudtrail' return { integration_name: integrationName, - integration_title: integrationTitle, // title gets re-initialized later in addRegistryPackage() + integration_title: integrationTitle, is_enabled: input.enabled, }; }); + + // Base package may not have policy template, so pull directly from `policy.package` if so + return [ + ...integrationInfos, + ...(policy.package + ? [ + { + integration_name: policy.package.name, + integration_title: policy.package.title, + is_enabled: true, // Always true if `policy.package` exists since this corresponds to the base package + }, + ] + : []), + ]; }; const normalizeString = (raw: string | null | undefined): string => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_installed_integrations/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_installed_integrations/route.ts index 7b904c282e1e4..559abe391f5a1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_installed_integrations/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_installed_integrations/route.ts @@ -7,7 +7,6 @@ import type { Logger } from '@kbn/core/server'; import { transformError } from '@kbn/securitysolution-es-utils'; -import { initPromisePool } from '../../../../../utils/promise_pool'; import { buildSiemResponse } from '../../../routes/utils'; import type { SecuritySolutionPluginRouter } from '../../../../../types'; @@ -15,8 +14,6 @@ import type { GetInstalledIntegrationsResponse } from '../../../../../../common/ import { GET_INSTALLED_INTEGRATIONS_URL } from '../../../../../../common/detection_engine/fleet_integrations'; import { createInstalledIntegrationSet } from './installed_integration_set'; -const MAX_CONCURRENT_REQUESTS_TO_PACKAGE_REGISTRY = 5; - /** * Returns an array of installed Fleet integrations and their packages. */ @@ -40,48 +37,18 @@ export const getInstalledIntegrationsRoute = ( const fleet = ctx.securitySolution.getInternalFleetServices(); const set = createInstalledIntegrationSet(); - const packagePolicies = await fleet.packagePolicy.list(fleet.internalReadonlySoClient, {}); + // Pulls all packages into memory just like the main fleet landing page + // No pagination support currently, so cannot batch this call + const allThePackages = await fleet.packages.getPackages(); + allThePackages.forEach((fleetPackage) => { + set.addPackage(fleetPackage); + }); + const packagePolicies = await fleet.packagePolicy.list(fleet.internalReadonlySoClient, {}); packagePolicies.items.forEach((policy) => { set.addPackagePolicy(policy); }); - const registryPackages = await initPromisePool({ - concurrency: MAX_CONCURRENT_REQUESTS_TO_PACKAGE_REGISTRY, - items: set.getPackages(), - executor: async (packageInfo) => { - const registryPackage = await fleet.packages.getPackage( - packageInfo.package_name, - packageInfo.package_version - ); - return registryPackage; - }, - }); - - if (registryPackages.errors.length > 0) { - const errors = registryPackages.errors.map(({ error, item }) => { - return { - error, - packageId: `${item.package_name}@${item.package_version}`, - }; - }); - - const packages = errors.map((e) => e.packageId).join(', '); - logger.error( - `Unable to retrieve installed integrations. Error fetching packages from registry: ${packages}.` - ); - - errors.forEach(({ error, packageId }) => { - const logMessage = `Error fetching package info from registry for ${packageId}`; - const logReason = error instanceof Error ? error.message : String(error); - logger.debug(`${logMessage}. ${logReason}`); - }); - } - - registryPackages.results.forEach(({ result }) => { - set.addRegistryPackage(result.packageInfo); - }); - const installedIntegrations = set.getIntegrations(); const body: GetInstalledIntegrationsResponse = { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.ts index ddcbf2cbdd6e4..c4490e418b859 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.ts @@ -135,6 +135,7 @@ export const importRulesRoute = ( success: actionConnectorSuccess, warnings: actionConnectorWarnings, errors: actionConnectorErrors, + rulesWithMigratedActions, } = await importRuleActionConnectors({ actionConnectors, actionsClient, @@ -142,9 +143,12 @@ export const importRulesRoute = ( rules: migratedParsedObjectsWithoutDuplicateErrors, overwrite: request.query.overwrite_action_connectors, }); + + // rulesWithMigratedActions: Is returened only in case connectors were exorted from different namesapce and the + // original rules actions' ids were replaced with new destinationIds const parsedRules = actionConnectorErrors.length ? [] - : migratedParsedObjectsWithoutDuplicateErrors; + : rulesWithMigratedActions || migratedParsedObjectsWithoutDuplicateErrors; // gather all exception lists that the imported rules reference const foundReferencedExceptionLists = await getReferencedExceptionLists({ @@ -166,7 +170,6 @@ export const importRulesRoute = ( existingLists: foundReferencedExceptionLists, allowMissingConnectorSecrets: !!actionConnectors.length, }); - const errorsResp = importRuleResponse.filter((resp) => isBulkError(resp)) as BulkError[]; const successes = importRuleResponse.filter((resp) => { if (isImportRegular(resp)) { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/action_connectors/import_rule_action_connectors.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/action_connectors/import_rule_action_connectors.test.ts index 6c732d4057313..54574c85f7036 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/action_connectors/import_rule_action_connectors.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/action_connectors/import_rule_action_connectors.test.ts @@ -55,7 +55,7 @@ describe('importRuleActionConnectors', () => { const res = await importRuleActionConnectors({ actionConnectors, actionsClient, - actionsImporter: actionsImporter2() as never, + actionsImporter: actionsImporter2(), rules, overwrite: false, }); @@ -82,7 +82,6 @@ describe('importRuleActionConnectors', () => { import: jest.fn().mockResolvedValue({ success: true, successCount: 1, - successResults: [], errors: [], warnings: [], }), @@ -92,7 +91,7 @@ describe('importRuleActionConnectors', () => { const res = await importRuleActionConnectors({ actionConnectors, actionsClient, - actionsImporter: actionsImporter() as never, + actionsImporter: actionsImporter(), rules, overwrite: false, }); @@ -100,7 +99,6 @@ describe('importRuleActionConnectors', () => { expect(res).toEqual({ success: true, successCount: 1, - successResults: [], errors: [], warnings: [], }); @@ -110,7 +108,6 @@ describe('importRuleActionConnectors', () => { import: jest.fn().mockResolvedValue({ success: true, successCount: 1, - successResults: [], errors: [], warnings: [], }), @@ -143,7 +140,7 @@ describe('importRuleActionConnectors', () => { const res = await importRuleActionConnectors({ actionConnectors, actionsClient, - actionsImporter: actionsImporter() as never, + actionsImporter: actionsImporter(), rules: ruleWith2Connectors, overwrite: false, }); @@ -151,7 +148,6 @@ describe('importRuleActionConnectors', () => { expect(res).toEqual({ success: true, successCount: 1, - successResults: [], errors: [], warnings: [], }); @@ -163,7 +159,7 @@ describe('importRuleActionConnectors', () => { const res = await importRuleActionConnectors({ actionConnectors: [], actionsClient, - actionsImporter: actionsImporter() as never, + actionsImporter: actionsImporter(), rules, overwrite: false, }); @@ -191,7 +187,7 @@ describe('importRuleActionConnectors', () => { const res = await importRuleActionConnectors({ actionConnectors: [], actionsClient, - actionsImporter: actionsImporter() as never, + actionsImporter: actionsImporter(), rules: [ { ...getImportRulesSchemaMock(), @@ -237,7 +233,7 @@ describe('importRuleActionConnectors', () => { const res = await importRuleActionConnectors({ actionConnectors: [], actionsClient, - actionsImporter: actionsImporter() as never, + actionsImporter: actionsImporter(), rules: [ { ...getImportRulesSchemaMock(), @@ -288,18 +284,17 @@ describe('importRuleActionConnectors', () => { import: jest.fn().mockResolvedValue({ success: true, successCount: 2, - successResults: [], errors: [], warnings: [], }), }); const actionsImporter2 = core.savedObjects.getImporter; - const actionsImporter2Import = actionsImporter2().import; + const actionsImporter2Importer = actionsImporter2(); const res = await importRuleActionConnectors({ actionConnectors, actionsClient, - actionsImporter: actionsImporter2Import as never, + actionsImporter: actionsImporter2Importer, rules: rulesWithoutActions, overwrite: false, }); @@ -310,7 +305,7 @@ describe('importRuleActionConnectors', () => { errors: [], warnings: [], }); - expect(actionsImporter2Import).not.toBeCalled(); + expect(actionsImporter2Importer.import).not.toBeCalled(); }); it('should skip importing the action-connectors if all connectors have been imported/created before', async () => { @@ -325,12 +320,12 @@ describe('importRuleActionConnectors', () => { }, ]); const actionsImporter2 = core.savedObjects.getImporter; - const actionsImporter2Import = actionsImporter2().import; + const actionsImporter2Importer = actionsImporter2(); const res = await importRuleActionConnectors({ actionConnectors, actionsClient, - actionsImporter: actionsImporter2Import as never, + actionsImporter: actionsImporter2Importer, rules, overwrite: false, }); @@ -341,7 +336,7 @@ describe('importRuleActionConnectors', () => { errors: [], warnings: [], }); - expect(actionsImporter2Import).not.toBeCalled(); + expect(actionsImporter2Importer.import).not.toBeCalled(); }); it('should not skip importing the action-connectors if all connectors have been imported/created before when overwrite is true', async () => { @@ -349,7 +344,6 @@ describe('importRuleActionConnectors', () => { import: jest.fn().mockResolvedValue({ success: true, successCount: 1, - successResults: [], errors: [], warnings: [], }), @@ -370,7 +364,7 @@ describe('importRuleActionConnectors', () => { const res = await importRuleActionConnectors({ actionConnectors, actionsClient, - actionsImporter: actionsImporter() as never, + actionsImporter: actionsImporter(), rules, overwrite: true, }); @@ -380,7 +374,194 @@ describe('importRuleActionConnectors', () => { successCount: 1, errors: [], warnings: [], - successResults: [], + }); + }); + + it('should import one rule with connector successfully even if it was exported from different namespaces by generating destinationId and replace the old actionId with it', async () => { + const successResults = [ + { + destinationId: '72cab9bb-535f-45dd-b9c2-5bc1bc0db96b', + id: 'cabc78e0-9031-11ed-b076-53cc4d57aaf1', + meta: { title: 'Connector: [anotherSpaceSlack]', icon: undefined }, + type: 'action', + }, + ]; + core.savedObjects.getImporter = jest.fn().mockReturnValueOnce({ + import: jest.fn().mockResolvedValue({ + success: true, + successCount: 1, + successResults, + errors: [], + warnings: [], + }), + }); + const actionsImporter = core.savedObjects.getImporter; + + actionsClient.getAll.mockResolvedValue([]); + + const res = await importRuleActionConnectors({ + actionConnectors, + actionsClient, + actionsImporter: actionsImporter(), + rules, + overwrite: false, + }); + const rulesWithMigratedActions = [ + { + actions: [ + { + action_type_id: '.webhook', + group: 'default', + id: '72cab9bb-535f-45dd-b9c2-5bc1bc0db96b', + params: {}, + }, + ], + description: 'some description', + language: 'kuery', + name: 'Query with a rule id', + query: 'user.name: root or user.name: admin', + risk_score: 55, + rule_id: 'rule-1', + severity: 'high', + type: 'query', + }, + ]; + + expect(res).toEqual({ + success: true, + successCount: 1, + errors: [], + warnings: [], + rulesWithMigratedActions, + }); + }); + + it('should import multiple rules with connectors successfully even if they were exported from different namespaces by generating destinationIds and replace the old actionIds with them', async () => { + const multipleRules = [ + { + ...getImportRulesSchemaMock(), + actions: [ + { + group: 'default', + id: 'cabc78e0-9031-11ed-b076-53cc4d57aaf1', + action_type_id: '.webhook', + params: {}, + }, + ], + }, + { + ...getImportRulesSchemaMock(), + rule_id: 'rule_2', + id: '0abc78e0-7031-11ed-b076-53cc4d57aaf1', + actions: [ + { + group: 'default', + id: '11abc78e0-9031-11ed-b076-53cc4d57aaw', + action_type_id: '.index', + params: {}, + }, + ], + }, + ]; + const successResults = [ + { + destinationId: '72cab9bb-535f-45dd-b9c2-5bc1bc0db96b', + id: 'cabc78e0-9031-11ed-b076-53cc4d57aaf1', + meta: { title: 'Connector: [anotherSpaceSlack]', icon: undefined }, + type: 'action', + }, + { + destinationId: '892cab9bb-535f-45dd-b9c2-5bc1bc0db96', + id: '11abc78e0-9031-11ed-b076-53cc4d57aaw', + meta: { title: 'Connector: [anotherSpaceSlack]', icon: undefined }, + type: 'action', + }, + ]; + core.savedObjects.getImporter = jest.fn().mockReturnValueOnce({ + import: jest.fn().mockResolvedValue({ + success: true, + successCount: 1, + successResults, + errors: [], + warnings: [], + }), + }); + const actionsImporter = core.savedObjects.getImporter; + const actionConnectorsWithIndex = [ + ...actionConnectors, + { + id: '0abc78e0-7031-11ed-b076-53cc4d57aaf1', + type: 'action', + updated_at: '2023-01-25T14:35:52.852Z', + created_at: '2023-01-25T14:35:52.852Z', + version: 'WzUxNTksMV0=', + attributes: { + actionTypeId: '.webhook', + name: 'webhook', + isMissingSecrets: false, + config: {}, + secrets: {}, + }, + references: [], + migrationVersion: { action: '8.3.0' }, + coreMigrationVersion: '8.7.0', + }, + ]; + actionsClient.getAll.mockResolvedValue([]); + + const res = await importRuleActionConnectors({ + actionConnectors: actionConnectorsWithIndex, + actionsClient, + actionsImporter: actionsImporter(), + rules: multipleRules, + overwrite: false, + }); + const rulesWithMigratedActions = [ + { + actions: [ + { + action_type_id: '.webhook', + group: 'default', + id: '72cab9bb-535f-45dd-b9c2-5bc1bc0db96b', + params: {}, + }, + ], + description: 'some description', + language: 'kuery', + name: 'Query with a rule id', + query: 'user.name: root or user.name: admin', + risk_score: 55, + rule_id: 'rule-1', + severity: 'high', + type: 'query', + }, + { + actions: [ + { + action_type_id: '.index', + group: 'default', + id: '892cab9bb-535f-45dd-b9c2-5bc1bc0db96', + params: {}, + }, + ], + description: 'some description', + language: 'kuery', + name: 'Query with a rule id', + id: '0abc78e0-7031-11ed-b076-53cc4d57aaf1', + rule_id: 'rule_2', + query: 'user.name: root or user.name: admin', + risk_score: 55, + severity: 'high', + type: 'query', + }, + ]; + + expect(res).toEqual({ + success: true, + successCount: 1, + errors: [], + warnings: [], + rulesWithMigratedActions, }); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/action_connectors/import_rule_action_connectors.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/action_connectors/import_rule_action_connectors.ts index 3d2fc9fb7c6b6..dda1a28192eef 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/action_connectors/import_rule_action_connectors.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/action_connectors/import_rule_action_connectors.ts @@ -9,6 +9,7 @@ import { Readable } from 'stream'; import type { SavedObjectsImportResponse } from '@kbn/core-saved-objects-common'; import type { SavedObject } from '@kbn/core-saved-objects-server'; +import type { RuleToImport } from '../../../../../../../common/detection_engine/rule_management'; import type { WarningSchema } from '../../../../../../../common/detection_engine/schemas/response'; import { checkIfActionsHaveMissingConnectors, @@ -17,6 +18,7 @@ import { handleActionsHaveNoConnectors, mapSOErrorToRuleError, returnErroredImportResult, + updateRuleActionsWithMigratedResults, } from './utils'; import type { ImportRuleActionConnectorsParams, ImportRuleActionConnectorsResult } from './types'; @@ -71,12 +73,21 @@ export const importRuleActionConnectors = async ({ overwrite, createNewCopies: false, }); + /* + // When a connector is exported from one namespace and imported to another, it does not result in an error, but instead a new object is created with + // new destination id and id will have the old origin id, so in order to be able to use the newly generated Connectors id, this util is used to swap the old id with the + // new destination Id + */ + let rulesWithMigratedActions: Array | undefined; + if (successResults?.some((res) => res.destinationId)) + rulesWithMigratedActions = updateRuleActionsWithMigratedResults(rules, successResults); + return { success, successCount, - successResults, errors: errors ? mapSOErrorToRuleError(errors) : [], warnings: (warnings as WarningSchema[]) || [], + rulesWithMigratedActions, }; } catch (error) { return returnErroredImportResult(error); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/action_connectors/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/action_connectors/types.ts index 8ba6c41c42dde..3d9471016a859 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/action_connectors/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/action_connectors/types.ts @@ -6,10 +6,7 @@ */ import type { ISavedObjectsImporter, SavedObject } from '@kbn/core-saved-objects-server'; import type { ActionsClient } from '@kbn/actions-plugin/server'; -import type { - SavedObjectsImportFailure, - SavedObjectsImportSuccess, -} from '@kbn/core-saved-objects-common'; +import type { SavedObjectsImportFailure } from '@kbn/core-saved-objects-common'; import type { RuleToImport } from '../../../../../../../common/detection_engine/rule_management'; import type { WarningSchema } from '../../../../../../../common/detection_engine/schemas/response'; import type { BulkError } from '../../../../routes/utils'; @@ -17,9 +14,9 @@ import type { BulkError } from '../../../../routes/utils'; export interface ImportRuleActionConnectorsResult { success: boolean; successCount: number; - successResults?: SavedObjectsImportSuccess[]; errors: BulkError[] | []; warnings: WarningSchema[] | []; + rulesWithMigratedActions?: Array; } export interface ImportRuleActionConnectorsParams { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/action_connectors/utils/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/action_connectors/utils/index.ts index cecee94ffa5ad..4ab122f6da94e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/action_connectors/utils/index.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/action_connectors/utils/index.ts @@ -5,7 +5,10 @@ * 2.0. */ import { pick } from 'lodash'; -import type { SavedObjectsImportFailure } from '@kbn/core-saved-objects-common'; +import type { + SavedObjectsImportFailure, + SavedObjectsImportSuccess, +} from '@kbn/core-saved-objects-common'; import type { SavedObject } from '@kbn/core-saved-objects-server'; import type { ActionsClient } from '@kbn/actions-plugin/server'; import type { BulkError } from '../../../../../routes/utils'; @@ -128,3 +131,43 @@ export const checkIfActionsHaveMissingConnectors = ( } return null; }; + +export const mapActionIdToNewDestinationId = ( + connectorsImportResult: SavedObjectsImportSuccess[] +) => { + return connectorsImportResult.reduce( + (acc: { [actionId: string]: string }, { destinationId, id }) => { + acc[id] = destinationId || id; + return acc; + }, + {} + ); +}; + +export const swapNonDefaultSpaceIdWithDestinationId = ( + rule: RuleToImport, + actionIdDestinationIdLookup: { [actionId: string]: string } +) => { + return rule.actions?.map((action) => { + const destinationId = actionIdDestinationIdLookup[action.id]; + return { ...action, id: destinationId }; + }); +}; +/* +// When a connector is exported from one namespace and imported to another, it does not result in an error, but instead a new object is created with +// new destination id and id will have the old origin id, so in order to be able to use the newly generated Connectors id, this util is used to swap the old id with the +// new destination Id +*/ +export const updateRuleActionsWithMigratedResults = ( + rules: Array, + connectorsImportResult: SavedObjectsImportSuccess[] +): Array => { + const actionIdDestinationIdLookup = mapActionIdToNewDestinationId(connectorsImportResult); + return rules.map((rule) => { + if (rule instanceof Error) return rule; + return { + ...rule, + actions: swapNonDefaultSpaceIdWithDestinationId(rule, actionIdDestinationIdLookup), + }; + }); +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/build_threat_enrichment.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/build_threat_enrichment.ts index 70819f6896861..99b6834523ef5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/build_threat_enrichment.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/build_threat_enrichment.ts @@ -61,6 +61,7 @@ export const buildThreatEnrichment = ({ const signalsQueryMap = await getSignalsQueryMapFromThreatIndex({ threatSearchParams, eventsCount: signals.length, + termsQueryAllowed: false, }); const enrichment = threatEnrichmentFactory({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/create_event_signal.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/create_event_signal.ts index 8fea8412be38a..17ce22aa40bee 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/create_event_signal.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/create_event_signal.ts @@ -86,6 +86,7 @@ export const createEventSignal = async ({ threatSearchParams, eventsCount: currentEventList.length, signalValueMap: getSignalValueMap({ eventList: currentEventList, threatMatchedFields }), + termsQueryAllowed: true, }); const ids = Array.from(signalsQueryMap.keys()); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/get_signals_map_from_threat_index.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/get_signals_map_from_threat_index.test.ts index 38a6947beebcb..4a48db4816b48 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/get_signals_map_from_threat_index.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/get_signals_map_from_threat_index.test.ts @@ -53,6 +53,7 @@ describe('getSignalsQueryMapFromThreatIndex', () => { await getSignalsQueryMapFromThreatIndex({ threatSearchParams: threatSearchParamsMock, eventsCount: 50, + termsQueryAllowed: false, }); expect(getThreatListMock).toHaveBeenCalledTimes(1); @@ -65,6 +66,7 @@ describe('getSignalsQueryMapFromThreatIndex', () => { const signalsQueryMap = await getSignalsQueryMapFromThreatIndex({ threatSearchParams: threatSearchParamsMock, eventsCount: 50, + termsQueryAllowed: false, }); expect(signalsQueryMap).toEqual(new Map()); @@ -98,6 +100,7 @@ describe('getSignalsQueryMapFromThreatIndex', () => { const signalsQueryMap = await getSignalsQueryMapFromThreatIndex({ threatSearchParams: threatSearchParamsMock, eventsCount: 50, + termsQueryAllowed: false, }); expect(signalsQueryMap).toEqual( @@ -153,6 +156,7 @@ describe('getSignalsQueryMapFromThreatIndex', () => { const signalsQueryMap = await getSignalsQueryMapFromThreatIndex({ threatSearchParams: threatSearchParamsMock, eventsCount: 50, + termsQueryAllowed: false, }); expect(signalsQueryMap.get('source-1')).toHaveLength(MAX_NUMBER_OF_SIGNAL_MATCHES); @@ -168,6 +172,7 @@ describe('getSignalsQueryMapFromThreatIndex', () => { const signalsQueryMap = await getSignalsQueryMapFromThreatIndex({ threatSearchParams: threatSearchParamsMock, eventsCount: 50, + termsQueryAllowed: false, }); expect(signalsQueryMap).toEqual(new Map()); @@ -201,6 +206,7 @@ describe('getSignalsQueryMapFromThreatIndex', () => { threatSearchParams: threatSearchParamsMock, eventsCount: 50, signalValueMap, + termsQueryAllowed: true, }); expect(signalsQueryMap).toEqual(new Map()); @@ -234,6 +240,7 @@ describe('getSignalsQueryMapFromThreatIndex', () => { threatSearchParams: threatSearchParamsMock, eventsCount: 50, signalValueMap, + termsQueryAllowed: true, }); const queries = [ @@ -283,6 +290,7 @@ describe('getSignalsQueryMapFromThreatIndex', () => { threatSearchParams: threatSearchParamsMock, eventsCount: 50, signalValueMap, + termsQueryAllowed: true, }); const queries = [ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/get_signals_map_from_threat_index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/get_signals_map_from_threat_index.ts index 0deb3beeee2e8..7d0f49b548f37 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/get_signals_map_from_threat_index.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/get_signals_map_from_threat_index.ts @@ -21,20 +21,34 @@ import { MAX_NUMBER_OF_SIGNAL_MATCHES } from './enrich_signal_threat_matches'; export type SignalsQueryMap = Map; -interface GetSignalsMatchesFromThreatIndexOptions { +interface GetSignalsQueryMapFromThreatIndexOptionsTerms { threatSearchParams: Omit; eventsCount: number; - signalValueMap?: SignalValuesMap; + signalValueMap: SignalValuesMap; + termsQueryAllowed: true; +} + +interface GetSignalsQueryMapFromThreatIndexOptionsMatch { + threatSearchParams: Omit; + eventsCount: number; + termsQueryAllowed: false; } /** * fetches threats and creates signals map from results, that matches signal is with list of threat queries */ -export const getSignalsQueryMapFromThreatIndex = async ({ - threatSearchParams, - eventsCount, - signalValueMap, -}: GetSignalsMatchesFromThreatIndexOptions): Promise => { +/** + * fetches threats and creates signals map from results, that matches signal is with list of threat queries + * @param options.termsQueryAllowed - if terms query allowed to be executed, then signalValueMap should be provided + * @param options.signalValueMap - map of signal values from terms query results + */ +export async function getSignalsQueryMapFromThreatIndex( + options: + | GetSignalsQueryMapFromThreatIndexOptionsTerms + | GetSignalsQueryMapFromThreatIndexOptionsMatch +): Promise { + const { threatSearchParams, eventsCount, termsQueryAllowed } = options; + let threatList: Awaited> | undefined; const signalsQueryMap = new Map(); // number of threat matches per signal is limited by MAX_NUMBER_OF_SIGNAL_MATCHES. Once it hits this number, threats stop to be processed for a signal @@ -50,9 +64,6 @@ export const getSignalsQueryMapFromThreatIndex = async ({ decodedQuery: ThreatMatchNamedQuery | ThreatTermNamedQuery; }) => { const signalMatch = signalsQueryMap.get(signalId); - if (!signalMatch) { - signalsQueryMap.set(signalId, []); - } const threatQuery = { id: threatHit._id, @@ -74,15 +85,9 @@ export const getSignalsQueryMapFromThreatIndex = async ({ } }; - while ( - maxThreatsReachedMap.size < eventsCount && - (threatList ? threatList?.hits.hits.length > 0 : true) - ) { - threatList = await getThreatList({ - ...threatSearchParams, - searchAfter: threatList?.hits.hits[threatList.hits.hits.length - 1].sort || undefined, - }); + threatList = await getThreatList({ ...threatSearchParams, searchAfter: undefined }); + while (maxThreatsReachedMap.size < eventsCount && threatList?.hits.hits.length > 0) { threatList.hits.hits.forEach((threatHit) => { const matchedQueries = threatHit?.matched_queries || []; @@ -90,13 +95,13 @@ export const getSignalsQueryMapFromThreatIndex = async ({ const decodedQuery = decodeThreatMatchNamedQuery(matchedQuery); const signalId = decodedQuery.id; - if (decodedQuery.queryType === ThreatMatchQueryType.term) { + if (decodedQuery.queryType === ThreatMatchQueryType.term && termsQueryAllowed) { const threatValue = get(threatHit?._source, decodedQuery.value); const values = Array.isArray(threatValue) ? threatValue : [threatValue]; values.forEach((value) => { - if (value && signalValueMap) { - const ids = signalValueMap[decodedQuery.field][value?.toString()]; + if (value && options.signalValueMap) { + const ids = options.signalValueMap[decodedQuery.field][value?.toString()]; ids?.forEach((id: string) => { addSignalValueToMap({ @@ -120,7 +125,12 @@ export const getSignalsQueryMapFromThreatIndex = async ({ } }); }); + + threatList = await getThreatList({ + ...threatSearchParams, + searchAfter: threatList.hits.hits[threatList.hits.hits.length - 1].sort, + }); } return signalsQueryMap; -}; +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/strategies/merge_all_fields_with_source.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/strategies/merge_all_fields_with_source.test.ts index 5aac1418cf45d..1a5733cbd7760 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/strategies/merge_all_fields_with_source.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/strategies/merge_all_fields_with_source.test.ts @@ -1327,6 +1327,35 @@ describe('merge_all_fields_with_source', () => { }); }); + test('does not add multi field values such as "process.command_line.text" to nested source when "process.command_line" has value', () => { + const _source: SignalSourceHit['_source'] = { + process: { + command_line: 'string longer than 10 characters', + }, + }; + const fields: SignalSourceHit['fields'] = { + 'process.command_line.text': ['string longer than 10 characters'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source; + expect(merged).toEqual(_source); + }); + + test('does not add multi field values such as "process.command_line.text" to nested source when "process.command_line" has array value', () => { + const _source: SignalSourceHit['_source'] = { + process: { + command_line: ['string longer than 10 characters'], + }, + }; + const fields: SignalSourceHit['fields'] = { + 'process.command_line.text': ['string longer than 10 characters'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source; + + expect(merged).toEqual(_source); + }); + test('multi-field values mixed with regular values will not be merged accidentally"', () => { const _source: SignalSourceHit['_source'] = {}; const fields: SignalSourceHit['fields'] = { @@ -1393,6 +1422,32 @@ describe('merge_all_fields_with_source', () => { foo: 'other_value_1', }); }); + + test('does not add multi field values such as "process.command_line.text" to flattened source when "process.command_line" has value', () => { + const _source: SignalSourceHit['_source'] = { + 'process.command_line': 'string longer than 10 characters', + }; + + const fields: SignalSourceHit['fields'] = { + 'process.command_line.text': ['string longer than 10 characters'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source; + expect(merged).toEqual(_source); + }); + + test('does not add multi field values such as "process.command_line.text" to flattened source when "process.command_line" has array value', () => { + const _source: SignalSourceHit['_source'] = { + 'process.command_line': ['string longer than 10 characters'], + }; + + const fields: SignalSourceHit['fields'] = { + 'process.command_line.text': ['string longer than 10 characters'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source; + expect(merged).toEqual(_source); + }); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/strategies/merge_all_fields_with_source.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/strategies/merge_all_fields_with_source.ts index b9193f952fd18..ab9cc86fa1049 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/strategies/merge_all_fields_with_source.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/strategies/merge_all_fields_with_source.ts @@ -15,8 +15,8 @@ import { isNestedObject } from '../utils/is_nested_object'; import { recursiveUnboxingFields } from '../utils/recursive_unboxing_fields'; import { isPrimitive } from '../utils/is_primitive'; import { isArrayOfPrimitives } from '../utils/is_array_of_primitives'; -import { arrayInPathExists } from '../utils/array_in_path_exists'; import { isTypeObject } from '../utils/is_type_object'; +import { isPathValid } from '../utils/is_path_valid'; /** * Merges all of "doc._source" with its "doc.fields" on a "best effort" basis. See ../README.md for more information @@ -107,7 +107,7 @@ const hasEarlyReturnConditions = ({ const valueInMergedDocument = get(fieldsKey, merged); return ( fieldsValue.length === 0 || - (valueInMergedDocument === undefined && arrayInPathExists(fieldsKey, merged)) || + (valueInMergedDocument === undefined && !isPathValid(fieldsKey, merged)) || (isObjectLikeOrArrayOfObjectLikes(valueInMergedDocument) && !isNestedObject(fieldsValue) && !isTypeObject(fieldsValue)) diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/strategies/merge_missing_fields_with_source.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/strategies/merge_missing_fields_with_source.test.ts index 911df7400ec63..7e997ceb0ee65 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/strategies/merge_missing_fields_with_source.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/strategies/merge_missing_fields_with_source.test.ts @@ -1283,6 +1283,38 @@ describe('merge_missing_fields_with_source', () => { foo: 'other_value_1', }); }); + + test('does not add multi field values such as "process.command_line.text" to nested source when "process.command_line" has value', () => { + const _source: SignalSourceHit['_source'] = { + '@timestamp': '2023-02-10T10:15:50Z', + process: { + command_line: 'string longer than 10 characters', + }, + }; + const fields: SignalSourceHit['fields'] = { + 'process.command_line.text': ['string longer than 10 characters'], + '@timestamp': ['2023-02-10T10:15:50.000Z'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source; + expect(merged).toEqual(_source); + }); + + test('does not add multi field values such as "process.command_line.text" to nested source when "process.command_line" has array value', () => { + const _source: SignalSourceHit['_source'] = { + '@timestamp': '2023-02-10T10:15:50Z', + process: { + command_line: ['string longer than 10 characters'], + }, + }; + const fields: SignalSourceHit['fields'] = { + 'process.command_line.text': ['string longer than 10 characters'], + '@timestamp': ['2023-02-10T10:15:50.000Z'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source; + expect(merged).toEqual(_source); + }); }); describe('flattened keys for the _source', () => { @@ -1331,6 +1363,36 @@ describe('merge_missing_fields_with_source', () => { foo: 'other_value_1', }); }); + + test('does not add multi field values such as "process.command_line.text" to flattened source when "process.command_line" has value', () => { + const _source: SignalSourceHit['_source'] = { + '@timestamp': '2023-02-10T10:15:50Z', + 'process.command_line': 'string longer than 10 characters', + }; + + const fields: SignalSourceHit['fields'] = { + 'process.command_line.text': ['string longer than 10 characters'], + '@timestamp': ['2023-02-10T10:15:50.000Z'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source; + expect(merged).toEqual(_source); + }); + + test('does not add multi field values such as "process.command_line.text" to flattened source when "process.command_line" has array value', () => { + const _source: SignalSourceHit['_source'] = { + '@timestamp': '2023-02-10T10:15:50Z', + 'process.command_line': ['string longer than 10 characters'], + }; + + const fields: SignalSourceHit['fields'] = { + 'process.command_line.text': ['string longer than 10 characters'], + '@timestamp': ['2023-02-10T10:15:50.000Z'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source; + expect(merged).toEqual(_source); + }); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/strategies/merge_missing_fields_with_source.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/strategies/merge_missing_fields_with_source.ts index 3efe1a7925d9b..e4bf563f4f055 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/strategies/merge_missing_fields_with_source.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/strategies/merge_missing_fields_with_source.ts @@ -12,8 +12,8 @@ import { filterFieldEntries } from '../utils/filter_field_entries'; import type { FieldsType, MergeStrategyFunction } from '../types'; import { recursiveUnboxingFields } from '../utils/recursive_unboxing_fields'; import { isTypeObject } from '../utils/is_type_object'; -import { arrayInPathExists } from '../utils/array_in_path_exists'; import { isNestedObject } from '../utils/is_nested_object'; +import { isPathValid } from '../utils/is_path_valid'; /** * Merges only missing sections of "doc._source" with its "doc.fields" on a "best effort" basis. See ../README.md for more information @@ -79,7 +79,7 @@ const hasEarlyReturnConditions = ({ return ( fieldsValue.length === 0 || valueInMergedDocument !== undefined || - arrayInPathExists(fieldsKey, merged) || + !isPathValid(fieldsKey, merged) || isNestedObject(fieldsValue) || isTypeObject(fieldsValue) ); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/is_path_valid.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/is_path_valid.test.ts new file mode 100644 index 0000000000000..e899142bb7352 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/is_path_valid.test.ts @@ -0,0 +1,74 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isPathValid } from './is_path_valid'; + +describe('isPathValid', () => { + test('not valid when empty string and empty object', () => { + expect(isPathValid('', {})).toEqual(false); + }); + + test('valid when a path and empty object', () => { + expect(isPathValid('a.b.c', {})).toEqual(true); + }); + + test('not valid when a path and an array exists', () => { + expect(isPathValid('a', { a: [] })).toEqual(false); + }); + + test('not valid when a path and primitive value exists', () => { + expect(isPathValid('a', { a: 'test' })).toEqual(false); + expect(isPathValid('a', { a: 1 })).toEqual(false); + expect(isPathValid('a', { a: true })).toEqual(false); + }); + + test('valid when a path and object value exists', () => { + expect(isPathValid('a', { a: {} })).toEqual(true); + }); + + test('not valid when a path and an array exists within the parent path at level 1', () => { + expect(isPathValid('a.b', { a: [] })).toEqual(false); + }); + + test('not valid when a path and primitive value exists within the parent path at level 1', () => { + expect(isPathValid('a.b', { a: 'test' })).toEqual(false); + expect(isPathValid('a.b', { a: 1 })).toEqual(false); + expect(isPathValid('a.b', { a: true })).toEqual(false); + }); + + test('valid when a path and object value exists within the parent path at level 1', () => { + expect(isPathValid('a.b', { a: {} })).toEqual(true); + }); + + test('not valid when a path and an array exists within the parent path at level 2', () => { + expect(isPathValid('a.b.c', { a: { b: [] } })).toEqual(false); + }); + + test('not valid when a path and primitive value exists within the parent path at level 2', () => { + expect(isPathValid('a.b', { a: { b: 'test' } })).toEqual(false); + expect(isPathValid('a.b', { a: { b: 1 } })).toEqual(false); + expect(isPathValid('a.b', { a: { b: true } })).toEqual(false); + }); + + test('valid when a path and object value exists within the parent path at level 2', () => { + expect(isPathValid('a.b', { a: { b: {} } })).toEqual(true); + }); + + test('not valid when a path and an array exists within the parent path at level 3', () => { + expect(isPathValid('a.b.c', { a: { b: { c: [] } } })).toEqual(false); + }); + + test('not valid when a path and primitive value exists within the parent path at level 3', () => { + expect(isPathValid('a.b.c', { a: { b: { c: 'test' } } })).toEqual(false); + expect(isPathValid('a.b.c', { a: { b: { c: 1 } } })).toEqual(false); + expect(isPathValid('a.b.c', { a: { b: { c: true } } })).toEqual(false); + }); + + test('valid when a path and object value exists within the parent path at level 3', () => { + expect(isPathValid('a.b.c', { a: { b: { c: {} } } })).toEqual(true); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/is_path_valid.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/is_path_valid.ts new file mode 100644 index 0000000000000..c5038531baa2a --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/is_path_valid.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 { get, isPlainObject } from 'lodash/fp'; +import type { SignalSource } from '../../../types'; + +/** + * Returns true if path in SignalSource object is valid + * Path is valid if each field in hierarchy is object or undefined + * Path is not valid if ANY of field in hierarchy is not object or undefined + * @param path in source to check within source + * @param source The source document + * @returns boolean + */ +export const isPathValid = (path: string, source: SignalSource): boolean => { + if (!path) { + return false; + } + const splitPath = path.split('.'); + + return splitPath.every((_, index, array) => { + const newPath = [...array].splice(0, index + 1).join('.'); + const valueToCheck = get(newPath, source); + return valueToCheck === undefined || isPlainObject(valueToCheck); + }); +}; diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index db7a32670727b..cbc595a22bbf0 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -21,10 +21,10 @@ import type { Logger } from '@kbn/core/server'; import { SavedObjectsClient } from '@kbn/core/server'; import type { UsageCounter } from '@kbn/usage-collection-plugin/server'; -import { ECS_COMPONENT_TEMPLATE_NAME } from '@kbn/rule-registry-plugin/common/assets'; -import type { FieldMap } from '@kbn/rule-registry-plugin/common/field_map'; +import { ECS_COMPONENT_TEMPLATE_NAME } from '@kbn/alerting-plugin/server'; +import { mappingFromFieldMap } from '@kbn/alerting-plugin/common'; +import type { FieldMap } from '@kbn/alerts-as-data-utils'; import { technicalRuleFieldMap } from '@kbn/rule-registry-plugin/common/assets/field_maps/technical_rule_field_map'; -import { mappingFromFieldMap } from '@kbn/rule-registry-plugin/common/mapping_from_field_map'; import type { IRuleDataClient } from '@kbn/rule-registry-plugin/server'; import { Dataset } from '@kbn/rule-registry-plugin/server'; import type { ListPluginSetup } from '@kbn/lists-plugin/server'; @@ -220,6 +220,7 @@ export class Plugin implements ISecuritySolutionPlugin { Object.entries(aadFieldConversion).forEach(([key, value]) => { aliasesFieldMap[key] = { type: 'alias', + required: false, path: value, }; }); @@ -509,6 +510,7 @@ export class Plugin implements ISecuritySolutionPlugin { registerListsServerExtension: this.lists?.registerExtension, featureUsageService, experimentalFeatures: config.experimentalFeatures, + messageSigningService: plugins.fleet?.messageSigningService, }); this.telemetryReceiver.start( diff --git a/x-pack/plugins/security_solution/tsconfig.json b/x-pack/plugins/security_solution/tsconfig.json index 6f831e7633dbb..1cc1a355d3537 100644 --- a/x-pack/plugins/security_solution/tsconfig.json +++ b/x-pack/plugins/security_solution/tsconfig.json @@ -36,6 +36,7 @@ "@kbn/actions-plugin", "@kbn/alerting-plugin", "@kbn/cases-plugin", + "@kbn/cloud-defend-plugin", "@kbn/cloud-experiments-plugin", "@kbn/cloud-security-posture-plugin", "@kbn/encrypted-saved-objects-plugin", @@ -141,5 +142,7 @@ "@kbn/securitysolution-ecs", "@kbn/cell-actions", "@kbn/shared-ux-router", + "@kbn/alerts-as-data-utils", + "@kbn/expandable-flyout", ] } diff --git a/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_search_source_query.test.ts b/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_search_source_query.test.ts index 9a4026b582381..9afffb7c10ceb 100644 --- a/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_search_source_query.test.ts +++ b/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_search_source_query.test.ts @@ -124,6 +124,7 @@ describe('fetchSearchSourceQuery', () => { Object { "range": Object { "time": Object { + "format": "strict_date_optional_time", "gt": "2020-02-09T23:12:41.941Z", }, }, diff --git a/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_search_source_query.ts b/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_search_source_query.ts index e033f9c6ef4a8..bc22a228ce988 100644 --- a/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_search_source_query.ts +++ b/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_search_source_query.ts @@ -122,7 +122,11 @@ export function updateSearchSource( // add additional filter for documents with a timestamp greater then // the timestamp of the previous run, so that those documents are not counted twice const field = index.fields.find((f) => f.name === timeFieldName); - const addTimeRangeField = buildRangeFilter(field!, { gt: latestTimestamp }, index); + const addTimeRangeField = buildRangeFilter( + field!, + { gt: latestTimestamp, format: 'strict_date_optional_time' }, + index + ); filters.push(addTimeRangeField); } } diff --git a/x-pack/plugins/stack_connectors/public/connector_types/es_index/es_index_params.tsx b/x-pack/plugins/stack_connectors/public/connector_types/es_index/es_index_params.tsx index be34c715fb8d4..fc51ae0ba1ccf 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/es_index/es_index_params.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/es_index/es_index_params.tsx @@ -6,6 +6,7 @@ */ import React, { useEffect, useState } from 'react'; +import { isEmpty } from 'lodash'; import { EuiIcon, EuiText, @@ -45,10 +46,25 @@ export const IndexParamsFields = ({ ); const [isActionConnectorChanged, setIsActionConnectorChanged] = useState(false); - const getDocumentToIndex = (doc: Array> | undefined) => - doc && doc.length > 0 ? (doc[0] as unknown as string) : undefined; + const getDocumentToIndex = (docs: Array> | undefined) => { + // 'documents' param is stored as an array of objects but the JSON editor expects a single + // stringified object - const [documentToIndex, setDocumentToIndex] = useState( + // check that param is a non-empty array + return docs && docs.length > 0 + ? // if the array entry is a string, we can pass it directly to the JSON editor + typeof docs[0] === 'string' + ? docs[0] + : // otherwise check that the array entry is non-empty as sometimes we + // use an empty object to trigger validation but we don't want to auto-populate with an empty object + !isEmpty(docs[0]) + ? // if non-empty object, stringify it into format that JSON editor expects + JSON.stringify(docs[0], null, 2) + : null + : undefined; + }; + + const [documentToIndex, setDocumentToIndex] = useState( getDocumentToIndex(documents) ); const [alertHistoryIndexSuffix, setAlertHistoryIndexSuffix] = useState( @@ -58,9 +74,6 @@ export const IndexParamsFields = ({ useEffect(() => { setDocumentToIndex(getDocumentToIndex(documents)); - if (documents === null) { - setDocumentToIndex('{}'); - } }, [documents]); useEffect(() => { @@ -77,10 +90,14 @@ export const IndexParamsFields = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [actionConnector?.id]); - const onDocumentsChange = (updatedDocuments: string) => { + const onDocumentsChange = (updatedDocuments: string | null) => { try { - const documentsJSON = JSON.parse(updatedDocuments); - editAction('documents', [documentsJSON], index); + if (updatedDocuments != null) { + const documentsJSON = JSON.parse(updatedDocuments); + editAction('documents', [documentsJSON], index); + } else { + editAction('documents', updatedDocuments, index); + } setDocumentToIndex(updatedDocuments); } catch (e) { // set document as empty to turn on the validation for non empty valid JSON object @@ -180,11 +197,7 @@ export const IndexParamsFields = ({ messageVariables={messageVariables} paramsProperty={'documents'} data-test-subj="documentToIndex" - inputTargetValue={ - documentToIndex === null - ? '{}' // need this to trigger validation - : documentToIndex - } + inputTargetValue={documentToIndex} label={documentsFieldLabel} aria-label={i18n.translate('xpack.stackConnectors.components.index.jsonDocAriaLabel', { defaultMessage: 'Code editor', @@ -202,7 +215,7 @@ export const IndexParamsFields = ({ onBlur={() => { if (!documentToIndex) { // set document as empty to turn on the validation for non empty valid JSON object - onDocumentsChange('{}'); + onDocumentsChange(null); } }} /> diff --git a/x-pack/plugins/synthetics/common/constants/synthetics/rest_api.ts b/x-pack/plugins/synthetics/common/constants/synthetics/rest_api.ts index 7e61e0a9e5e08..a0875d9df4977 100644 --- a/x-pack/plugins/synthetics/common/constants/synthetics/rest_api.ts +++ b/x-pack/plugins/synthetics/common/constants/synthetics/rest_api.ts @@ -12,6 +12,7 @@ export enum SYNTHETICS_API_URLS { OVERVIEW_STATUS = `/internal/synthetics/overview_status`, INDEX_SIZE = `/internal/synthetics/index_size`, PARAMS = `/synthetics/params`, + PRIVATE_LOCATIONS = `/synthetics/private_locations`, SYNC_GLOBAL_PARAMS = `/synthetics/sync_global_params`, ENABLE_DEFAULT_ALERTING = `/synthetics/enable_default_alerting`, JOURNEY = `/internal/synthetics/journey/{checkGroup}`, diff --git a/x-pack/plugins/synthetics/common/rules/alert_actions.test.ts b/x-pack/plugins/synthetics/common/rules/alert_actions.test.ts index 19fc96ef86cf1..219790bfe991c 100644 --- a/x-pack/plugins/synthetics/common/rules/alert_actions.test.ts +++ b/x-pack/plugins/synthetics/common/rules/alert_actions.test.ts @@ -8,9 +8,11 @@ import { populateAlertActions } from './alert_actions'; import { ActionConnector } from './types'; import { MONITOR_STATUS } from '../constants/uptime_alerts'; +import { MONITOR_STATUS as SYNTHETICS_MONITOR_STATUS } from '../constants/synthetics_alerts'; import { MonitorStatusTranslations } from '../translations'; +import { SyntheticsMonitorStatusTranslations } from './synthetics/translations'; -describe('Alert Actions factory', () => { +describe('Legacy Alert Actions factory', () => { it('generate expected action for pager duty', async () => { const resp = populateAlertActions({ groupId: MONITOR_STATUS.id, @@ -32,6 +34,7 @@ describe('Alert Actions factory', () => { defaultRecoveryMessage: MonitorStatusTranslations.defaultRecoveryMessage, defaultSubjectMessage: MonitorStatusTranslations.defaultSubjectMessage, }, + isLegacy: true, }); expect(resp).toEqual([ { @@ -57,6 +60,66 @@ describe('Alert Actions factory', () => { ]); }); + it('generate expected action for index', async () => { + const resp = populateAlertActions({ + groupId: MONITOR_STATUS.id, + defaultActions: [ + { + actionTypeId: '.index', + group: 'xpack.uptime.alerts.actionGroups.monitorStatus', + params: { + dedupKey: 'always-downxpack.uptime.alerts.actionGroups.monitorStatus', + eventAction: 'trigger', + severity: 'error', + summary: MonitorStatusTranslations.defaultActionMessage, + }, + id: 'f2a3b195-ed76-499a-805d-82d24d4eeba9', + }, + ] as unknown as ActionConnector[], + translations: { + defaultActionMessage: MonitorStatusTranslations.defaultActionMessage, + defaultRecoveryMessage: MonitorStatusTranslations.defaultRecoveryMessage, + defaultSubjectMessage: MonitorStatusTranslations.defaultSubjectMessage, + }, + isLegacy: true, + }); + expect(resp).toEqual([ + { + group: 'recovered', + id: 'f2a3b195-ed76-499a-805d-82d24d4eeba9', + params: { + documents: [ + { + latestErrorMessage: '', + monitorName: '{{context.monitorName}}', + monitorUrl: '{{{context.monitorUrl}}}', + observerLocation: '{{context.observerLocation}}', + statusMessage: + 'Alert for monitor {{context.monitorName}} with url {{{context.monitorUrl}}} from {{context.observerLocation}} has recovered', + }, + ], + indexOverride: null, + }, + }, + { + group: 'xpack.uptime.alerts.actionGroups.monitorStatus', + id: 'f2a3b195-ed76-499a-805d-82d24d4eeba9', + params: { + documents: [ + { + latestErrorMessage: '{{{context.latestErrorMessage}}}', + monitorName: '{{context.monitorName}}', + monitorUrl: '{{{context.monitorUrl}}}', + observerLocation: '{{context.observerLocation}}', + statusMessage: '{{{context.statusMessage}}}', + }, + ], + indexOverride: null, + }, + }, + ]); + }); + it('generate expected action for slack action connector', async () => { const resp = populateAlertActions({ groupId: MONITOR_STATUS.id, @@ -104,3 +167,157 @@ describe('Alert Actions factory', () => { ]); }); }); + +describe('Alert Actions factory', () => { + it('generate expected action for pager duty', async () => { + const resp = populateAlertActions({ + groupId: SYNTHETICS_MONITOR_STATUS.id, + defaultActions: [ + { + actionTypeId: '.pagerduty', + group: 'xpack.uptime.alerts.actionGroups.monitorStatus', + params: { + dedupKey: 'always-downxpack.uptime.alerts.actionGroups.monitorStatus', + eventAction: 'trigger', + severity: 'error', + summary: SyntheticsMonitorStatusTranslations.defaultActionMessage, + }, + id: 'f2a3b195-ed76-499a-805d-82d24d4eeba9', + }, + ] as unknown as ActionConnector[], + translations: { + defaultActionMessage: SyntheticsMonitorStatusTranslations.defaultActionMessage, + defaultRecoveryMessage: SyntheticsMonitorStatusTranslations.defaultRecoveryMessage, + defaultSubjectMessage: SyntheticsMonitorStatusTranslations.defaultSubjectMessage, + }, + }); + expect(resp).toEqual([ + { + group: 'recovered', + id: 'f2a3b195-ed76-499a-805d-82d24d4eeba9', + params: { + dedupKey: expect.any(String), + eventAction: 'resolve', + summary: + 'The alert for the monitor {{context.monitorName}} checking {{{context.monitorUrl}}} from {{context.locationName}} is no longer active: {{context.recoveryReason}}.', + }, + }, + { + group: 'xpack.synthetics.alerts.actionGroups.monitorStatus', + id: 'f2a3b195-ed76-499a-805d-82d24d4eeba9', + params: { + dedupKey: expect.any(String), + eventAction: 'trigger', + severity: 'error', + summary: SyntheticsMonitorStatusTranslations.defaultActionMessage, + }, + }, + ]); + }); + + it('generate expected action for index', async () => { + const resp = populateAlertActions({ + groupId: SYNTHETICS_MONITOR_STATUS.id, + defaultActions: [ + { + actionTypeId: '.index', + group: 'xpack.synthetics.alerts.actionGroups.monitorStatus', + params: { + dedupKey: 'always-downxpack.uptime.alerts.actionGroups.monitorStatus', + eventAction: 'trigger', + severity: 'error', + summary: SyntheticsMonitorStatusTranslations.defaultActionMessage, + }, + id: 'f2a3b195-ed76-499a-805d-82d24d4eeba9', + }, + ] as unknown as ActionConnector[], + translations: { + defaultActionMessage: SyntheticsMonitorStatusTranslations.defaultActionMessage, + defaultRecoveryMessage: SyntheticsMonitorStatusTranslations.defaultRecoveryMessage, + defaultSubjectMessage: SyntheticsMonitorStatusTranslations.defaultSubjectMessage, + }, + }); + expect(resp).toEqual([ + { + group: 'recovered', + id: 'f2a3b195-ed76-499a-805d-82d24d4eeba9', + params: { + documents: [ + { + latestErrorMessage: '{{{context.latestErrorMessage}}}', + monitorName: '{{context.monitorName}}', + monitorUrl: '{{{context.monitorUrl}}}', + observerLocation: '{{context.locationName}}', + statusMessage: '{{{context.status}}}', + recoveryReason: '{{context.recoveryReason}}', + }, + ], + indexOverride: null, + }, + }, + { + group: 'xpack.synthetics.alerts.actionGroups.monitorStatus', + id: 'f2a3b195-ed76-499a-805d-82d24d4eeba9', + params: { + documents: [ + { + latestErrorMessage: '{{{context.lastErrorMessage}}}', + monitorName: '{{context.monitorName}}', + monitorUrl: '{{{context.monitorUrl}}}', + observerLocation: '{{context.locationName}}', + statusMessage: '{{{context.status}}}', + }, + ], + indexOverride: null, + }, + }, + ]); + }); + + it('generate expected action for slack action connector', async () => { + const resp = populateAlertActions({ + groupId: SYNTHETICS_MONITOR_STATUS.id, + defaultActions: [ + { + actionTypeId: '.pagerduty', + group: 'xpack.synthetics.alerts.actionGroups.monitorStatus', + params: { + dedupKey: 'always-downxpack.uptime.alerts.actionGroups.monitorStatus', + eventAction: 'trigger', + severity: 'error', + summary: + 'Monitor {{context.monitorName}} with url {{{context.monitorUrl}}} from {{context.observerLocation}} {{{context.statusMessage}}} The latest error message is {{{context.latestErrorMessage}}}', + }, + id: 'f2a3b195-ed76-499a-805d-82d24d4eeba9', + }, + ] as unknown as ActionConnector[], + translations: { + defaultActionMessage: SyntheticsMonitorStatusTranslations.defaultActionMessage, + defaultRecoveryMessage: SyntheticsMonitorStatusTranslations.defaultRecoveryMessage, + defaultSubjectMessage: SyntheticsMonitorStatusTranslations.defaultSubjectMessage, + }, + }); + expect(resp).toEqual([ + { + group: 'recovered', + id: 'f2a3b195-ed76-499a-805d-82d24d4eeba9', + params: { + dedupKey: expect.any(String), + eventAction: 'resolve', + summary: + 'The alert for the monitor {{context.monitorName}} checking {{{context.monitorUrl}}} from {{context.locationName}} is no longer active: {{context.recoveryReason}}.', + }, + }, + { + group: 'xpack.synthetics.alerts.actionGroups.monitorStatus', + id: 'f2a3b195-ed76-499a-805d-82d24d4eeba9', + params: { + dedupKey: expect.any(String), + eventAction: 'trigger', + severity: 'error', + summary: SyntheticsMonitorStatusTranslations.defaultActionMessage, + }, + }, + ]); + }); +}); diff --git a/x-pack/plugins/synthetics/common/rules/alert_actions.ts b/x-pack/plugins/synthetics/common/rules/alert_actions.ts index 9c32fbdf8d3cf..3f8cedf715536 100644 --- a/x-pack/plugins/synthetics/common/rules/alert_actions.ts +++ b/x-pack/plugins/synthetics/common/rules/alert_actions.ts @@ -43,11 +43,13 @@ export function populateAlertActions({ defaultEmail, groupId, translations, + isLegacy = false, }: { groupId: string; defaultActions: ActionConnector[]; defaultEmail?: DefaultEmail; translations: Translations; + isLegacy?: boolean; }) { const actions: RuleAction[] = []; defaultActions.forEach((aId) => { @@ -78,8 +80,8 @@ export function populateAlertActions({ actions.push(recoveredAction); break; case INDEX_ACTION_ID: - action.params = getIndexActionParams(translations); - recoveredAction.params = getIndexActionParams(translations, true); + action.params = getIndexActionParams(translations, false, isLegacy); + recoveredAction.params = getIndexActionParams(translations, true, isLegacy); actions.push(recoveredAction); break; case SERVICE_NOW_ACTION_ID: @@ -119,8 +121,12 @@ export function populateAlertActions({ return actions; } -function getIndexActionParams(translations: Translations, recovery = false): IndexActionParams { - if (recovery) { +function getIndexActionParams( + translations: Translations, + recovery = false, + isLegacy = false +): IndexActionParams { + if (isLegacy && recovery) { return { documents: [ { @@ -134,14 +140,45 @@ function getIndexActionParams(translations: Translations, recovery = false): Ind indexOverride: null, }; } + + if (isLegacy) { + return { + documents: [ + { + monitorName: '{{context.monitorName}}', + monitorUrl: '{{{context.monitorUrl}}}', + statusMessage: '{{{context.statusMessage}}}', + latestErrorMessage: '{{{context.latestErrorMessage}}}', + observerLocation: '{{context.observerLocation}}', + }, + ], + indexOverride: null, + }; + } + + if (recovery) { + return { + documents: [ + { + monitorName: '{{context.monitorName}}', + monitorUrl: '{{{context.monitorUrl}}}', + statusMessage: '{{{context.status}}}', + latestErrorMessage: '{{{context.latestErrorMessage}}}', + observerLocation: '{{context.locationName}}', + recoveryReason: '{{context.recoveryReason}}', + }, + ], + indexOverride: null, + }; + } return { documents: [ { monitorName: '{{context.monitorName}}', monitorUrl: '{{{context.monitorUrl}}}', - statusMessage: '{{{context.statusMessage}}}', - latestErrorMessage: '{{{context.latestErrorMessage}}}', - observerLocation: '{{context.observerLocation}}', + statusMessage: '{{{context.status}}}', + latestErrorMessage: '{{{context.lastErrorMessage}}}', + observerLocation: '{{context.locationName}}', }, ], indexOverride: null, diff --git a/x-pack/plugins/synthetics/common/rules/uptime_rule_field_map.ts b/x-pack/plugins/synthetics/common/rules/uptime_rule_field_map.ts index ff69d3a5e6e7f..be097ed8d8268 100644 --- a/x-pack/plugins/synthetics/common/rules/uptime_rule_field_map.ts +++ b/x-pack/plugins/synthetics/common/rules/uptime_rule_field_map.ts @@ -9,48 +9,62 @@ export const uptimeRuleFieldMap = { // common fields 'monitor.id': { type: 'keyword', + required: false, }, 'url.full': { type: 'keyword', + required: false, }, 'observer.geo.name': { type: 'keyword', + required: false, }, // monitor status alert fields 'error.message': { type: 'text', + required: false, }, 'agent.name': { type: 'keyword', + required: false, }, 'monitor.name': { type: 'keyword', + required: false, }, 'monitor.type': { type: 'keyword', + required: false, }, // tls alert fields 'tls.server.x509.issuer.common_name': { type: 'keyword', + required: false, }, 'tls.server.x509.subject.common_name': { type: 'keyword', + required: false, }, 'tls.server.x509.not_after': { type: 'date', + required: false, }, 'tls.server.x509.not_before': { type: 'date', + required: false, }, 'tls.server.hash.sha256': { type: 'keyword', + required: false, }, // anomaly alert fields 'anomaly.start': { type: 'date', + required: false, }, 'anomaly.bucket_span.minutes': { type: 'keyword', + required: false, }, } as const; diff --git a/x-pack/plugins/synthetics/common/runtime_types/ping/error_state.ts b/x-pack/plugins/synthetics/common/runtime_types/ping/error_state.ts index 86b0b07052eac..cf09da6eb92fc 100644 --- a/x-pack/plugins/synthetics/common/runtime_types/ping/error_state.ts +++ b/x-pack/plugins/synthetics/common/runtime_types/ping/error_state.ts @@ -6,14 +6,26 @@ */ import * as t from 'io-ts'; +export const StateEndsCodec = t.type({ + duration_ms: t.union([t.string, t.number]), + checks: t.number, + ends: t.union([t.string, t.null]), + started_at: t.string, + id: t.string, + up: t.number, + down: t.number, + status: t.string, +}); export const ErrorStateCodec = t.type({ - duration_ms: t.string, + duration_ms: t.union([t.string, t.number]), checks: t.number, - ends: t.union([t.string, t.null]), + ends: t.union([StateEndsCodec, t.null]), started_at: t.string, id: t.string, up: t.number, down: t.number, status: t.string, }); + +export type ErrorState = t.TypeOf; 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 index 8cb69f498b066..e9aec6dc3f1c4 100644 --- a/x-pack/plugins/synthetics/e2e/journeys/synthetics/private_locations.journey.ts +++ b/x-pack/plugins/synthetics/e2e/journeys/synthetics/private_locations.journey.ts @@ -149,4 +149,20 @@ journey(`PrivateLocationsSettings`, async ({ page, params }) => { await page.click('button:has-text("Delete location")'); await page.click('text=Create your first private location'); }); + + step('login with non super user', async () => { + await page.click('[data-test-subj="userMenuAvatar"]'); + await page.click('text="Log out"'); + await syntheticsApp.loginToKibana('viewer', 'changeme'); + }); + + step('viewer user cannot add locations', async () => { + await syntheticsApp.navigateToSettings(false); + await page.click('text=Private Locations'); + await page.waitForSelector( + `text="You're missing some Kibana privileges to manage private locations"` + ); + const createLocationBtn = await page.getByRole('button', { name: 'Create location' }); + expect(await createLocationBtn.getAttribute('disabled')).toEqual(''); + }); }); diff --git a/x-pack/plugins/synthetics/e2e/journeys/synthetics/test_run_details.journey.ts b/x-pack/plugins/synthetics/e2e/journeys/synthetics/test_run_details.journey.ts index ea8f70742f578..e9f931e64bf04 100644 --- a/x-pack/plugins/synthetics/e2e/journeys/synthetics/test_run_details.journey.ts +++ b/x-pack/plugins/synthetics/e2e/journeys/synthetics/test_run_details.journey.ts @@ -65,6 +65,6 @@ journey(`TestRunDetailsPage`, async ({ page, params }) => { await page.waitForSelector('text=Test run details'); await page.waitForSelector('text=Go to https://www.google.com'); - await page.waitForSelector('text=After 2.1 s'); + await page.waitForSelector('text=After 2.12 s'); }); }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/monitor_details_panel.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/monitor_details_panel.tsx index 2bfd04fa26d57..9aa9d90e29ee9 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/monitor_details_panel.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/monitor_details_panel.tsx @@ -40,19 +40,25 @@ const DescriptionLabel = euiStyled(EuiDescriptionListDescription)` width: 60%; `; +export interface MonitorDetailsPanelProps { + latestPing?: Ping; + loading: boolean; + configId: string; + monitor: EncryptedSyntheticsSavedMonitor | null; + hideEnabled?: boolean; + hideLocations?: boolean; + hasBorder?: boolean; +} + export const MonitorDetailsPanel = ({ monitor, latestPing, loading, configId, hideEnabled = false, -}: { - latestPing?: Ping; - loading: boolean; - configId: string; - monitor: EncryptedSyntheticsSavedMonitor | null; - hideEnabled?: boolean; -}) => { + hideLocations = false, + hasBorder = true, +}: MonitorDetailsPanelProps) => { const dispatch = useDispatch(); if (!monitor) { @@ -60,7 +66,12 @@ export const MonitorDetailsPanel = ({ } return ( - + @@ -116,10 +127,15 @@ export const MonitorDetailsPanel = ({ {FREQUENCY_LABEL} {frequencyStr(monitor[ConfigKey.SCHEDULE])} - {LOCATIONS_LABEL} - - - + + {!hideLocations && ( + <> + {LOCATIONS_LABEL} + + + + + )} {TAGS_LABEL} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/permissions.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/permissions.tsx index e9094bbc9a3f0..97a9ac3e62ce3 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/permissions.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/permissions.tsx @@ -6,13 +6,14 @@ */ import React, { ReactNode } from 'react'; -import { EuiCallOut, EuiToolTip } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { EuiCallOut, EuiToolTip, EuiCode } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; export const FleetPermissionsCallout = () => { return ( - -

      {NEED_FLEET_READ_AGENT_POLICIES_PERMISSION}

      + +

      {NEED_PRIVATE_LOCATIONS_PERMISSION}

      ); }; @@ -62,26 +63,32 @@ function getRestrictionReasonLabel( : undefined; } -export const NEED_PERMISSIONS = i18n.translate( - 'xpack.synthetics.monitorManagement.needPermissions', +export const NEED_PERMISSIONS_PRIVATE_LOCATIONS = i18n.translate( + 'xpack.synthetics.monitorManagement.privateLocations.needPermissions', { - defaultMessage: 'Need permissions', + defaultMessage: "You're missing some Kibana privileges to manage private locations", } ); -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.', - } +export const ALL = i18n.translate('xpack.synthetics.monitorManagement.priviledges.all', { + defaultMessage: 'All', +}); + +export const NEED_PRIVATE_LOCATIONS_PERMISSION = ( + {`"${ALL}"`}, + }} + /> ); export const CANNOT_SAVE_INTEGRATION_LABEL = i18n.translate( 'xpack.synthetics.monitorManagement.cannotSaveIntegration', { defaultMessage: - 'You are not authorized to update integrations. Integrations write permissions are required.', + 'You are not authorized to manage private locations. It requires the "All" Kibana privilege for both Fleet and Integrations.', } ); @@ -89,7 +96,7 @@ const CANNOT_PERFORM_ACTION_FLEET = i18n.translate( 'xpack.synthetics.monitorManagement.noFleetPermission', { defaultMessage: - 'You are not authorized to perform this action. Integrations write permissions are required.', + 'You are not authorized to perform this action. It requires the "All" Kibana privilege for Integrations.', } ); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/browser_steps_list.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/browser_steps_list.tsx index c90a00710f32f..a9341eae5fa69 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/browser_steps_list.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/browser_steps_list.tsx @@ -6,7 +6,7 @@ */ import { i18n } from '@kbn/i18n'; -import React, { CSSProperties, ReactElement, useState } from 'react'; +import React, { CSSProperties, ReactElement, useCallback, useEffect, useState } from 'react'; import { EuiBasicTable, EuiBasicTableColumn, @@ -62,25 +62,38 @@ export const BrowserStepsList = ({ Record >({}); - const toggleDetails = (item: JourneyStep) => { - const itemIdToExpandedRowMapValues = { ...itemIdToExpandedRowMap }; - if (itemIdToExpandedRowMapValues[item._id]) { - delete itemIdToExpandedRowMapValues[item._id]; - } else { - if (testNowMode) { - itemIdToExpandedRowMapValues[item._id] = ( - - - - - - ); - } else { - itemIdToExpandedRowMapValues[item._id] = <>; - } + const toggleDetails = useCallback( + (item: JourneyStep) => { + setItemIdToExpandedRowMap((prevState) => { + const itemIdToExpandedRowMapValues = { ...prevState }; + if (itemIdToExpandedRowMapValues[item._id]) { + delete itemIdToExpandedRowMapValues[item._id]; + } else { + if (testNowMode) { + itemIdToExpandedRowMapValues[item._id] = ( + + + + + + ); + } else { + itemIdToExpandedRowMapValues[item._id] = <>; + } + } + return itemIdToExpandedRowMapValues; + }); + }, + [steps, testNowMode] + ); + + const failedStep = stepEnds?.find((step) => step.synthetics.step?.status === 'failed'); + + useEffect(() => { + if (failedStep && showExpand) { + toggleDetails(failedStep); } - setItemIdToExpandedRowMap(itemIdToExpandedRowMapValues); - }; + }, [failedStep, showExpand, toggleDetails]); const columns: Array> = [ ...(showExpand diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/components/error_duration.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/components/error_duration.tsx index 9b65149be3ad0..bac7b09ecbf84 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/components/error_duration.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/components/error_duration.tsx @@ -8,7 +8,8 @@ import React from 'react'; import { EuiDescriptionList } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import moment from 'moment'; +import moment, { Moment } from 'moment'; +import { useFindMyKillerState } from '../hooks/use_find_my_killer_state'; import { useErrorFailedTests } from '../hooks/use_last_error_state'; export const ErrorDuration: React.FC = () => { @@ -16,13 +17,41 @@ export const ErrorDuration: React.FC = () => { const state = failedTests?.[0]?.state; - const duration = state ? moment().diff(moment(state?.started_at), 'minutes') : 0; + const { killerState } = useFindMyKillerState(); - return ( - - ); + const endsAt = killerState?.timestamp ? moment(killerState?.timestamp) : moment(); + const startedAt = moment(state?.started_at); + + const duration = state ? getErrorDuration(startedAt, endsAt) : 0; + + return ; }; const ERROR_DURATION = i18n.translate('xpack.synthetics.errorDetails.errorDuration', { defaultMessage: 'Error duration', }); + +const getErrorDuration = (startedAt: Moment, endsAt: Moment) => { + // const endsAt = state.ends ? moment(state.ends) : moment(); + // const startedAt = moment(state?.started_at); + + const diffInDays = endsAt.diff(startedAt, 'days'); + if (diffInDays > 1) { + return i18n.translate('xpack.synthetics.errorDetails.errorDuration.days', { + defaultMessage: '{value} days', + values: { value: diffInDays }, + }); + } + const diffInHours = endsAt.diff(startedAt, 'hours'); + if (diffInHours > 1) { + return i18n.translate('xpack.synthetics.errorDetails.errorDuration.hours', { + defaultMessage: '{value} hours', + values: { value: diffInHours }, + }); + } + const diffInMinutes = endsAt.diff(startedAt, 'minutes'); + return i18n.translate('xpack.synthetics.errorDetails.errorDuration.mins', { + defaultMessage: '{value} mins', + values: { value: diffInMinutes }, + }); +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/components/error_timeline.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/components/error_timeline.tsx index 30461842e963f..d2e0b12793bc6 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/components/error_timeline.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/components/error_timeline.tsx @@ -5,8 +5,32 @@ * 2.0. */ import React from 'react'; +import { EuiLoadingContent } from '@elastic/eui'; +import moment from 'moment'; +import { Ping } from '../../../../../../common/runtime_types'; import { MonitorFailedTests } from '../../monitor_details/monitor_errors/failed_tests'; -export const ErrorTimeline = () => { - return ; +export const ErrorTimeline = ({ lastTestRun }: { lastTestRun?: Ping }) => { + if (!lastTestRun) { + return ; + } + const diff = moment(lastTestRun.monitor.timespan?.lt).diff( + moment(lastTestRun.monitor.timespan?.gte), + 'minutes' + ); + const startedAt = lastTestRun?.state?.started_at; + + return ( + + ); }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/components/resolved_at.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/components/resolved_at.tsx index 75b1f9a31690b..76e8ca2ebaea1 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/components/resolved_at.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/components/resolved_at.tsx @@ -8,15 +8,13 @@ import React, { ReactElement } from 'react'; import { EuiDescriptionList } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { useErrorFailedTests } from '../hooks/use_last_error_state'; import { useFormatTestRunAt } from '../../../utils/monitor_test_result/test_time_formats'; +import { useFindMyKillerState } from '../hooks/use_find_my_killer_state'; export const ResolvedAt: React.FC = () => { - const { failedTests } = useErrorFailedTests(); + const { killerState } = useFindMyKillerState(); - const state = failedTests?.[0]?.state; - - let endsAt: string | ReactElement = useFormatTestRunAt(state?.ends ?? ''); + let endsAt: string | ReactElement = useFormatTestRunAt(killerState?.timestamp); if (!endsAt) { endsAt = 'N/A'; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/error_details_page.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/error_details_page.tsx index 3174d85776733..4a6ea2d3f34e1 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/error_details_page.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/error_details_page.tsx @@ -21,7 +21,7 @@ import { FailedTestsList } from './components/failed_tests_list'; import { ErrorTimeline } from './components/error_timeline'; import { useErrorDetailsBreadcrumbs } from './hooks/use_error_details_breadcrumbs'; import { StepImage } from '../step_details_page/step_screenshot/step_image'; -import { MonitorDetailsPanelContainer } from '../monitor_details/monitor_summary/monitor_details_panel'; +import { MonitorDetailsPanelContainer } from '../monitor_details/monitor_summary/monitor_details_panel_container'; export function ErrorDetailsPage() { const { failedTests, loading } = useErrorFailedTests(); @@ -43,7 +43,7 @@ export function ErrorDetailsPage() { return (
      - + @@ -71,16 +71,22 @@ export function ErrorDetailsPage() { - - {data?.details?.journey && failedStep && ( - - )} - + {data?.details?.journey && failedStep && ( + <> + + + + + + )} - - +
      diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/hooks/use_error_details_breadcrumbs.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/hooks/use_error_details_breadcrumbs.ts index 61cef2b818615..c96df76b40456 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/hooks/use_error_details_breadcrumbs.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/hooks/use_error_details_breadcrumbs.ts @@ -6,6 +6,7 @@ */ import { i18n } from '@kbn/i18n'; import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { useSelectedLocation } from '../../monitor_details/hooks/use_selected_location'; import { useTestRunDetailsBreadcrumbs } from '../../test_run_details/hooks/use_test_run_details_breadcrumbs'; import { useSelectedMonitor } from '../../monitor_details/hooks/use_selected_monitor'; import { ConfigKey } from '../../../../../../common/runtime_types'; @@ -19,10 +20,14 @@ export const useErrorDetailsBreadcrumbs = ( const { monitor } = useSelectedMonitor(); + const selectedLocation = useSelectedLocation(); + const errorsBreadcrumbs = [ { text: ERRORS_CRUMB, - href: `${appPath}/monitor/${monitor?.[ConfigKey.CONFIG_ID]}/errors`, + href: `${appPath}/monitor/${monitor?.[ConfigKey.CONFIG_ID]}/errors?locationId=${ + selectedLocation?.id + }`, }, ...(extraCrumbs ?? []), ]; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/hooks/use_error_failed_tests.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/hooks/use_error_failed_tests.tsx index 241c410038276..8b061d7c587f7 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/hooks/use_error_failed_tests.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/hooks/use_error_failed_tests.tsx @@ -57,7 +57,8 @@ export function useErrorFailedTests() { return useMemo(() => { const failedTests = data?.hits.hits?.map((doc) => { - return doc._source as Ping; + const source = doc._source as any; + return { ...source, timestamp: source['@timestamp'] } as Ping; }) ?? []; return { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/hooks/use_find_my_killer_state.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/hooks/use_find_my_killer_state.ts new file mode 100644 index 0000000000000..33f923c94d2ba --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/hooks/use_find_my_killer_state.ts @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useParams } from 'react-router-dom'; +import { useMemo } from 'react'; +import { useReduxEsSearch } from '../../../hooks/use_redux_es_search'; +import { Ping } from '../../../../../../common/runtime_types'; +import { + EXCLUDE_RUN_ONCE_FILTER, + SUMMARY_FILTER, +} from '../../../../../../common/constants/client_defaults'; +import { SYNTHETICS_INDEX_PATTERN } from '../../../../../../common/constants'; +import { useSyntheticsRefreshContext } from '../../../contexts'; +import { useGetUrlParams } from '../../../hooks'; + +export function useFindMyKillerState() { + const { lastRefresh } = useSyntheticsRefreshContext(); + + const { errorStateId, monitorId } = useParams<{ errorStateId: string; monitorId: string }>(); + + const { dateRangeStart, dateRangeEnd } = useGetUrlParams(); + + const { data, loading } = useReduxEsSearch( + { + index: SYNTHETICS_INDEX_PATTERN, + + body: { + // TODO: remove this once we have a better way to handle this mapping + runtime_mappings: { + 'state.ends.id': { + type: 'keyword', + }, + }, + size: 1, + query: { + bool: { + filter: [ + SUMMARY_FILTER, + EXCLUDE_RUN_ONCE_FILTER, + { + term: { + 'state.ends.id': errorStateId, + }, + }, + { + term: { + config_id: monitorId, + }, + }, + ], + }, + }, + sort: [{ '@timestamp': 'desc' }], + }, + }, + [lastRefresh, monitorId, dateRangeStart, dateRangeEnd], + { name: 'getStateWhichEndTheState' } + ); + + return useMemo(() => { + const killerStates = + data?.hits.hits?.map((doc) => { + const source = doc._source as any; + return { ...source, timestamp: source['@timestamp'] } as Ping; + }) ?? []; + + return { + loading, + killerState: killerStates?.[0], + }; + }, [data, loading]); +} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/route_config.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/route_config.tsx index c53de93fa5123..3e2d0f39000fe 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/route_config.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/error_details/route_config.tsx @@ -39,7 +39,7 @@ export const getErrorDetailsRouteConfig = ( ), rightSideItems: [ , - , + , , , ], diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_monitor_errors.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_monitor_errors.tsx index 6f6052af64a3f..e45e64dab1c15 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_monitor_errors.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_monitor_errors.tsx @@ -87,12 +87,15 @@ export function useMonitorErrors(monitorIdArg?: string) { }, }, }, - [lastRefresh, monitorId, monitorIdArg, dateRangeStart, dateRangeEnd], - { name: 'getMonitorErrors', isRequestReady: Boolean(selectedLocation?.label) } + [lastRefresh, monitorId, monitorIdArg, dateRangeStart, dateRangeEnd, selectedLocation?.label], + { + name: `getMonitorErrors/${dateRangeStart}/${dateRangeEnd}`, + isRequestReady: Boolean(selectedLocation?.label), + } ); return useMemo(() => { - const errorStates = (data?.aggregations?.errorStates.buckets ?? []).map((loc) => { + const errorStates = data?.aggregations?.errorStates.buckets?.map((loc) => { return loc.summary.hits.hits?.[0]._source as PingState; }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/errors_list.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/errors_list.tsx index ecd1f6141c7f4..d8fb9cc827fcd 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/errors_list.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/errors_list.tsx @@ -50,6 +50,10 @@ export const ErrorsList = ({ const selectedLocation = useSelectedLocation(); + const lastTestRun = errorStates?.sort((a, b) => { + return moment(b.state.started_at).valueOf() - moment(a.state.started_at).valueOf(); + })?.[0]; + const columns = [ { field: 'item.state.started_at', @@ -67,49 +71,56 @@ export const ErrorsList = ({ /> ); const isActive = isActiveState(item); - if (!isActive) { + if (!isActive || lastTestRun.state.id !== item.state.id) { return link; } return ( - {link} + + {link} + - Active + {ACTIVE_LABEL} ); }, }, + ...(isBrowserType + ? [ + { + field: 'monitor.check_group', + name: FAILED_STEP_LABEL, + truncateText: true, + sortable: (a: PingState) => { + const failedStep = failedSteps.find( + (step) => step.monitor.check_group === a.monitor.check_group + ); + if (!failedStep) { + return a.monitor.check_group; + } + return failedStep.synthetics?.step?.name; + }, + render: (value: string, item: PingState) => { + const failedStep = failedSteps.find((step) => step.monitor.check_group === value); + if (!failedStep) { + return <>--; + } + return ( + + {failedStep.synthetics?.step?.index}. {failedStep.synthetics?.step?.name} + + ); + }, + }, + ] + : []), { - field: 'monitor.check_group', - name: !isBrowserType ? ERROR_MESSAGE_LABEL : FAILED_STEP_LABEL, - truncateText: true, - sortable: (a: PingState) => { - const failedStep = failedSteps.find( - (step) => step.monitor.check_group === a.monitor.check_group - ); - if (!failedStep) { - return a.monitor.check_group; - } - return failedStep.synthetics?.step?.name; - }, - render: (value: string, item: PingState) => { - if (!isBrowserType) { - return {item.error.message ?? '--'}; - } - const failedStep = failedSteps.find((step) => step.monitor.check_group === value); - if (!failedStep) { - return <>--; - } - return ( - - {failedStep.synthetics?.step?.index}. {failedStep.synthetics?.step?.name} - - ); - }, + field: 'error.message', + name: ERROR_MESSAGE_LABEL, }, { field: 'state.duration_ms', @@ -157,6 +168,7 @@ export const ErrorsList = ({
      { +export const MonitorFailedTests = ({ + time, + allowBrushing = true, +}: { + time: { to: string; from: string }; + allowBrushing?: boolean; +}) => { const { observability } = useKibana().services; const { ExploratoryViewEmbeddable } = observability; @@ -41,7 +47,8 @@ export const MonitorFailedTests = ({ time }: { time: { to: string; from: string { time, reportDefinitions: { - ...(monitorId ? { 'monitor.id': [monitorId] } : { 'state.id': [errorStateId] }), + ...(monitorId ? { 'monitor.id': [monitorId] } : {}), + ...(errorStateId ? { 'state.id': [errorStateId] } : {}), }, dataType: 'synthetics', selectedMetricField: 'failed_tests', @@ -49,21 +56,25 @@ export const MonitorFailedTests = ({ time }: { time: { to: string; from: string }, ]} onBrushEnd={({ range }) => { - updateUrl({ - dateRangeStart: moment(range[0]).toISOString(), - dateRangeEnd: moment(range[1]).toISOString(), - }); + if (allowBrushing) { + updateUrl({ + dateRangeStart: moment(range[0]).toISOString(), + dateRangeEnd: moment(range[1]).toISOString(), + }); + } }} /> {FAILED_TESTS_LABEL} - - - {BRUSH_LABEL} - - + {allowBrushing && ( + + + {BRUSH_LABEL} + + + )} ); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/monitor_errors.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/monitor_errors.tsx index 4d2794e9da995..04930af018152 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/monitor_errors.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/monitor_errors.tsx @@ -23,9 +23,9 @@ import { ErrorsTabContent } from './errors_tab_content'; export const MonitorErrors = () => { const { errorStates, loading, data } = useMonitorErrors(); - const initialLoading = loading && !data; + const initialLoading = !data; - const emptyState = !loading && errorStates.length === 0; + const emptyState = !loading && errorStates && errorStates?.length === 0; const redirect = useMonitorDetailsPage(); if (redirect) { @@ -39,7 +39,7 @@ export const MonitorErrors = () => { {initialLoading && } {emptyState && }
      - +
      ); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_details_panel.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_details_panel_container.tsx similarity index 85% rename from x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_details_panel.tsx rename to x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_details_panel_container.tsx index c2a26f5242f0f..99885ac30fb87 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_details_panel.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_details_panel_container.tsx @@ -9,12 +9,15 @@ import React from 'react'; import { EuiLoadingContent } from '@elastic/eui'; import { useParams } from 'react-router-dom'; import { euiStyled } from '@kbn/kibana-react-plugin/common'; -import { MonitorDetailsPanel } from '../../common/components/monitor_details_panel'; +import { + MonitorDetailsPanelProps, + MonitorDetailsPanel, +} from '../../common/components/monitor_details_panel'; import { useSelectedMonitor } from '../hooks/use_selected_monitor'; import { ConfigKey } from '../../../../../../common/runtime_types'; import { useMonitorLatestPing } from '../hooks/use_monitor_latest_ping'; -export const MonitorDetailsPanelContainer = () => { +export const MonitorDetailsPanelContainer = (props: Partial) => { const { latestPing } = useMonitorLatestPing(); const { monitorId: configId } = useParams<{ monitorId: string }>(); @@ -34,6 +37,7 @@ export const MonitorDetailsPanelContainer = () => { monitor={monitor} loading={loading} configId={configId} + {...props} /> ); }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_summary.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_summary.tsx index b087da10f767c..ebaaee6e44e50 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_summary.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_summary.tsx @@ -21,7 +21,7 @@ import { MonitorDurationTrend } from './duration_trend'; import { StepDurationPanel } from './step_duration_panel'; import { AvailabilityPanel } from './availability_panel'; import { DurationPanel } from './duration_panel'; -import { MonitorDetailsPanelContainer } from './monitor_details_panel'; +import { MonitorDetailsPanelContainer } from './monitor_details_panel_container'; import { AvailabilitySparklines } from './availability_sparklines'; import { LastTestRun } from './last_test_run'; import { LAST_10_TEST_RUNS, TestRunsTable } from './test_runs_table'; 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 b34f4f6fe421a..187f590c9b360 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 @@ -325,6 +325,7 @@ export function MonitorDetailFlyout(props: Props) { { setIsOpen(false); @@ -69,9 +69,12 @@ export const AddLocationFlyout = ({ - {!canReadAgentPolicies && } + {!canManagePrivateLocation && } - + 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 index 42aa5e8208ece..b3ad93e26d041 100644 --- 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 @@ -11,7 +11,7 @@ import { i18n } from '@kbn/i18n'; import { useSyntheticsSettingsContext } from '../../../contexts'; import { LEARN_MORE, READ_DOCS } from './empty_locations'; -export const AgentPolicyNeeded = () => { +export const AgentPolicyNeeded = ({ disabled }: { disabled: boolean }) => { const { basePath } = useSyntheticsSettingsContext(); return ( @@ -20,7 +20,12 @@ export const AgentPolicyNeeded = () => { title={

      {AGENT_POLICY_NEEDED}

      } body={

      {ADD_AGENT_POLICY_DESCRIPTION}

      } actions={ - + {CREATE_AGENT_POLICY} } 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 index 6d2c95cac70ae..959520c911469 100644 --- 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 @@ -9,7 +9,7 @@ import React, { useState } from 'react'; import { EuiButtonIcon, EuiConfirmModal, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { useSyntheticsSettingsContext } from '../../../contexts'; -import { useFleetPermissions } from '../../../hooks'; +import { useFleetPermissions, useCanManagePrivateLocation } from '../../../hooks'; import { CANNOT_SAVE_INTEGRATION_LABEL } from '../../common/components/permissions'; export const DeleteLocation = ({ @@ -30,6 +30,7 @@ export const DeleteLocation = ({ const { canSave } = useSyntheticsSettingsContext(); const { canSaveIntegrations } = useFleetPermissions(); + const canManagePrivateLocation = useCanManagePrivateLocation(); const [isModalOpen, setIsModalOpen] = useState(false); @@ -62,7 +63,9 @@ export const DeleteLocation = ({ return ( <> {isModalOpen && deleteModal} - + { setIsModalOpen(true); }} - isDisabled={!canDelete || !canSave} + isDisabled={!canDelete || !canManagePrivateLocation || !canSave} /> 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 index e343e9faf1aec..ce80c7d97e71e 100644 --- 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 @@ -5,12 +5,25 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; -import { defaultCore, WrappedHelper } from '../../../../utils/testing'; - +import { renderHook, act } from '@testing-library/react-hooks'; +import { WrappedHelper } from '../../../../utils/testing'; +import { getServiceLocations } from '../../../../state/service_locations'; +import { setAddingNewPrivateLocation } from '../../../../state/private_locations'; import { useLocationsAPI } from './use_locations_api'; +import * as locationAPI from '../../../../state/private_locations/api'; +import * as reduxHooks from 'react-redux'; describe('useLocationsAPI', () => { + const dispatch = jest.fn(); + const addAPI = jest.spyOn(locationAPI, 'addSyntheticsPrivateLocations').mockResolvedValue({ + locations: [], + }); + const deletedAPI = jest.spyOn(locationAPI, 'deleteSyntheticsPrivateLocations').mockResolvedValue({ + locations: [], + }); + const getAPI = jest.spyOn(locationAPI, 'getSyntheticsPrivateLocations'); + jest.spyOn(reduxHooks, 'useDispatch').mockReturnValue(dispatch); + it('returns expected results', () => { const { result } = renderHook(() => useLocationsAPI(), { wrapper: WrappedHelper, @@ -22,20 +35,15 @@ describe('useLocationsAPI', () => { privateLocations: [], }) ); - expect(defaultCore.savedObjects.client.get).toHaveBeenCalledWith( - 'synthetics-privates-locations', - 'synthetics-privates-locations-singleton' - ); + expect(getAPI).toHaveBeenCalledTimes(1); }); - defaultCore.savedObjects.client.get = jest.fn().mockReturnValue({ - attributes: { - locations: [ - { - id: 'Test', - agentPolicyId: 'testPolicy', - }, - ], - }, + jest.spyOn(locationAPI, 'getSyntheticsPrivateLocations').mockResolvedValue({ + locations: [ + { + id: 'Test', + agentPolicyId: 'testPolicy', + } as any, + ], }); it('returns expected results after data', async () => { const { result, waitForNextUpdate } = renderHook(() => useLocationsAPI(), { @@ -71,77 +79,50 @@ describe('useLocationsAPI', () => { await waitForNextUpdate(); - result.current.onSubmit({ - id: 'new', - agentPolicyId: 'newPolicy', - label: 'new', + act(() => { + result.current.onSubmit({ + id: 'new', + agentPolicyId: 'newPolicy', + label: 'new', + concurrentMonitors: 1, + geo: { + lat: 0, + lon: 0, + }, + }); + }); + + await waitForNextUpdate(); + + expect(addAPI).toHaveBeenCalledWith({ concurrentMonitors: 1, + id: 'newPolicy', geo: { lat: 0, lon: 0, }, + label: 'new', + agentPolicyId: 'newPolicy', }); - - 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 } - ); + expect(dispatch).toBeCalledWith(setAddingNewPrivateLocation(false)); + expect(dispatch).toBeCalledWith(getServiceLocations()); }); 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'); + act(() => { + 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 } - ); + expect(deletedAPI).toHaveBeenLastCalledWith('Test'); + expect(dispatch).toBeCalledWith(setAddingNewPrivateLocation(false)); + expect(dispatch).toBeCalledWith(getServiceLocations()); }); }); 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 index 6b35e79152c87..8678328445a62 100644 --- 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 @@ -7,12 +7,13 @@ 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 { getServiceLocations } from '../../../../state/service_locations'; import { setAddingNewPrivateLocation } from '../../../../state/private_locations'; import { + addSyntheticsPrivateLocations, + deleteSyntheticsPrivateLocations, getSyntheticsPrivateLocations, - setSyntheticsPrivateLocations, } from '../../../../state/private_locations/api'; import { PrivateLocation } from '../../../../../../../common/runtime_types'; @@ -21,31 +22,29 @@ export const useLocationsAPI = () => { 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); + const result = await getSyntheticsPrivateLocations(); + setPrivateLocations(result.locations); 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 }], + if (formData) { + const result = await addSyntheticsPrivateLocations({ + ...formData, + id: formData.agentPolicyId, }); setPrivateLocations(result.locations); setFormData(undefined); setIsAddingNew(false); + dispatch(getServiceLocations()); return result; } - }, [formData, privateLocations]); + }, [formData]); const onSubmit = (data: PrivateLocation) => { setFormData(data); @@ -57,14 +56,13 @@ export const useLocationsAPI = () => { const { loading: deleteLoading } = useFetcher(async () => { if (deleteId) { - const result = await setSyntheticsPrivateLocations(savedObjects?.client!, { - locations: (privateLocations ?? []).filter((loc) => loc.id !== deleteId), - }); + const result = await deleteSyntheticsPrivateLocations(deleteId); setPrivateLocations(result.locations); setDeleteId(undefined); + dispatch(getServiceLocations()); return result; } - }, [deleteId, privateLocations]); + }, [deleteId]); return { formData, 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 index 89fe466d241f1..a001c503697b1 100644 --- 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 @@ -26,13 +26,17 @@ import { selectAgentPolicies } from '../../../state/private_locations'; export const LocationForm = ({ privateLocations, + hasPermissions, }: { onDiscard?: () => void; privateLocations: PrivateLocation[]; + hasPermissions: boolean; }) => { const { data } = useSelector(selectAgentPolicies); - const { control, register } = useFormContext(); + const { control, register, watch } = useFormContext(); const { errors } = useFormState(); + const selectedPolicyId = watch('agentPolicyId'); + const selectedPolicy = data?.items.find((item) => item.id === selectedPolicyId); const tagsList = privateLocations.reduce((acc, item) => { const tags = item.tags || []; @@ -41,7 +45,7 @@ export const LocationForm = ({ return ( <> - {data?.items.length === 0 && } + {data?.items.length === 0 && } + + + {selectedPolicy?.agents === 0 && ( + +

      + { + + + + ), + }} + /> + } +

      +
      + )}
      ); @@ -107,6 +144,13 @@ export const AGENT_CALLOUT_TITLE = i18n.translate( } ); +export const AGENT_MISSING_CALLOUT_TITLE = i18n.translate( + 'xpack.synthetics.monitorManagement.agentMissingCallout.title', + { + defaultMessage: 'Selected agent policy has no agents', + } +); + export const LOCATION_NAME_LABEL = i18n.translate( 'xpack.synthetics.monitorManagement.locationName', { 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 index b6996d4e6149b..1f7ba4a0b37ea 100644 --- 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 @@ -22,7 +22,7 @@ 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 { useFleetPermissions } from '../../../hooks'; +import { useCanManagePrivateLocation } from '../../../hooks'; import { setAddingNewPrivateLocation } from '../../../state/private_locations'; import { PrivateLocationDocsLink, START_ADDING_LOCATIONS_DESCRIPTION } from './empty_locations'; import { PrivateLocation } from '../../../../../../common/runtime_types'; @@ -53,7 +53,7 @@ export const PrivateLocationsTable = ({ const { locationMonitors, loading } = useLocationMonitors(); const { canSave } = useSyntheticsSettingsContext(); - const { canSaveIntegrations } = useFleetPermissions(); + const canManagePrivateLocations = useCanManagePrivateLocation(); const tagsList = privateLocations.reduce((acc, item) => { const tags = item.tags || []; @@ -129,13 +129,14 @@ export const PrivateLocationsTable = ({ const renderToolRight = () => { return [ setIsAddingNew(true)} iconType="plusInCircle" - title={!canSaveIntegrations ? CANNOT_SAVE_INTEGRATION_LABEL : undefined} + title={!canManagePrivateLocations ? CANNOT_SAVE_INTEGRATION_LABEL : undefined} > {ADD_LABEL} , 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 index 9cc313a106c96..92768f48a83ef 100644 --- 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 @@ -20,7 +20,7 @@ export const ManageEmptyState: FC<{ const { data: agentPolicies } = useSelector(selectAgentPolicies); if (agentPolicies?.total === 0) { - return ; + return ; } if (privateLocations.length === 0) { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/manage_private_locations.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/manage_private_locations.test.tsx new file mode 100644 index 0000000000000..b4e406353f2a1 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/manage_private_locations.test.tsx @@ -0,0 +1,169 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { render } from '../../../utils/testing/rtl_helpers'; +import * as permissionsHooks from '../../../hooks'; +import * as locationHooks from './hooks/use_locations_api'; +import * as settingsHooks from '../../../contexts/synthetics_settings_context'; +import type { SyntheticsSettingsContextValues } from '../../../contexts'; +import { ManagePrivateLocations } from './manage_private_locations'; +import { PrivateLocation } from '../../../../../../common/runtime_types'; + +jest.mock('../../../hooks'); +jest.mock('./hooks/use_locations_api'); +jest.mock('../../../contexts/synthetics_settings_context'); + +describe('', () => { + beforeEach(() => { + jest.spyOn(permissionsHooks, 'useCanManagePrivateLocation').mockReturnValue(true); + jest.spyOn(locationHooks, 'useLocationsAPI').mockReturnValue({ + formData: {} as PrivateLocation, + loading: false, + onSubmit: jest.fn(), + privateLocations: [], + onDelete: jest.fn(), + deleteLoading: false, + }); + jest.spyOn(settingsHooks, 'useSyntheticsSettingsContext').mockReturnValue({ + canSave: true, + } as SyntheticsSettingsContextValues); + }); + + it.each([true, false])( + 'handles no agent found when the user does and does not have permissions', + (hasFleetPermissions) => { + jest + .spyOn(permissionsHooks, 'useCanManagePrivateLocation') + .mockReturnValue(hasFleetPermissions); + const { getByText, getByRole, queryByText } = render(, { + state: { + agentPolicies: { + data: { + items: [], + total: 0, + page: 1, + perPage: 20, + }, + loading: false, + error: null, + isManageFlyoutOpen: false, + isAddingNewPrivateLocation: false, + }, + }, + }); + expect(getByText('No agent policies found')).toBeInTheDocument(); + + if (hasFleetPermissions) { + const button = getByRole('link', { name: 'Create agent policy' }); + expect(button).not.toBeDisabled(); + expect( + queryByText(/You are not authorized to manage private locations./) + ).not.toBeInTheDocument(); + } else { + const button = getByRole('button', { name: 'Create agent policy' }); + expect(button).toBeDisabled(); + expect(getByText(/You are not authorized to manage private locations./)); + } + } + ); + + it.each([true, false])( + 'handles create first location when the user does and does not have permissions', + (hasFleetPermissions) => { + jest + .spyOn(permissionsHooks, 'useCanManagePrivateLocation') + .mockReturnValue(hasFleetPermissions); + const { getByText, getByRole, queryByText } = render(, { + state: { + agentPolicies: { + data: { + items: [{}], + total: 1, + page: 1, + perPage: 20, + }, + loading: false, + error: null, + isManageFlyoutOpen: false, + isAddingNewPrivateLocation: false, + }, + }, + }); + expect(getByText('Create your first private location')).toBeInTheDocument(); + const button = getByRole('button', { name: 'Create location' }); + + if (hasFleetPermissions) { + expect(button).not.toBeDisabled(); + expect( + queryByText(/You are not authorized to manage private locations./) + ).not.toBeInTheDocument(); + } else { + expect(button).toBeDisabled(); + expect(getByText(/You are not authorized to manage private locations./)); + } + } + ); + + it.each([true, false])( + 'handles location table when the user does and does not have permissions', + (hasFleetPermissions) => { + const privateLocationName = 'Test private location'; + jest + .spyOn(permissionsHooks, 'useCanManagePrivateLocation') + .mockReturnValue(hasFleetPermissions); + jest.spyOn(permissionsHooks, 'useFleetPermissions').mockReturnValue({ + canSaveIntegrations: hasFleetPermissions, + canReadAgentPolicies: hasFleetPermissions, + }); + jest.spyOn(locationHooks, 'useLocationsAPI').mockReturnValue({ + formData: {} as PrivateLocation, + loading: false, + onSubmit: jest.fn(), + privateLocations: [ + { + label: privateLocationName, + id: 'lkjlere', + agentPolicyId: 'lkjelrje', + isServiceManaged: false, + concurrentMonitors: 2, + }, + ], + onDelete: jest.fn(), + deleteLoading: false, + }); + const { getByText, getByRole, queryByText } = render(, { + state: { + agentPolicies: { + data: { + items: [{}], + total: 1, + page: 1, + perPage: 20, + }, + loading: false, + error: null, + isManageFlyoutOpen: false, + isAddingNewPrivateLocation: false, + }, + }, + }); + expect(getByText(privateLocationName)).toBeInTheDocument(); + const button = getByRole('button', { name: 'Create location' }); + + if (hasFleetPermissions) { + expect(button).not.toBeDisabled(); + expect( + queryByText(/You are not authorized to manage private locations./) + ).not.toBeInTheDocument(); + } else { + expect(button).toBeDisabled(); + expect(getByText(/You are not authorized to manage private locations./)); + } + } + ); +}); 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 index d697d011e5841..e8246aa13221e 100644 --- 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 @@ -6,9 +6,10 @@ */ import React, { useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; +import { EuiSpacer } from '@elastic/eui'; import { LoadingState } from '../../monitors_page/overview/overview/monitor_detail_flyout'; import { PrivateLocationsTable } from './locations_table'; -import { useFleetPermissions } from '../../../hooks'; +import { useCanManagePrivateLocation } from '../../../hooks'; import { ManageEmptyState } from './manage_empty_state'; import { AddLocationFlyout } from './add_location_flyout'; import { useLocationsAPI } from './hooks/use_locations_api'; @@ -25,12 +26,11 @@ 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 { canReadAgentPolicies } = useFleetPermissions(); + const canManagePrivateLocation = useCanManagePrivateLocation(); useEffect(() => { dispatch(getAgentPoliciesAction.get()); @@ -43,7 +43,12 @@ export const ManagePrivateLocations = () => { return ( <> - {!canReadAgentPolicies && } + {!canManagePrivateLocation && ( + <> + + + + )} {loading ? ( @@ -51,7 +56,7 @@ export const ManagePrivateLocations = () => { { return ( -

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

      + {canReadAgentPolicies ? ( + + {policy ? ( + + {policy?.name} + + ) : ( + + {POLICY_IS_DELETED} + + )} + + ) : ( + agentPolicyId + )} +     + + {AGENTS_LABEL} + {policy?.agents} +
      ); }; @@ -50,3 +55,7 @@ export const PolicyName = ({ agentPolicyId }: { agentPolicyId: string }) => { const POLICY_IS_DELETED = i18n.translate('xpack.synthetics.monitorManagement.deletedPolicy', { defaultMessage: 'Policy is deleted', }); + +const AGENTS_LABEL = i18n.translate('xpack.synthetics.monitorManagement.agents', { + defaultMessage: 'Agents: ', +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/route_config.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/route_config.ts index ee9b6c34bd8f9..51ddf14ee5f67 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/route_config.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/route_config.ts @@ -17,32 +17,27 @@ export const getSettingsRouteConfig = ( syntheticsPath: string, baseTitle: string ) => { + const sharedProps = { + title: i18n.translate('xpack.synthetics.settingsRoute.title', { + defaultMessage: 'Settings | {baseTitle}', + values: { baseTitle }, + }), + component: SettingsPage, + pageHeader: getSettingsPageHeader(history, syntheticsPath), + dataTestSubj: 'syntheticsSettingsPage', + pageSectionProps: { + paddingSize: 'm', + }, + }; + return [ { - title: i18n.translate('xpack.synthetics.settingsRoute.title', { - defaultMessage: 'Settings | {baseTitle}', - values: { baseTitle }, - }), + ...sharedProps, path: SETTINGS_ROUTE, - component: SettingsPage, - dataTestSubj: 'syntheticsSettingsPage', - pageSectionProps: { - paddingSize: 'm', - }, - pageHeader: getSettingsPageHeader(history, syntheticsPath), }, { - title: i18n.translate('xpack.synthetics.settingsRoute.title', { - defaultMessage: 'Settings | {baseTitle}', - values: { baseTitle }, - }), + ...sharedProps, path: SYNTHETICS_SETTINGS_ROUTE, - component: SettingsPage, - dataTestSubj: 'syntheticsSettingsPage', - pageSectionProps: { - paddingSize: 'm', - }, - pageHeader: getSettingsPageHeader(history, syntheticsPath), }, ] as RouteProps[]; }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/test_run_details/test_run_details.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/test_run_details/test_run_details.tsx index e05211ce69830..a063c5bbabe98 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/test_run_details/test_run_details.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/test_run_details/test_run_details.tsx @@ -11,7 +11,7 @@ import moment from 'moment'; import { FormattedMessage } from '@kbn/i18n-react'; import { useParams } from 'react-router-dom'; import { TestRunErrorInfo } from './components/test_run_error_info'; -import { MonitorDetailsPanelContainer } from '../monitor_details/monitor_summary/monitor_details_panel'; +import { MonitorDetailsPanelContainer } from '../monitor_details/monitor_summary/monitor_details_panel_container'; import { useSelectedLocation } from '../monitor_details/hooks/use_selected_location'; import { MonitorDetailsLinkPortal } from '../monitor_add_edit/monitor_details_portal'; import { StepNumberNav } from './components/step_number_nav'; @@ -89,7 +89,7 @@ export const TestRunDetails = () => { - +
      )} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_fleet_permissions.ts b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_fleet_permissions.ts index bbd5aa4f681bf..2ed8af08891ab 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_fleet_permissions.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_fleet_permissions.ts @@ -31,6 +31,12 @@ export function useCanUpdatePrivateMonitor(monitor: EncryptedSyntheticsMonitor) return canUpdatePrivateMonitor(monitor, canSaveIntegrations); } +export function useCanManagePrivateLocation() { + const { canSaveIntegrations, canReadAgentPolicies } = useFleetPermissions(); + + return Boolean(canSaveIntegrations && canReadAgentPolicies); +} + export function canUpdatePrivateMonitor( monitor: EncryptedSyntheticsMonitor, canSaveIntegrations: boolean 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 index 3c48696c685b9..50683fdd87a35 100644 --- 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 @@ -5,14 +5,10 @@ * 2.0. */ -import { SavedObjectsClientContract } from '@kbn/core/public'; -import { SyntheticsPrivateLocations } from '../../../../../common/runtime_types'; +import { SYNTHETICS_API_URLS } from '../../../../../common/constants'; +import { PrivateLocation, 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', @@ -33,26 +29,18 @@ export const fetchAgentPolicies = async (): Promise => { ); }; -export const setSyntheticsPrivateLocations = async ( - client: SavedObjectsClientContract, - privateLocations: SyntheticsPrivateLocations -) => { - const result = await client.create(privateLocationsSavedObjectName, privateLocations, { - id: privateLocationsSavedObjectId, - overwrite: true, - }); +export const addSyntheticsPrivateLocations = async ( + newLocation: PrivateLocation +): Promise => { + return await apiService.post(SYNTHETICS_API_URLS.PRIVATE_LOCATIONS, newLocation); +}; - return result.attributes; +export const getSyntheticsPrivateLocations = async (): Promise => { + return await apiService.get(SYNTHETICS_API_URLS.PRIVATE_LOCATIONS); }; -export const getSyntheticsPrivateLocations = async (client: SavedObjectsClientContract) => { - try { - const obj = await client.get( - privateLocationsSavedObjectName, - privateLocationsSavedObjectId - ); - return obj?.attributes.locations ?? []; - } catch (getErr) { - return []; - } +export const deleteSyntheticsPrivateLocations = async ( + locationId: string +): Promise => { + return await apiService.delete(SYNTHETICS_API_URLS.PRIVATE_LOCATIONS + `/${locationId}`); }; 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 ced9c212e83cf..371e054476c79 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 @@ -45,7 +45,6 @@ export const rootEffect = function* root(): Generator { fork(fetchAgentPoliciesEffect), fork(fetchDynamicSettingsEffect), fork(setDynamicSettingsEffect), - fork(fetchAgentPoliciesEffect), fork(fetchAlertConnectorsEffect), fork(syncGlobalParamsEffect), fork(enableDefaultAlertingEffect), diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/test_time_formats.test.ts b/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/test_time_formats.test.ts new file mode 100644 index 0000000000000..b1e808d748d76 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/test_time_formats.test.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { formatTestDuration } from './test_time_formats'; + +describe('formatTestDuration', () => { + it.each` + duration | expected | isMilli + ${undefined} | ${'0 ms'} | ${undefined} + ${120_000_000} | ${'2 mins'} | ${undefined} + ${6_200_000} | ${'6.2 sec'} | ${false} + ${500_000} | ${'500 ms'} | ${undefined} + ${100} | ${'0 ms'} | ${undefined} + ${undefined} | ${'0 ms'} | ${true} + ${600_000} | ${'10 mins'} | ${true} + ${6_200} | ${'6.2 sec'} | ${true} + ${500} | ${'500 ms'} | ${true} + `( + 'returns $expected when `duration` is $duration and `isMilli` $isMilli', + ({ + duration, + expected, + isMilli, + }: { + duration?: number; + expected: string; + isMilli?: boolean; + }) => { + expect(formatTestDuration(duration, isMilli)).toBe(expected); + } + ); +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/test_time_formats.ts b/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/test_time_formats.ts index fcf07f1cf8714..efae8b1652738 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/test_time_formats.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/utils/monitor_test_result/test_time_formats.ts @@ -6,6 +6,7 @@ */ import moment from 'moment'; +import { i18n } from '@kbn/i18n'; import { useKibanaDateFormat } from '../../../../hooks/use_kibana_date_format'; /** @@ -16,19 +17,40 @@ import { useKibanaDateFormat } from '../../../../hooks/use_kibana_date_format'; export const formatTestDuration = (duration = 0, isMilli = false) => { const secs = isMilli ? duration / 1e3 : duration / 1e6; + const hours = Math.floor(secs / 3600); + + if (hours >= 1) { + return i18n.translate('xpack.synthetics.errorDetails.errorDuration.hours', { + defaultMessage: '{value} hours', + values: { value: hours }, + }); + } + if (secs >= 60) { - return `${(secs / 60).toFixed(1)} min`; + return i18n.translate('xpack.synthetics.errorDetails.errorDuration.minutes', { + defaultMessage: '{value} mins', + values: { value: parseFloat((secs / 60).toFixed(1)) }, + }); } if (secs >= 1) { - return `${secs.toFixed(1)} s`; + return i18n.translate('xpack.synthetics.errorDetails.errorDuration.seconds', { + defaultMessage: '{value} sec', + values: { value: parseFloat(secs.toFixed(1)) }, + }); } if (isMilli) { - return `${duration.toFixed(0)} ms`; + return i18n.translate('xpack.synthetics.errorDetails.errorDuration.milliseconds', { + defaultMessage: '{value} ms', + values: { value: duration.toFixed(0) }, + }); } - return `${(duration / 1000).toFixed(0)} ms`; + return i18n.translate('xpack.synthetics.errorDetails.errorDuration.microseconds', { + defaultMessage: '{value} ms', + values: { value: (duration / 1000).toFixed(0) }, + }); }; export function formatTestRunAt(timestamp: string, format: string) { diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/synthetics_policy_edit_extension_wrapper.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/synthetics_policy_edit_extension_wrapper.tsx index 8a2671c14cc6c..00d5e8bd7b940 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/synthetics_policy_edit_extension_wrapper.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/synthetics_policy_edit_extension_wrapper.tsx @@ -14,8 +14,8 @@ import type { } from '@kbn/fleet-plugin/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { useEditMonitorLocator } from '../../../apps/synthetics/hooks'; -import { PolicyConfig, MonitorFields } from './types'; -import { ConfigKey, DataStream, TLSFields } from './types'; +import type { PolicyConfig, MonitorFields, TLSFields } from './types'; +import { ConfigKey, DataStream } from './types'; import { SyntheticsPolicyEditExtension } from './synthetics_policy_edit_extension'; import { PolicyConfigContextProvider, @@ -80,10 +80,10 @@ export const SyntheticsPolicyEditExtensionWrapper = memo = { diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/edit_monitor_config.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/edit_monitor_config.tsx index 0c487eb1fdc3d..e80a305afa6fa 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/edit_monitor_config.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/edit_monitor_config.tsx @@ -52,8 +52,8 @@ export const EditMonitorConfig = ({ monitor, throttling }: Props) => { [ConfigKey.TLS_VERSION]: monitor[ConfigKey.TLS_VERSION], }; - enableTLS = Boolean(monitor[ConfigKey.METADATA].is_tls_enabled); - enableZipUrlTLS = Boolean(monitor[ConfigKey.METADATA].is_zip_url_tls_enabled); + enableTLS = Boolean(monitor[ConfigKey.METADATA]?.is_tls_enabled); + enableZipUrlTLS = Boolean(monitor[ConfigKey.METADATA]?.is_zip_url_tls_enabled); const formattedDefaultConfig: Partial = { [type]: monitor, diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/manage_locations_flyout.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/manage_locations_flyout.tsx index 103a9a37480db..805097312d4e8 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/manage_locations_flyout.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/manage_locations_flyout.tsx @@ -164,7 +164,7 @@ export const NEED_PERMISSIONS = i18n.translate( ); export const NEED_FLEET_READ_AGENT_POLICIES_PERMISSION = i18n.translate( - 'xpack.synthetics.monitorManagement.needFleetReadAgentPoliciesPermission', + 'xpack.synthetics.monitorManagement.needFleetReadAgentPoliciesPermissionUptime', { 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/legacy_uptime/components/monitor_management/monitor_config/locations.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/locations.tsx index 11e2588e909b7..3bcaa51c48f77 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/locations.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/locations.tsx @@ -146,7 +146,7 @@ export const INVALID_LABEL = i18n.translate('xpack.synthetics.monitorManagement. }); export const CANNOT_SAVE_INTEGRATION_LABEL = i18n.translate( - 'xpack.synthetics.monitorManagement.cannotSaveIntegration', + 'xpack.synthetics.monitorManagement.cannotSaveIntegrationUptime', { defaultMessage: 'You are not authorized to update integrations. Integrations write permissions are required.', diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/state/api/alerts.ts b/x-pack/plugins/synthetics/public/legacy_uptime/state/api/alerts.ts index e0f83c917aa8a..33cbac6468306 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/state/api/alerts.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/state/api/alerts.ts @@ -87,6 +87,7 @@ export const createAlert = async ({ defaultRecoveryMessage: MonitorStatusTranslations.defaultRecoveryMessage, defaultSubjectMessage: MonitorStatusTranslations.defaultSubjectMessage, }, + isLegacy: true, }); const data: NewMonitorStatusAlert = { diff --git a/x-pack/plugins/synthetics/server/alert_rules/common.test.ts b/x-pack/plugins/synthetics/server/alert_rules/common.test.ts index 34caffe5fcdd9..6280ebb3fdd5b 100644 --- a/x-pack/plugins/synthetics/server/alert_rules/common.test.ts +++ b/x-pack/plugins/synthetics/server/alert_rules/common.test.ts @@ -4,9 +4,11 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - -import { updateState } from './common'; +import { alertsMock } from '@kbn/alerting-plugin/server/mocks'; +import { IBasePath } from '@kbn/core/server'; +import { updateState, setRecoveredAlertsContext } from './common'; import { SyntheticsCommonState } from '../../common/runtime_types/alert_rules/common'; +import { StaleDownConfig } from './status_rule/status_rule_executor'; describe('updateState', () => { let spy: jest.SpyInstance; @@ -180,3 +182,153 @@ describe('updateState', () => { `); }); }); + +describe('setRecoveredAlertsContext', () => { + const { alertFactory } = alertsMock.createRuleExecutorServices(); + const { getRecoveredAlerts } = alertFactory.done(); + const alertUuid = 'alert-id'; + const location = 'US Central'; + const configId = '12345'; + const idWithLocation = `${configId}-${location}`; + const basePath = { + publicBaseUrl: 'https://localhost:5601', + } as IBasePath; + const getAlertUuid = () => alertUuid; + + const upConfigs = { + [idWithLocation]: { + configId, + monitorQueryId: 'stale-config', + status: 'up', + location: '', + ping: { + '@timestamp': new Date().toISOString(), + } as StaleDownConfig['ping'], + timestamp: new Date().toISOString(), + }, + }; + + it('sets context correctly when monitor is deleted', () => { + const setContext = jest.fn(); + getRecoveredAlerts.mockReturnValue([ + { + getId: () => alertUuid, + getState: () => ({ + idWithLocation, + monitorName: 'test-monitor', + }), + setContext, + }, + ]); + const staleDownConfigs = { + [idWithLocation]: { + configId, + monitorQueryId: 'stale-config', + status: 'down', + location: 'location', + ping: { + '@timestamp': new Date().toISOString(), + } as StaleDownConfig['ping'], + timestamp: new Date().toISOString(), + isDeleted: true, + }, + }; + setRecoveredAlertsContext({ + alertFactory, + basePath, + getAlertUuid, + spaceId: 'default', + staleDownConfigs, + upConfigs: {}, + }); + expect(setContext).toBeCalledWith({ + idWithLocation, + alertDetailsUrl: 'https://localhost:5601/app/observability/alerts/alert-id', + monitorName: 'test-monitor', + recoveryReason: 'Monitor has been deleted', + }); + }); + + it('sets context correctly when location is removed', () => { + const setContext = jest.fn(); + getRecoveredAlerts.mockReturnValue([ + { + getId: () => alertUuid, + getState: () => ({ + idWithLocation, + monitorName: 'test-monitor', + }), + setContext, + }, + ]); + const staleDownConfigs = { + [idWithLocation]: { + configId, + monitorQueryId: 'stale-config', + status: 'down', + location: 'location', + ping: { + '@timestamp': new Date().toISOString(), + } as StaleDownConfig['ping'], + timestamp: new Date().toISOString(), + isLocationRemoved: true, + }, + }; + setRecoveredAlertsContext({ + alertFactory, + basePath, + getAlertUuid, + spaceId: 'default', + staleDownConfigs, + upConfigs: {}, + }); + expect(setContext).toBeCalledWith({ + idWithLocation, + alertDetailsUrl: 'https://localhost:5601/app/observability/alerts/alert-id', + monitorName: 'test-monitor', + recoveryReason: 'Location has been removed from the monitor', + }); + }); + + it('sets context correctly when monitor is up', () => { + const setContext = jest.fn(); + getRecoveredAlerts.mockReturnValue([ + { + getId: () => alertUuid, + getState: () => ({ + idWithLocation, + monitorName: 'test-monitor', + }), + setContext, + }, + ]); + const staleDownConfigs = { + [idWithLocation]: { + configId, + monitorQueryId: 'stale-config', + status: 'down', + location: 'location', + ping: { + '@timestamp': new Date().toISOString(), + } as StaleDownConfig['ping'], + timestamp: new Date().toISOString(), + isLocationRemoved: true, + }, + }; + setRecoveredAlertsContext({ + alertFactory, + basePath, + getAlertUuid, + spaceId: 'default', + staleDownConfigs, + upConfigs, + }); + expect(setContext).toBeCalledWith({ + idWithLocation, + alertDetailsUrl: 'https://localhost:5601/app/observability/alerts/alert-id', + monitorName: 'test-monitor', + status: 'up', + recoveryReason: 'Monitor has recovered with status Up', + }); + }); +}); diff --git a/x-pack/plugins/synthetics/server/alert_rules/common.ts b/x-pack/plugins/synthetics/server/alert_rules/common.ts index 2b629e865a115..e12e85bf82ee2 100644 --- a/x-pack/plugins/synthetics/server/alert_rules/common.ts +++ b/x-pack/plugins/synthetics/server/alert_rules/common.ts @@ -80,12 +80,14 @@ export const setRecoveredAlertsContext = ({ getAlertUuid, spaceId, staleDownConfigs, + upConfigs, }: { alertFactory: RuleExecutorServices['alertFactory']; basePath?: IBasePath; getAlertUuid?: (alertId: string) => string | null; spaceId?: string; staleDownConfigs: AlertOverviewStatus['staleDownConfigs']; + upConfigs: AlertOverviewStatus['upConfigs']; }) => { const { getRecoveredAlerts } = alertFactory.done(); for (const alert of getRecoveredAlerts()) { @@ -95,6 +97,7 @@ export const setRecoveredAlertsContext = ({ const state = alert.getState() as SyntheticsCommonState; let recoveryReason = ''; + let isUp = false; if (state?.idWithLocation && staleDownConfigs[state.idWithLocation]) { const { idWithLocation } = state; @@ -110,8 +113,16 @@ export const setRecoveredAlertsContext = ({ } } + if (state?.idWithLocation && upConfigs[state.idWithLocation]) { + isUp = Boolean(upConfigs[state.idWithLocation]) || false; + recoveryReason = i18n.translate('xpack.synthetics.alerts.monitorStatus.upCheck', { + defaultMessage: `Monitor has recovered with status Up`, + }); + } + alert.setContext({ ...state, + ...(isUp ? { status: 'up' } : {}), ...(recoveryReason ? { [RECOVERY_REASON]: recoveryReason } : {}), ...(basePath && spaceId && alertUuid ? { [ALERT_DETAILS_URL]: getAlertDetailsUrl(basePath, spaceId, alertUuid) } diff --git a/x-pack/plugins/synthetics/server/alert_rules/status_rule/monitor_status_rule.ts b/x-pack/plugins/synthetics/server/alert_rules/status_rule/monitor_status_rule.ts index 7d309c1f595aa..e1719c1e84b93 100644 --- a/x-pack/plugins/synthetics/server/alert_rules/status_rule/monitor_status_rule.ts +++ b/x-pack/plugins/synthetics/server/alert_rules/status_rule/monitor_status_rule.ts @@ -84,7 +84,7 @@ export const registerSyntheticsStatusCheckRule = ( syntheticsMonitorClient ); - const { downConfigs, staleDownConfigs } = await statusRule.getDownChecks( + const { downConfigs, staleDownConfigs, upConfigs } = await statusRule.getDownChecks( ruleState.meta?.downConfigs as OverviewStatus['downConfigs'] ); @@ -129,6 +129,7 @@ export const registerSyntheticsStatusCheckRule = ( getAlertUuid, spaceId, staleDownConfigs, + upConfigs, }); return { diff --git a/x-pack/plugins/synthetics/server/feature.ts b/x-pack/plugins/synthetics/server/feature.ts index 4026bbdb19fb9..908d6e6f1f9fa 100644 --- a/x-pack/plugins/synthetics/server/feature.ts +++ b/x-pack/plugins/synthetics/server/feature.ts @@ -57,7 +57,7 @@ export const uptimeFeature = { read: { app: ['uptime', 'kibana', 'synthetics'], catalogue: ['uptime'], - api: ['uptime-read', 'lists-read'], + api: ['uptime-read', 'lists-read', 'rac'], savedObject: { all: [], read: [ diff --git a/x-pack/plugins/synthetics/server/plugin.ts b/x-pack/plugins/synthetics/server/plugin.ts index 598fdd18b229b..c6120c70c3818 100644 --- a/x-pack/plugins/synthetics/server/plugin.ts +++ b/x-pack/plugins/synthetics/server/plugin.ts @@ -13,7 +13,7 @@ import { SavedObjectsClient, SavedObjectsClientContract, } from '@kbn/core/server'; -import { mappingFromFieldMap } from '@kbn/rule-registry-plugin/common/mapping_from_field_map'; +import { mappingFromFieldMap } from '@kbn/alerting-plugin/common'; import { experimentalRuleFieldMap } from '@kbn/rule-registry-plugin/common/assets/field_maps/experimental_rule_field_map'; import { Dataset } from '@kbn/rule-registry-plugin/server'; import { SyntheticsMonitorClient } from './synthetics_service/synthetics_monitor/synthetics_monitor_client'; diff --git a/x-pack/plugins/synthetics/server/queries/query_monitor_status.ts b/x-pack/plugins/synthetics/server/queries/query_monitor_status.ts index 058d28d046c8a..a737e80e08069 100644 --- a/x-pack/plugins/synthetics/server/queries/query_monitor_status.ts +++ b/x-pack/plugins/synthetics/server/queries/query_monitor_status.ts @@ -53,7 +53,6 @@ export async function queryMonitorStatus( const pageCount = Math.ceil(monitorQueryIds.length / idSize); let up = 0; let down = 0; - let pending = 0; const upConfigs: Record = {}; const downConfigs: Record = {}; const monitorsWithoutData = new Map(Object.entries(cloneDeep(monitorLocationsMap))); @@ -135,8 +134,6 @@ export async function queryMonitorStatus( 'getCurrentStatusOverview' + i ); - pending += idsToQuery.length - (result.aggregations?.id.buckets.length ?? 0); - result.aggregations?.id.buckets.forEach(({ location, key: queryId }) => { const locationSummaries = location.buckets.map(({ status, key: locationName }) => { const ping = status.hits.hits[0]._source; @@ -188,8 +185,6 @@ export async function queryMonitorStatus( if (!monitorsWithoutData.get(monitorQueryId)?.length) { monitorsWithoutData.delete(monitorQueryId); } - } else { - pending += 1; } }); }); @@ -212,7 +207,7 @@ export async function queryMonitorStatus( return { up, down, - pending, + pending: Object.values(pendingConfigs).length, upConfigs, downConfigs, pendingConfigs, diff --git a/x-pack/plugins/synthetics/server/routes/index.ts b/x-pack/plugins/synthetics/server/routes/index.ts index 6978a38b2be46..b394c53f20142 100644 --- a/x-pack/plugins/synthetics/server/routes/index.ts +++ b/x-pack/plugins/synthetics/server/routes/index.ts @@ -44,6 +44,9 @@ import { getHasIntegrationMonitorsRoute } from './fleet/get_has_integration_moni import { addSyntheticsParamsRoute } from './settings/add_param'; import { enableDefaultAlertingRoute } from './default_alerts/enable_default_alert'; import { getDefaultAlertingRoute } from './default_alerts/get_default_alert'; +import { addPrivateLocationRoute } from './settings/private_locations/add_private_location'; +import { deletePrivateLocationRoute } from './settings/private_locations/delete_private_location'; +import { getPrivateLocationsRoute } from './settings/private_locations/get_private_locations'; export const syntheticsAppRestApiRoutes: SyntheticsRestApiRouteFactory[] = [ addSyntheticsMonitorRoute, @@ -77,6 +80,9 @@ export const syntheticsAppRestApiRoutes: SyntheticsRestApiRouteFactory[] = [ getDefaultAlertingRoute, updateDefaultAlertingRoute, createJourneyRoute, + addPrivateLocationRoute, + deletePrivateLocationRoute, + getPrivateLocationsRoute, ]; export const syntheticsAppStreamingApiRoutes: SyntheticsStreamingRouteFactory[] = [ diff --git a/x-pack/plugins/synthetics/server/routes/overview_status/overview_status.test.ts b/x-pack/plugins/synthetics/server/routes/overview_status/overview_status.test.ts index 4679d555f6504..635b964c922ab 100644 --- a/x-pack/plugins/synthetics/server/routes/overview_status/overview_status.test.ts +++ b/x-pack/plugins/synthetics/server/routes/overview_status/overview_status.test.ts @@ -4,11 +4,21 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import { SavedObjectsFindResult } from '@kbn/core-saved-objects-api-server'; +import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; import { getUptimeESMockClient } from '../../legacy_uptime/lib/requests/test_helpers'; import { periodToMs } from './overview_status'; import { queryMonitorStatus } from '../../queries/query_monitor_status'; +import { RouteContext } from '../../legacy_uptime/routes'; +import { getStatus } from './overview_status'; import times from 'lodash/times'; +import * as monitorsFns from '../../saved_objects/synthetics_monitor/get_all_monitors'; +import { EncryptedSyntheticsMonitor } from '../../../common/runtime_types'; + +jest.mock('../../saved_objects/synthetics_monitor/get_all_monitors', () => ({ + ...jest.requireActual('../../saved_objects/synthetics_monitor/get_all_monitors'), + getAllMonitors: jest.fn(), +})); jest.mock('../common', () => ({ getMonitors: jest.fn().mockReturnValue({ @@ -34,6 +44,7 @@ jest.mock('../common', () => ({ }, ], }), + getMonitorFilters: () => '', })); jest.mock('../../legacy_uptime/lib/requests/get_snapshot_counts', () => ({ @@ -61,7 +72,7 @@ describe('current status route', () => { }); }); - describe('getStats', () => { + describe('queryMonitorStatus', () => { it('parses expected agg fields', async () => { const { esClient, uptimeEsClient } = getUptimeESMockClient(); esClient.search.mockResponseOnce( @@ -514,7 +525,7 @@ describe('current status route', () => { } ) ).toEqual({ - pending: 2, + pending: 4, down: 1, enabledMonitorQueryIds: ['id1', 'id2', 'project-monitor-id', 'id4'], up: 2, @@ -575,6 +586,239 @@ describe('current status route', () => { }); }); }); + + describe('getStatus', () => { + it.each([ + [['US Central QA'], 1], + [['North America - US Central'], 1], + [['North America - US Central', 'US Central QA'], 2], + [undefined, 2], + ])('handles disabled count when using location filters', async (locations, disabledCount) => { + jest.spyOn(monitorsFns, 'getAllMonitors').mockResolvedValue([ + { + type: 'synthetics-monitor', + id: 'a9a94f2f-47ba-4fe2-afaa-e5cd29b281f1', + attributes: { + enabled: false, + schedule: { + number: '3', + unit: 'm', + }, + config_id: 'a9a94f2f-47ba-4fe2-afaa-e5cd29b281f1', + locations: [ + { + color: 'default', + isServiceManaged: true, + label: 'US Central QA', + id: 'us_central_qa', + }, + { + isServiceManaged: true, + label: 'North America - US Central', + id: 'us_central', + }, + ], + origin: 'project', + id: 'a-test2-default', + }, + references: [], + migrationVersion: { + 'synthetics-monitor': '8.6.0', + }, + coreMigrationVersion: '8.0.0', + updated_at: '2023-02-28T14:31:37.641Z', + created_at: '2023-02-28T14:31:37.641Z', + version: 'Wzg0MzkzLDVd', + namespaces: ['default'], + score: null, + sort: ['a', 3013], + } as unknown as SavedObjectsFindResult, + ]); + const { esClient, uptimeEsClient } = getUptimeESMockClient(); + esClient.search.mockResponseOnce( + getEsResponse([ + { + key: 'id1', + location: { + buckets: [ + { + key: 'Asia/Pacific - Japan', + status: { + hits: { + hits: [ + { + _source: { + '@timestamp': '2022-09-15T16:08:16.724Z', + monitor: { + status: 'up', + id: 'id1', + }, + summary: { + up: 1, + down: 0, + }, + config_id: 'id1', + observer: { + geo: { + name: 'Asia/Pacific - Japan', + }, + }, + }, + }, + ], + }, + }, + }, + ], + }, + }, + { + key: 'id2', + location: { + buckets: [ + { + key: 'Asia/Pacific - Japan', + status: { + hits: { + hits: [ + { + _source: { + '@timestamp': '2022-09-15T16:09:16.724Z', + monitor: { + status: 'up', + id: 'id2', + }, + summary: { + up: 1, + down: 0, + }, + config_id: 'id2', + observer: { + geo: { + name: 'Asia/Pacific - Japan', + }, + }, + }, + }, + ], + }, + }, + }, + { + key: 'Europe - Germany', + status: { + hits: { + hits: [ + { + _source: { + '@timestamp': '2022-09-15T16:19:16.724Z', + monitor: { + status: 'down', + id: 'id2', + }, + summary: { + down: 1, + up: 0, + }, + config_id: 'id2', + observer: { + geo: { + name: 'Europe - Germany', + }, + }, + }, + }, + ], + }, + }, + }, + ], + }, + }, + ]) + ); + expect( + await getStatus( + { + uptimeEsClient, + savedObjectsClient: savedObjectsClientMock.create(), + } as unknown as RouteContext, + { + locations, + } + ) + ).toEqual( + expect.objectContaining({ + disabledCount, + }) + ); + }); + + it.each([ + [['US Central QA'], 1], + [['North America - US Central'], 1], + [['North America - US Central', 'US Central QA'], 2], + [undefined, 2], + ])('handles pending count when using location filters', async (locations, pending) => { + jest.spyOn(monitorsFns, 'getAllMonitors').mockResolvedValue([ + { + type: 'synthetics-monitor', + id: 'a9a94f2f-47ba-4fe2-afaa-e5cd29b281f1', + attributes: { + enabled: true, + schedule: { + number: '3', + unit: 'm', + }, + config_id: 'a9a94f2f-47ba-4fe2-afaa-e5cd29b281f1', + locations: [ + { + color: 'default', + isServiceManaged: true, + label: 'US Central QA', + id: 'us_central_qa', + }, + { + isServiceManaged: true, + label: 'North America - US Central', + id: 'us_central', + }, + ], + origin: 'project', + id: 'a-test2-default', + }, + references: [], + migrationVersion: { + 'synthetics-monitor': '8.6.0', + }, + coreMigrationVersion: '8.0.0', + updated_at: '2023-02-28T14:31:37.641Z', + created_at: '2023-02-28T14:31:37.641Z', + version: 'Wzg0MzkzLDVd', + namespaces: ['default'], + score: null, + sort: ['a', 3013], + } as unknown as SavedObjectsFindResult, + ]); + const { esClient, uptimeEsClient } = getUptimeESMockClient(); + esClient.search.mockResponseOnce(getEsResponse([])); + expect( + await getStatus( + { + uptimeEsClient, + savedObjectsClient: savedObjectsClientMock.create(), + } as unknown as RouteContext, + { + locations, + } + ) + ).toEqual( + expect.objectContaining({ + pending, + }) + ); + }); + }); }); function getEsResponse(buckets: any[]) { diff --git a/x-pack/plugins/synthetics/server/routes/overview_status/overview_status.ts b/x-pack/plugins/synthetics/server/routes/overview_status/overview_status.ts index 750727d85f7ce..11ae9ff793e21 100644 --- a/x-pack/plugins/synthetics/server/routes/overview_status/overview_status.ts +++ b/x-pack/plugins/synthetics/server/routes/overview_status/overview_status.ts @@ -76,7 +76,13 @@ export async function getStatus(context: RouteContext, params: OverviewStatusQue disabledMonitorsCount, projectMonitorsCount, monitorQueryIdToConfigIdMap, - } = await processMonitors(allMonitors, server, savedObjectsClient, syntheticsMonitorClient); + } = await processMonitors( + allMonitors, + server, + savedObjectsClient, + syntheticsMonitorClient, + queryLocations + ); // Account for locations filter const queryLocationsArray = diff --git a/x-pack/plugins/synthetics/server/routes/settings/private_locations/add_private_location.ts b/x-pack/plugins/synthetics/server/routes/settings/private_locations/add_private_location.ts new file mode 100644 index 0000000000000..9edd4bf53f81a --- /dev/null +++ b/x-pack/plugins/synthetics/server/routes/settings/private_locations/add_private_location.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; +import { getAllPrivateLocations } from './get_private_locations'; +import { + privateLocationsSavedObjectId, + privateLocationsSavedObjectName, +} from '../../../../common/saved_objects/private_locations'; +import { SyntheticsRestApiRouteFactory } from '../../../legacy_uptime/routes'; +import { SYNTHETICS_API_URLS } from '../../../../common/constants'; +import { PrivateLocation } from '../../../../common/runtime_types'; + +export const PrivateLocationSchema = schema.object({ + label: schema.string(), + id: schema.string(), + agentPolicyId: schema.string(), + concurrentMonitors: schema.number(), + tags: schema.maybe(schema.arrayOf(schema.string())), + geo: schema.maybe( + schema.object({ + lat: schema.oneOf([schema.number(), schema.string()]), + lon: schema.oneOf([schema.number(), schema.string()]), + }) + ), +}); + +export const addPrivateLocationRoute: SyntheticsRestApiRouteFactory = () => ({ + method: 'POST', + path: SYNTHETICS_API_URLS.PRIVATE_LOCATIONS, + validate: { + body: PrivateLocationSchema, + }, + writeAccess: true, + handler: async ({ request, server, savedObjectsClient }): Promise => { + const location = request.body as PrivateLocation; + + const { locations } = await getAllPrivateLocations(savedObjectsClient); + const existingLocations = locations.filter((loc) => loc.id !== location.agentPolicyId); + + const result = await savedObjectsClient.create( + privateLocationsSavedObjectName, + { locations: [...existingLocations, location] }, + { + id: privateLocationsSavedObjectId, + overwrite: true, + } + ); + + return result.attributes; + }, +}); diff --git a/x-pack/plugins/synthetics/server/routes/settings/private_locations/delete_private_location.ts b/x-pack/plugins/synthetics/server/routes/settings/private_locations/delete_private_location.ts new file mode 100644 index 0000000000000..7360adfbc0b60 --- /dev/null +++ b/x-pack/plugins/synthetics/server/routes/settings/private_locations/delete_private_location.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; +import { getAllPrivateLocations } from './get_private_locations'; +import { SyntheticsRestApiRouteFactory } from '../../../legacy_uptime/routes'; +import { SYNTHETICS_API_URLS } from '../../../../common/constants'; +import { + privateLocationsSavedObjectId, + privateLocationsSavedObjectName, +} from '../../../../common/saved_objects/private_locations'; + +export const deletePrivateLocationRoute: SyntheticsRestApiRouteFactory = () => ({ + method: 'DELETE', + path: SYNTHETICS_API_URLS.PRIVATE_LOCATIONS + '/{locationId}', + validate: { + params: schema.object({ + locationId: schema.string({ minLength: 1, maxLength: 1024 }), + }), + }, + writeAccess: true, + handler: async ({ savedObjectsClient, request, server }): Promise => { + const { locationId } = request.params as { locationId: string }; + + const { locations } = await getAllPrivateLocations(savedObjectsClient); + const remainingLocations = locations.filter((loc) => loc.id !== locationId); + + const result = await savedObjectsClient.create( + privateLocationsSavedObjectName, + { locations: remainingLocations }, + { + id: privateLocationsSavedObjectId, + overwrite: true, + } + ); + + return result.attributes; + }, +}); diff --git a/x-pack/plugins/synthetics/server/routes/settings/private_locations/get_private_locations.ts b/x-pack/plugins/synthetics/server/routes/settings/private_locations/get_private_locations.ts new file mode 100644 index 0000000000000..82b1f4f4e0e8a --- /dev/null +++ b/x-pack/plugins/synthetics/server/routes/settings/private_locations/get_private_locations.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 { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import { SyntheticsPrivateLocations } from '../../../../common/runtime_types'; +import { SyntheticsRestApiRouteFactory } from '../../../legacy_uptime/routes'; +import { SYNTHETICS_API_URLS } from '../../../../common/constants'; +import { + privateLocationsSavedObjectId, + privateLocationsSavedObjectName, +} from '../../../../common/saved_objects/private_locations'; + +export const getPrivateLocationsRoute: SyntheticsRestApiRouteFactory = () => ({ + method: 'GET', + path: SYNTHETICS_API_URLS.PRIVATE_LOCATIONS, + validate: {}, + handler: async ({ savedObjectsClient }): Promise => { + return await getAllPrivateLocations(savedObjectsClient); + }, +}); + +export const getAllPrivateLocations = async ( + savedObjectsClient: SavedObjectsClientContract +): Promise => { + try { + const obj = await savedObjectsClient.get( + privateLocationsSavedObjectName, + privateLocationsSavedObjectId + ); + return obj?.attributes ?? { locations: [] }; + } catch (getErr) { + return { locations: [] }; + } +}; diff --git a/x-pack/plugins/synthetics/server/saved_objects/synthetics_monitor/get_all_monitors.ts b/x-pack/plugins/synthetics/server/saved_objects/synthetics_monitor/get_all_monitors.ts index d134705093937..e0f404ebef875 100644 --- a/x-pack/plugins/synthetics/server/saved_objects/synthetics_monitor/get_all_monitors.ts +++ b/x-pack/plugins/synthetics/server/saved_objects/synthetics_monitor/get_all_monitors.ts @@ -11,6 +11,7 @@ import { SavedObjectsFindResult, } from '@kbn/core-saved-objects-api-server'; import pMap from 'p-map'; +import { intersection } from 'lodash'; import { periodToMs } from '../../routes/overview_status/overview_status'; import { UptimeServerSetup } from '../../legacy_uptime/lib/adapters'; import { getAllLocations } from '../../synthetics_service/get_all_locations'; @@ -64,7 +65,8 @@ export const processMonitors = async ( allMonitors: Array>, server: UptimeServerSetup, soClient: SavedObjectsClientContract, - syntheticsMonitorClient: SyntheticsMonitorClient + syntheticsMonitorClient: SyntheticsMonitorClient, + queryLocations?: string[] | string ) => { /** * Walk through all monitor saved objects, bucket IDs by disabled/enabled status. @@ -72,7 +74,6 @@ export const processMonitors = async ( * Track max period to make sure the snapshot query should reach back far enough to catch * latest ping for all enabled monitors. */ - const enabledMonitorQueryIds: string[] = []; let disabledCount = 0; let disabledMonitorsCount = 0; @@ -109,7 +110,13 @@ export const processMonitors = async ( monitorQueryIdToConfigIdMap[attrs[ConfigKey.MONITOR_QUERY_ID]] = attrs[ConfigKey.CONFIG_ID]; if (attrs[ConfigKey.ENABLED] === false) { - disabledCount += attrs[ConfigKey.LOCATIONS].length; + const monitorLocations = attrs[ConfigKey.LOCATIONS].map((location) => location.label); + const queriedLocations = Array.isArray(queryLocations) ? queryLocations : [queryLocations]; + const intersectingLocations = intersection( + monitorLocations, + queryLocations ? queriedLocations : monitorLocations + ); + disabledCount += intersectingLocations.length; disabledMonitorsCount += 1; } else { const missingLabels = new Set(); @@ -133,7 +140,10 @@ export const processMonitors = async ( getLocationLabel(locationId) ); - monitorLocationMap[attrs[ConfigKey.MONITOR_QUERY_ID]] = [...monLocs, ...locLabels]; + const monitorLocations = [...monLocs, ...locLabels]; + monitorLocationMap[attrs[ConfigKey.MONITOR_QUERY_ID]] = queryLocations + ? intersection(monitorLocations, queryLocations) + : monitorLocations; listOfLocationsSet = new Set([...listOfLocationsSet, ...monLocs, ...locLabels]); maxPeriod = Math.max(maxPeriod, periodToMs(attrs[ConfigKey.SCHEDULE])); diff --git a/x-pack/plugins/task_manager/server/constants.ts b/x-pack/plugins/task_manager/server/constants.ts index e843a0b4815d1..e9294b25e2859 100644 --- a/x-pack/plugins/task_manager/server/constants.ts +++ b/x-pack/plugins/task_manager/server/constants.ts @@ -5,3 +5,13 @@ * 2.0. */ export const TASK_MANAGER_INDEX = '.kibana_task_manager'; +export const CONCURRENCY_ALLOW_LIST_BY_TASK_TYPE: string[] = [ + // for testing + 'sampleTaskWithSingleConcurrency', + 'sampleTaskWithLimitedConcurrency', + 'timedTaskWithSingleConcurrency', + 'timedTaskWithLimitedConcurrency', + + // task types requiring a concurrency + 'report:execute', +]; diff --git a/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.test.ts b/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.test.ts index d6d0881cf0d50..374a52ea84b2f 100644 --- a/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.test.ts +++ b/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.test.ts @@ -22,6 +22,10 @@ import { TaskPoolMock } from './task_pool.mock'; import { executionContextServiceMock } from '@kbn/core/server/mocks'; import { taskManagerMock } from './mocks'; +jest.mock('./constants', () => ({ + CONCURRENCY_ALLOW_LIST_BY_TASK_TYPE: ['report'], +})); + const executionContext = executionContextServiceMock.createSetupContract(); describe('EphemeralTaskLifecycle', () => { diff --git a/x-pack/plugins/task_manager/server/polling_lifecycle.test.ts b/x-pack/plugins/task_manager/server/polling_lifecycle.test.ts index f4616b2f8c205..51cac494d3a67 100644 --- a/x-pack/plugins/task_manager/server/polling_lifecycle.test.ts +++ b/x-pack/plugins/task_manager/server/polling_lifecycle.test.ts @@ -31,6 +31,10 @@ jest.mock('./queries/task_claiming', () => { }; }); +jest.mock('./constants', () => ({ + CONCURRENCY_ALLOW_LIST_BY_TASK_TYPE: ['report', 'quickReport'], +})); + describe('TaskPollingLifecycle', () => { let clock: sinon.SinonFakeTimers; const taskManagerLogger = mockLogger(); diff --git a/x-pack/plugins/task_manager/server/queries/task_claiming.test.ts b/x-pack/plugins/task_manager/server/queries/task_claiming.test.ts index d847165bdba9d..6d24e4c0f5987 100644 --- a/x-pack/plugins/task_manager/server/queries/task_claiming.test.ts +++ b/x-pack/plugins/task_manager/server/queries/task_claiming.test.ts @@ -27,6 +27,17 @@ import { taskStoreMock } from '../task_store.mock'; import apm from 'elastic-apm-node'; import { TASK_MANAGER_TRANSACTION_TYPE } from '../task_running'; +jest.mock('../constants', () => ({ + CONCURRENCY_ALLOW_LIST_BY_TASK_TYPE: [ + 'limitedToZero', + 'limitedToOne', + 'anotherLimitedToZero', + 'anotherLimitedToOne', + 'limitedToTwo', + 'limitedToFive', + ], +})); + const taskManagerLogger = mockLogger(); beforeEach(() => jest.clearAllMocks()); diff --git a/x-pack/plugins/task_manager/server/task_scheduling.test.ts b/x-pack/plugins/task_manager/server/task_scheduling.test.ts index 4433a0ad83e8c..7f59f9f362554 100644 --- a/x-pack/plugins/task_manager/server/task_scheduling.test.ts +++ b/x-pack/plugins/task_manager/server/task_scheduling.test.ts @@ -27,6 +27,10 @@ jest.mock('uuid', () => ({ v4: () => 'v4uuid', })); +jest.mock('./constants', () => ({ + CONCURRENCY_ALLOW_LIST_BY_TASK_TYPE: ['foo'], +})); + jest.mock('elastic-apm-node', () => ({ currentTraceparent: 'parent', currentTransaction: { diff --git a/x-pack/plugins/task_manager/server/task_type_dictionary.test.ts b/x-pack/plugins/task_manager/server/task_type_dictionary.test.ts index cb2f436fa8676..039403816da5e 100644 --- a/x-pack/plugins/task_manager/server/task_type_dictionary.test.ts +++ b/x-pack/plugins/task_manager/server/task_type_dictionary.test.ts @@ -14,6 +14,10 @@ import { TaskTypeDictionary, } from './task_type_dictionary'; +jest.mock('./constants', () => ({ + CONCURRENCY_ALLOW_LIST_BY_TASK_TYPE: ['foo'], +})); + interface Opts { numTasks: number; } @@ -182,7 +186,6 @@ describe('taskTypeDictionary', () => { definitions.registerTaskDefinitions({ foo: { title: 'foo', - maxConcurrency: 2, createTaskRunner: jest.fn(), }, }); @@ -209,5 +212,19 @@ describe('taskTypeDictionary', () => { `"Task sampleTaskRemovedType has been removed from registration!"` ); }); + + it(`throws error when setting maxConcurrency to a task type that isn't allowed to set it`, () => { + expect(() => { + definitions.registerTaskDefinitions({ + foo2: { + title: 'foo2', + maxConcurrency: 2, + createTaskRunner: jest.fn(), + }, + }); + }).toThrowErrorMatchingInlineSnapshot( + `"maxConcurrency setting isn't allowed for task type: foo2"` + ); + }); }); }); diff --git a/x-pack/plugins/task_manager/server/task_type_dictionary.ts b/x-pack/plugins/task_manager/server/task_type_dictionary.ts index 9aeafa0a18c7d..3c0e4a0fe5542 100644 --- a/x-pack/plugins/task_manager/server/task_type_dictionary.ts +++ b/x-pack/plugins/task_manager/server/task_type_dictionary.ts @@ -7,6 +7,7 @@ import { Logger } from '@kbn/core/server'; import { TaskDefinition, taskDefinitionSchema, TaskRunCreatorFunction } from './task'; +import { CONCURRENCY_ALLOW_LIST_BY_TASK_TYPE } from './constants'; /** * Types that are no longer registered and will be marked as unregistered @@ -129,12 +130,25 @@ export class TaskTypeDictionary { throw new Error(`Task ${removed} has been removed from registration!`); } + for (const taskType of Object.keys(taskDefinitions)) { + if ( + taskDefinitions[taskType].maxConcurrency !== undefined && + !CONCURRENCY_ALLOW_LIST_BY_TASK_TYPE.includes(taskType) + ) { + // maxConcurrency is designed to limit how many tasks of the same type a single Kibana + // instance should run at a time. Meaning if you have 8 Kibanas running, you will still + // see up to 8 tasks running at a time but one per Kibana instance. This is helpful for + // reporting purposes but not for many other cases and are better off not setting this value. + throw new Error(`maxConcurrency setting isn't allowed for task type: ${taskType}`); + } + } + try { for (const definition of sanitizeTaskDefinitions(taskDefinitions)) { this.definitions.set(definition.type, definition); } } catch (e) { - this.logger.error('Could not sanitize task definitions'); + this.logger.error(`Could not sanitize task definitions: ${e.message}`); } } } diff --git a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json index 5a94f1361380c..99e466ebbb357 100644 --- a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json +++ b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @@ -5502,18 +5502,6 @@ }, "maps": { "properties": { - "indexPatternsWithGeoFieldCount": { - "type": "long" - }, - "indexPatternsWithGeoPointFieldCount": { - "type": "long" - }, - "indexPatternsWithGeoShapeFieldCount": { - "type": "long" - }, - "geoShapeAggLayersCount": { - "type": "long" - }, "mapsTotalCount": { "type": "long" }, diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 7464ed2fbb4fe..82a2c51216e42 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -9642,15 +9642,9 @@ "xpack.cases.markdownEditor.plugins.lens.insertLensSavedObjectModal.searchSelection.notFoundLabel": "Impossible de trouver un Lens correspondant.", "xpack.cases.markdownEditor.plugins.lens.insertLensSavedObjectModal.searchSelection.savedObjectType.lens": "Lens", "xpack.cases.markdownEditor.plugins.lens.openVisualizationButtonLabel": "Visualisation ouverte", - "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.filterButtonLabel": "Types", "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.searchInputHelpText": "Insérez un Lens à partir de modèles existants ou en créant un nouveau modèle. Vous créerez un Lens uniquement pour ce commentaire et ne changerez pas la Bibliothèque Visualize.", "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.searchInputLabel": "Sélectionner un Lens", "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.searchInputPrependLabel": "Modèle", - "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.searchPlaceholder": "Rechercher…", - "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.sortAsc": "Croissant", - "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.sortAuto": "Meilleure correspondance", - "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.sortButtonLabel": "Trier", - "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.sortDesc": "Décroissant", "xpack.cases.markdownEditor.plugins.lens.visualizationButtonLabel": "Visualisation", "xpack.cases.markdownEditor.plugins.timeline.noParenthesesErrorMsg": "Parenthèses gauches attendues", "xpack.cases.markdownEditor.preview": "Aperçu", @@ -10592,8 +10586,6 @@ "xpack.enterpriseSearch.crawler.authenticationPanel.emptyPrompt.description": "Cliquer sur {addAuthenticationButtonLabel} afin de fournir les informations d'identification nécessaires pour indexer le contenu protégé", "xpack.enterpriseSearch.crawler.components.crawlDetailsSummary.crawlCountOnDomains": "{crawlType} indexation sur {domainCount, plural, one {# domaine} other {# domaines}}", "xpack.enterpriseSearch.crawler.crawlCustomSettingsFlyout.includeSitemapsCheckboxLabel": "Inclure les plans de site découverts dans {robotsDotTxt}", - "xpack.enterpriseSearch.crawler.crawlRulesTable.description": "Créez une règle d'indexation pour inclure ou exclure les pages dont l'URL correspond à la règle. Les règles sont exécutées dans l'ordre séquentiel, et chaque URL est évaluée en fonction de la première correspondance. {link}", - "xpack.enterpriseSearch.crawler.deduplicationPanel.description": "Le robot d'indexation n'indexe que les pages uniques. Choisissez les champs que le robot d'indexation doit utiliser lorsqu'il recherche les pages en double. Désélectionnez tous les champs de schéma pour autoriser les documents en double dans ce domaine. {documentationLink}.", "xpack.enterpriseSearch.crawler.deleteDomainModal.description": "Supprimer le domaine {domainUrl} de votre robot d'indexation. Cela supprimera également tous les points d'entrée et toutes les règles d'indexation que vous avez configurés. Tous les documents associés à ce domaine seront supprimés lors de la prochaine indexation. {thisCannotBeUndoneMessage}", "xpack.enterpriseSearch.crawler.entryPointsTable.emptyMessageDescription": "{link} pour spécifier un point d'entrée pour le robot d'indexation", "xpack.enterpriseSearch.errorConnectingState.cloudErrorMessage": "Les nœuds Enterprise Search fonctionnent-ils dans votre déploiement cloud ? {deploymentSettingsLink}", @@ -26784,7 +26776,6 @@ "xpack.securitySolution.exceptions.hideCommentsLabel": "Masquer ({comments}) {comments, plural, =1 {commentaire} other {commentaires}}", "xpack.securitySolution.exceptions.list.deleted_successfully": "{listName} supprimée avec succès", "xpack.securitySolution.exceptions.list.exception.item.card.exceptionItemDeleteSuccessText": "\"{itemName}\" supprimé avec succès.", - "xpack.securitySolution.exceptions.list.exported_successfully": "{listName} exporté avec succès", "xpack.securitySolution.exceptions.referenceModalDefaultDescription": "Voulez-vous vraiment SUPPRIMER la liste d'exceptions nommée {listName} ?", "xpack.securitySolution.exceptions.referenceModalDescription": "Cette liste d'exceptions est associée à ({referenceCount}) {referenceCount, plural, =1 {règle} other {règles}}. Le retrait de cette liste d'exceptions supprimera également sa référence des règles associées.", "xpack.securitySolution.exceptions.referenceModalSuccessDescription": "Liste d'exceptions - {listId} - supprimée avec succès.", @@ -28640,7 +28631,6 @@ "xpack.securitySolution.detectionEngine.rules.all.exceptions.deleteError": "Une erreur s'est produite lors de la suppression de la liste d'exceptions", "xpack.securitySolution.detectionEngine.rules.all.exceptions.errorFetching": "Erreur lors de la récupération des listes d'exceptions", "xpack.securitySolution.detectionEngine.rules.all.exceptions.exportError": "Erreur d'exportation de la liste d'exceptions", - "xpack.securitySolution.detectionEngine.rules.all.exceptions.exportSuccess": "Réussite de l'exportation de la liste d'exceptions", "xpack.securitySolution.detectionEngine.rules.all.exceptions.idTitle": "ID de liste", "xpack.securitySolution.detectionEngine.rules.all.exceptions.listName": "Nom", "xpack.securitySolution.detectionEngine.rules.all.exceptions.refresh": "Actualiser", @@ -33714,7 +33704,6 @@ "xpack.synthetics.monitorManagement.monitorSync.failure.statusLabel": "Statut", "xpack.synthetics.monitorManagement.monitorSync.failure.title": "Impossible de synchroniser les moniteurs avec le service Synthetics", "xpack.synthetics.monitorManagement.nameRequired": "Le nom de l’emplacement est requis", - "xpack.synthetics.monitorManagement.needFleetReadAgentPoliciesPermission": "Vous n'êtes pas autorisé à accéder à Fleet. Des autorisations Fleet sont nécessaires pour créer de nouveaux emplacements privés.", "xpack.synthetics.monitorManagement.needPermissions": "Permissions requises", "xpack.synthetics.monitorManagement.new.label": "Nouveauté", "xpack.synthetics.monitorManagement.noLabel": "Annuler", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index f3c0b84e452b1..9f1cb387004e9 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -9631,15 +9631,9 @@ "xpack.cases.markdownEditor.plugins.lens.insertLensSavedObjectModal.searchSelection.notFoundLabel": "一致するLensが見つかりません。", "xpack.cases.markdownEditor.plugins.lens.insertLensSavedObjectModal.searchSelection.savedObjectType.lens": "レンズ", "xpack.cases.markdownEditor.plugins.lens.openVisualizationButtonLabel": "ビジュアライゼーションを開く", - "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.filterButtonLabel": "タイプ", "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.searchInputHelpText": "既存のテンプレートからLensを挿入するか、新しく作成します。このコメントでのみLensが作成されます。Visualize Libraryは変更されません。", "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.searchInputLabel": "Lensを選択", "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.searchInputPrependLabel": "テンプレート", - "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.searchPlaceholder": "検索…", - "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.sortAsc": "昇順", - "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.sortAuto": "ベストマッチ", - "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.sortButtonLabel": "並べ替え", - "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.sortDesc": "降順", "xpack.cases.markdownEditor.plugins.lens.visualizationButtonLabel": "ビジュアライゼーション", "xpack.cases.markdownEditor.plugins.timeline.noParenthesesErrorMsg": "想定される左括弧", "xpack.cases.markdownEditor.preview": "プレビュー", @@ -10579,8 +10573,6 @@ "xpack.enterpriseSearch.crawler.authenticationPanel.emptyPrompt.description": "{addAuthenticationButtonLabel}をクリックすると、保護されたコンテンツのクローリングに必要な資格情報を提供します", "xpack.enterpriseSearch.crawler.components.crawlDetailsSummary.crawlCountOnDomains": "{domainCount, plural, other {# 件のドメイン}}で{crawlType}クロール", "xpack.enterpriseSearch.crawler.crawlCustomSettingsFlyout.includeSitemapsCheckboxLabel": "{robotsDotTxt}で検出されたサイトマップを含める", - "xpack.enterpriseSearch.crawler.crawlRulesTable.description": "URLがルールと一致するページを含めるか除外するためのクロールルールを作成します。ルールは連続で実行されます。各URLは最初の一致に従って評価されます。{link}", - "xpack.enterpriseSearch.crawler.deduplicationPanel.description": "Webクローラーは一意のページにのみインデックスします。重複するページを検討するときにクローラーが使用するフィールドを選択します。すべてのスキーマフィールドを選択解除して、このドメインで重複するドキュメントを許可します。{documentationLink}。", "xpack.enterpriseSearch.crawler.deleteDomainModal.description": "ドメイン{domainUrl}をクローラーから削除します。これにより、設定したすべてのエントリポイントとクロールルールも削除されます。このドメインに関連するすべてのドキュメントは、次回のクロールで削除されます。{thisCannotBeUndoneMessage}", "xpack.enterpriseSearch.crawler.entryPointsTable.emptyMessageDescription": "クローラーのエントリポイントを指定するには、{link}してください", "xpack.enterpriseSearch.errorConnectingState.cloudErrorMessage": "クラウドデプロイのエンタープライズ サーチノードが実行中ですか?{deploymentSettingsLink}", @@ -26757,7 +26749,6 @@ "xpack.securitySolution.exceptions.hideCommentsLabel": "({comments}){comments, plural, other {件のコメント}}を非表示", "xpack.securitySolution.exceptions.list.deleted_successfully": "{listName}が正常に削除されました", "xpack.securitySolution.exceptions.list.exception.item.card.exceptionItemDeleteSuccessText": "\"{itemName}\"が正常に削除されました。", - "xpack.securitySolution.exceptions.list.exported_successfully": "{listName}が正常にエクスポートされました", "xpack.securitySolution.exceptions.referenceModalDefaultDescription": "名前{listName}の例外リストを削除しますか?", "xpack.securitySolution.exceptions.referenceModalDescription": "この例外リストは、({referenceCount}) {referenceCount, plural, other {個のルール}}に関連付けられています。この例外リストを削除すると、関連付けられたルールからの参照も削除されます。", "xpack.securitySolution.exceptions.referenceModalSuccessDescription": "例外リスト{listId}が正常に削除されました。", @@ -28612,7 +28603,6 @@ "xpack.securitySolution.detectionEngine.rules.all.exceptions.deleteError": "例外リストの削除中にエラーが発生しました", "xpack.securitySolution.detectionEngine.rules.all.exceptions.errorFetching": "例外リストの取得エラー", "xpack.securitySolution.detectionEngine.rules.all.exceptions.exportError": "例外リストエクスポートエラー", - "xpack.securitySolution.detectionEngine.rules.all.exceptions.exportSuccess": "例外リストエクスポート成功", "xpack.securitySolution.detectionEngine.rules.all.exceptions.idTitle": "リスト ID", "xpack.securitySolution.detectionEngine.rules.all.exceptions.listName": "名前", "xpack.securitySolution.detectionEngine.rules.all.exceptions.refresh": "更新", @@ -33685,7 +33675,6 @@ "xpack.synthetics.monitorManagement.monitorSync.failure.statusLabel": "ステータス", "xpack.synthetics.monitorManagement.monitorSync.failure.title": "モニターをSyntheticsサービスと同期できませんでした", "xpack.synthetics.monitorManagement.nameRequired": "場所名は必須です", - "xpack.synthetics.monitorManagement.needFleetReadAgentPoliciesPermission": "Fleet へのアクセスが許可されていません。新しい非公開の場所を作成するには、Fleet権限が必要です。", "xpack.synthetics.monitorManagement.needPermissions": "権限が必要です", "xpack.synthetics.monitorManagement.new.label": "新規", "xpack.synthetics.monitorManagement.noLabel": "キャンセル", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index e8809adf47caf..cc3498186a34a 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -9646,15 +9646,9 @@ "xpack.cases.markdownEditor.plugins.lens.insertLensSavedObjectModal.searchSelection.notFoundLabel": "未找到匹配的 lens。", "xpack.cases.markdownEditor.plugins.lens.insertLensSavedObjectModal.searchSelection.savedObjectType.lens": "Lens", "xpack.cases.markdownEditor.plugins.lens.openVisualizationButtonLabel": "打开可视化", - "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.filterButtonLabel": "类型", "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.searchInputHelpText": "通过现有模板或创建新模板来插入 Lens。您将仅为此注释创建 Lens,并且不会更改可视化库。", "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.searchInputLabel": "选择 Lens", "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.searchInputPrependLabel": "模板", - "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.searchPlaceholder": "搜索……", - "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.sortAsc": "升序", - "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.sortAuto": "最佳匹配", - "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.sortButtonLabel": "排序", - "xpack.cases.markdownEditor.plugins.lens.savedObjects.finder.sortDesc": "降序", "xpack.cases.markdownEditor.plugins.lens.visualizationButtonLabel": "可视化", "xpack.cases.markdownEditor.plugins.timeline.noParenthesesErrorMsg": "应为左括号", "xpack.cases.markdownEditor.preview": "预览", @@ -10596,8 +10590,6 @@ "xpack.enterpriseSearch.crawler.authenticationPanel.emptyPrompt.description": "单击{addAuthenticationButtonLabel}以提供爬网受保护内容所需的凭据", "xpack.enterpriseSearch.crawler.components.crawlDetailsSummary.crawlCountOnDomains": "在 {domainCount, plural, other {# 个域}}上进行 {crawlType} 爬网", "xpack.enterpriseSearch.crawler.crawlCustomSettingsFlyout.includeSitemapsCheckboxLabel": "包括在 {robotsDotTxt} 中发现的站点地图", - "xpack.enterpriseSearch.crawler.crawlRulesTable.description": "创建爬网规则以包括或排除 URL 匹配规则的页面。规则按顺序运行,每个 URL 根据第一个匹配进行评估。{link}", - "xpack.enterpriseSearch.crawler.deduplicationPanel.description": "网络爬虫仅索引唯一的页面。选择网络爬虫在考虑哪些网页重复时应使用的字段。取消选择所有架构字段以在此域上允许重复的文档。{documentationLink}。", "xpack.enterpriseSearch.crawler.deleteDomainModal.description": "从网络爬虫中移除域 {domainUrl}。这还会删除您已设置的所有入口点和爬网规则。将在下次爬网时移除与此域相关的任何文档。{thisCannotBeUndoneMessage}", "xpack.enterpriseSearch.crawler.entryPointsTable.emptyMessageDescription": "{link}以指定网络爬虫的入口点", "xpack.enterpriseSearch.errorConnectingState.cloudErrorMessage": "您的云部署是否正在运行 Enterprise Search 节点?{deploymentSettingsLink}", @@ -26792,7 +26784,6 @@ "xpack.securitySolution.exceptions.hideCommentsLabel": "隐藏 ({comments}) 个{comments, plural, other {注释}}", "xpack.securitySolution.exceptions.list.deleted_successfully": "已成功删除 {listName}", "xpack.securitySolution.exceptions.list.exception.item.card.exceptionItemDeleteSuccessText": "已成功删除“{itemName}”。", - "xpack.securitySolution.exceptions.list.exported_successfully": "已成功导出 {listName}", "xpack.securitySolution.exceptions.referenceModalDefaultDescription": "是否确定要删除名为 {listName} 的例外列表?", "xpack.securitySolution.exceptions.referenceModalDescription": "此例外列表与 ({referenceCount}) 个{referenceCount, plural, other {规则}}关联。移除此例外列表还将会删除其对关联规则的引用。", "xpack.securitySolution.exceptions.referenceModalSuccessDescription": "例外列表 - {listId} - 已成功删除。", @@ -28646,7 +28637,6 @@ "xpack.securitySolution.detectionEngine.rules.all.exceptions.deleteError": "删除例外列表时发生错误", "xpack.securitySolution.detectionEngine.rules.all.exceptions.errorFetching": "提取例外列表时出错", "xpack.securitySolution.detectionEngine.rules.all.exceptions.exportError": "例外列表导出错误", - "xpack.securitySolution.detectionEngine.rules.all.exceptions.exportSuccess": "例外列表导出成功", "xpack.securitySolution.detectionEngine.rules.all.exceptions.idTitle": "列表 ID", "xpack.securitySolution.detectionEngine.rules.all.exceptions.listName": "名称", "xpack.securitySolution.detectionEngine.rules.all.exceptions.refresh": "刷新", @@ -33720,7 +33710,6 @@ "xpack.synthetics.monitorManagement.monitorSync.failure.statusLabel": "状态", "xpack.synthetics.monitorManagement.monitorSync.failure.title": "监测无法与 Synthetics 服务同步", "xpack.synthetics.monitorManagement.nameRequired": "“位置名称”必填", - "xpack.synthetics.monitorManagement.needFleetReadAgentPoliciesPermission": "您无权访问 Fleet。需要 Fleet 权限才能创建新的专用位置。", "xpack.synthetics.monitorManagement.needPermissions": "需要权限", "xpack.synthetics.monitorManagement.new.label": "新建", "xpack.synthetics.monitorManagement.noLabel": "取消", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/json_editor_with_message_variables.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/json_editor_with_message_variables.test.tsx index 5349e60a60adb..0cac98f01f636 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/json_editor_with_message_variables.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/json_editor_with_message_variables.test.tsx @@ -69,4 +69,18 @@ describe('JsonEditorWithMessageVariables', () => { '{{{myVar}}}' ); }); + + test('renders correct value when the input value prop updates', () => { + const wrapper = mountWithIntl(); + + expect(wrapper.find('[data-test-subj="fooJsonEditor"]').first().prop('value')).toEqual(''); + + const inputTargetValue = '{"new": "value"}'; + wrapper.setProps({ inputTargetValue }); + wrapper.update(); + + expect(wrapper.find('[data-test-subj="fooJsonEditor"]').first().prop('value')).toEqual( + inputTargetValue + ); + }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/json_editor_with_message_variables.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/json_editor_with_message_variables.tsx index 643e1b69a513d..3485a99c39456 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/json_editor_with_message_variables.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/json_editor_with_message_variables.tsx @@ -37,7 +37,7 @@ interface Props { buttonTitle?: string; messageVariables?: ActionVariable[]; paramsProperty: string; - inputTargetValue?: string; + inputTargetValue?: string | null; label: string; errors?: string[]; areaLabel?: string; @@ -75,6 +75,13 @@ export const JsonEditorWithMessageVariables: React.FunctionComponent = ({ const { convertToJson, setXJson, xJson } = useXJsonMode(inputTargetValue ?? null); + useEffect(() => { + if (!xJson && inputTargetValue) { + setXJson(inputTargetValue); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [inputTargetValue]); + const onSelectMessageVariable = (variable: ActionVariable) => { const editor = editorRef.current; if (!editor) { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/update_api_key_modal_confirmation.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/update_api_key_modal_confirmation.test.tsx new file mode 100644 index 0000000000000..2c1b10fca0c6c --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/update_api_key_modal_confirmation.test.tsx @@ -0,0 +1,131 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as React from 'react'; +import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; +import { IToasts } from '@kbn/core/public'; +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { UpdateApiKeyModalConfirmation } from './update_api_key_modal_confirmation'; +import { useKibana } from '../../common/lib/kibana'; + +const Providers = ({ children }: { children: any }) => ( + {children} +); + +const renderWithProviders = (ui: any) => { + return render(ui, { wrapper: Providers }); +}; + +jest.mock('../../common/lib/kibana'); +const useKibanaMock = useKibana as jest.Mocked; + +describe('Update Api Key', () => { + const onCancel = jest.fn(); + const apiUpdateApiKeyCall = jest.fn(); + const setIsLoadingState = jest.fn(); + const onUpdated = jest.fn(); + const onSearchPopulate = jest.fn(); + + const addSuccess = jest.fn(); + const addError = jest.fn(); + + beforeAll(() => { + useKibanaMock().services.notifications.toasts = { + addSuccess, + addError, + } as unknown as IToasts; + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('Render modal updates Api Key', async () => { + renderWithProviders( + + ); + + expect( + await screen.findByText('You will not be able to recover the old API key') + ).toBeInTheDocument(); + }); + + it('Cancel modal updates Api Key', async () => { + renderWithProviders( + + ); + + fireEvent.click(await screen.findByText('Cancel')); + expect(onCancel).toHaveBeenCalled(); + }); + + it('Update an Api Key', async () => { + apiUpdateApiKeyCall.mockResolvedValue({}); + renderWithProviders( + + ); + + fireEvent.click(await screen.findByText('Update')); + expect(setIsLoadingState).toBeCalledTimes(1); + expect(apiUpdateApiKeyCall).toHaveBeenLastCalledWith(expect.objectContaining({ ids: ['2'] })); + await waitFor(() => { + expect(setIsLoadingState).toBeCalledTimes(2); + expect(onUpdated).toHaveBeenCalled(); + }); + }); + + it('Failed to update an Api Key', async () => { + apiUpdateApiKeyCall.mockRejectedValue(500); + renderWithProviders( + + ); + + fireEvent.click(await screen.findByText('Update')); + expect(setIsLoadingState).toBeCalledTimes(1); + expect(apiUpdateApiKeyCall).toHaveBeenLastCalledWith(expect.objectContaining({ ids: ['2'] })); + await waitFor(() => { + expect(setIsLoadingState).toBeCalledTimes(2); + expect(addError).toHaveBeenCalled(); + expect(addError.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + 500, + Object { + "title": "Failed to update the API key", + }, + ] + `); + }); + }); +}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_search_bar/alerts_search_bar.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_search_bar/alerts_search_bar.tsx index 193d6abf1433b..6a170363d34a8 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_search_bar/alerts_search_bar.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_search_bar/alerts_search_bar.tsx @@ -7,6 +7,7 @@ import React, { useCallback, useState } from 'react'; import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { Query, TimeRange } from '@kbn/es-query'; import { NO_INDEX_PATTERNS } from './constants'; import { SEARCH_BAR_PLACEHOLDER } from './translations'; import { AlertsSearchBarProps, QueryLanguageType } from './types'; @@ -32,16 +33,20 @@ export function AlertsSearchBar({ const { value: dataView, loading, error } = useAlertDataView(featureIds); const onQuerySubmit = useCallback( - (payload) => { - const { dateRange, query: nextQuery } = payload; + ({ dateRange, query: nextQuery }: { dateRange: TimeRange; query?: Query }) => { onQueryChange({ dateRange, - query: typeof nextQuery?.query === 'string' ? nextQuery.query : '', + query: typeof nextQuery?.query === 'string' ? nextQuery.query : undefined, }); setQueryLanguage((nextQuery?.language ?? 'kuery') as QueryLanguageType); }, [onQueryChange, setQueryLanguage] ); + const onRefresh = ({ dateRange }: { dateRange: TimeRange }) => { + onQueryChange({ + dateRange, + }); + }; return ( ); } 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 e75dd56168687..2c94c250c168a 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 @@ -17,6 +17,6 @@ export interface AlertsSearchBarProps { query?: string; 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/alerts_table/bulk_actions/bulk_actions.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/bulk_actions/bulk_actions.test.tsx index 27188fab622c9..3dc86c5522e8e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/bulk_actions/bulk_actions.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/bulk_actions/bulk_actions.test.tsx @@ -584,7 +584,8 @@ describe('AlertsTable.BulkActions', () => { }); }); - describe('and executing a bulk action', () => { + // FLAKY: https://github.com/elastic/kibana/issues/152176 + describe.skip('and executing a bulk action', () => { it('should return the are all selected flag set to true', async () => { const mockedFn = jest.fn(); const props = { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.test.tsx index f70437b33c451..2775b200cb6c6 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.test.tsx @@ -146,10 +146,7 @@ const renderWithProviders = (ui: any) => { return render(ui, { wrapper: AllTheProviders }); }; -// FLAKY: https://github.com/elastic/kibana/issues/134922 -// FLAKY: https://github.com/elastic/kibana/issues/134923 - -describe.skip('Update Api Key', () => { +describe('Update Api Key', () => { const addSuccess = jest.fn(); const addError = jest.fn(); @@ -177,7 +174,7 @@ describe.skip('Update Api Key', () => { cleanup(); }); - it('Updates the Api Key successfully', async () => { + it('Have the option to update API key', async () => { bulkUpdateAPIKey.mockResolvedValueOnce({ errors: [], total: 1, rules: [], skipped: [] }); renderWithProviders(); @@ -186,45 +183,7 @@ describe.skip('Update Api Key', () => { fireEvent.click((await screen.findAllByTestId('selectActionButton'))[1]); expect(screen.getByTestId('collapsedActionPanel')).toBeInTheDocument(); - fireEvent.click(await screen.findByText('Update API key')); - expect(screen.getByText('You will not be able to recover the old API key')).toBeInTheDocument(); - - fireEvent.click(await screen.findByText('Cancel')); - expect( - screen.queryByText('You will not be able to recover the old API key') - ).not.toBeInTheDocument(); - - fireEvent.click((await screen.findAllByTestId('selectActionButton'))[1]); - expect(screen.getByTestId('collapsedActionPanel')).toBeInTheDocument(); - - fireEvent.click(await screen.findByText('Update API key')); - - fireEvent.click(await screen.findByTestId('confirmModalConfirmButton')); - await waitFor(() => expect(addSuccess).toHaveBeenCalledWith('Updated API key for 1 rule.')); - expect(bulkUpdateAPIKey).toHaveBeenCalledWith(expect.objectContaining({ ids: ['2'] })); - expect(screen.queryByText("You can't recover the old API key")).not.toBeInTheDocument(); - }); - - it('Update API key fails', async () => { - bulkUpdateAPIKey.mockRejectedValueOnce(500); - renderWithProviders(); - - expect(await screen.findByText('test rule ok')).toBeInTheDocument(); - - fireEvent.click((await screen.findAllByTestId('selectActionButton'))[1]); - expect(screen.getByTestId('collapsedActionPanel')).toBeInTheDocument(); - - fireEvent.click(await screen.findByText('Update API key')); - expect(screen.getByText('You will not be able to recover the old API key')).toBeInTheDocument(); - - fireEvent.click(await screen.findByText('Update')); - await waitFor(() => - expect(addError).toHaveBeenCalledWith(500, { title: 'Failed to update the API key' }) - ); - expect(bulkUpdateAPIKey).toHaveBeenCalledWith(expect.objectContaining({ ids: ['2'] })); - expect( - screen.queryByText('You will not be able to recover the old API key') - ).not.toBeInTheDocument(); + expect(screen.queryByText('Update API key')).toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_edit.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_edit.test.tsx index 40204483e430b..271c12aa3f63b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_edit.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_edit.test.tsx @@ -156,7 +156,9 @@ describe('Rules list Bulk Edit', () => { queryClient.clear(); }); - describe('bulk actions', () => { + // FLAKY: https://github.com/elastic/kibana/issues/152268 + // FLAKY: https://github.com/elastic/kibana/issues/152267 + describe.skip('bulk actions', () => { beforeEach(async () => { renderWithProviders(); await waitForElementToBeRemoved(() => screen.queryByTestId('centerJustifiedSpinner')); diff --git a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/group_by_over.tsx b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/group_by_over.tsx index e20fdd1c1a0b7..0819f7541d1cc 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/group_by_over.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/group_by_over.tsx @@ -185,6 +185,7 @@ export const GroupByExpression = ({ 0} error={errors.termSize}> { + it('should generate expected events for flapping alerts that settle on active', async () => { await supertest .post(`${getUrlPrefix(space.id)}/internal/alerting/rules/settings/_flapping`) .set('kbn-xsrf', 'foo') .auth('superuser', 'superuser') .send({ enabled: true, - look_back_window: 3, - status_change_threshold: 2, + look_back_window: 6, + status_change_threshold: 4, }) .expect(200); const { body: createdAction } = await supertest @@ -642,7 +642,10 @@ export default function eventLogTests({ getService }: FtrProviderContext) { .expect(200); // pattern of when the alert should fire - const instance = [true, false, true, true, true, true, true]; + const instance = [true, false, false, true, false, true, false, true, false].concat( + ...new Array(8).fill(true), + false + ); const pattern = { instance, }; @@ -664,6 +667,11 @@ export default function eventLogTests({ getService }: FtrProviderContext) { group: 'default', params: {}, }, + { + id: createdAction.id, + group: 'recovered', + params: {}, + }, ], notify_when: RuleNotifyWhen.CHANGE, }) @@ -685,10 +693,10 @@ export default function eventLogTests({ getService }: FtrProviderContext) { // make sure the counts of the # of events per type are as expected ['execute-start', { gte: 6 }], ['execute', { gte: 6 }], - ['execute-action', { equal: 1 }], - ['new-instance', { equal: 1 }], + ['execute-action', { equal: 6 }], + ['new-instance', { equal: 3 }], ['active-instance', { gte: 6 }], - ['recovered-instance', { equal: 1 }], + ['recovered-instance', { equal: 3 }], ]), }); }); @@ -700,19 +708,24 @@ export default function eventLogTests({ getService }: FtrProviderContext) { event?.event?.action === 'recovered-instance' ) .map((event) => event?.kibana?.alert?.flapping); - const result = [false, true, true, true, false, false, false, false]; + const result = [false, false, false, false, false].concat( + new Array(9).fill(true), + false, + false, + false + ); expect(flapping).to.eql(result); }); - it('should generate expected events for flapping alerts that are mainly recovered', async () => { + it('should generate expected events for flapping alerts settle on recovered', async () => { await supertest .post(`${getUrlPrefix(space.id)}/internal/alerting/rules/settings/_flapping`) .set('kbn-xsrf', 'foo') .auth('superuser', 'superuser') .send({ enabled: true, - look_back_window: 3, - status_change_threshold: 2, + look_back_window: 6, + status_change_threshold: 4, }) .expect(200); const { body: createdAction } = await supertest @@ -727,7 +740,9 @@ export default function eventLogTests({ getService }: FtrProviderContext) { .expect(200); // pattern of when the alert should fire - const instance = [true, false, true, false, false, false, true]; + const instance = [true, false, false, true, false, true, false, true, false, true].concat( + new Array(11).fill(false) + ); const pattern = { instance, }; @@ -749,6 +764,11 @@ export default function eventLogTests({ getService }: FtrProviderContext) { group: 'default', params: {}, }, + { + id: createdAction.id, + group: 'recovered', + params: {}, + }, ], notify_when: RuleNotifyWhen.CHANGE, }) @@ -770,10 +790,10 @@ export default function eventLogTests({ getService }: FtrProviderContext) { // make sure the counts of the # of events per type are as expected ['execute-start', { gte: 6 }], ['execute', { gte: 6 }], - ['execute-action', { equal: 2 }], - ['new-instance', { equal: 2 }], - ['active-instance', { gte: 6 }], - ['recovered-instance', { equal: 2 }], + ['execute-action', { equal: 6 }], + ['new-instance', { equal: 3 }], + ['active-instance', { gte: 3 }], + ['recovered-instance', { equal: 3 }], ]), }); }); @@ -785,18 +805,20 @@ export default function eventLogTests({ getService }: FtrProviderContext) { event?.event?.action === 'recovered-instance' ) .map((event) => event?.kibana?.alert?.flapping); - expect(flapping).to.eql([false, true, true, true, true, true, true, true]); + expect(flapping).to.eql( + [false, false, false, false, false].concat(new Array(8).fill(true)) + ); }); - it('should generate expected events for flapping alerts that are mainly active with notifyWhen not set to "on status change"', async () => { + it('should generate expected events for flapping alerts over a period of time longer than the look back', async () => { await supertest .post(`${getUrlPrefix(space.id)}/internal/alerting/rules/settings/_flapping`) .set('kbn-xsrf', 'foo') .auth('superuser', 'superuser') .send({ enabled: true, - look_back_window: 3, - status_change_threshold: 2, + look_back_window: 5, + status_change_threshold: 5, }) .expect(200); const { body: createdAction } = await supertest @@ -811,7 +833,10 @@ export default function eventLogTests({ getService }: FtrProviderContext) { .expect(200); // pattern of when the alert should fire - const instance = [true, false, true, true, true, true, true]; + const instance = [true, false, false, true, false, true, false, true, false].concat( + ...new Array(8).fill(true), + false + ); const pattern = { instance, }; @@ -833,7 +858,13 @@ export default function eventLogTests({ getService }: FtrProviderContext) { group: 'default', params: {}, }, + { + id: createdAction.id, + group: 'recovered', + params: {}, + }, ], + notify_when: RuleNotifyWhen.CHANGE, }) ); @@ -851,12 +882,110 @@ export default function eventLogTests({ getService }: FtrProviderContext) { provider: 'alerting', actions: new Map([ // make sure the counts of the # of events per type are as expected - ['execute-start', { gte: 6 }], - ['execute', { gte: 6 }], - ['execute-action', { equal: 6 }], - ['new-instance', { equal: 1 }], + ['execute-start', { gte: 8 }], + ['execute', { gte: 8 }], + ['execute-action', { equal: 8 }], + ['new-instance', { equal: 4 }], + ['active-instance', { gte: 4 }], + ['recovered-instance', { equal: 4 }], + ]), + }); + }); + + const flapping = events + .filter( + (event) => + event?.event?.action === 'active-instance' || + event?.event?.action === 'recovered-instance' + ) + .map((event) => event?.kibana?.alert?.flapping); + const result = [false, false, false, false, false, false, false].concat( + new Array(6).fill(true), + false, + false, + false, + false + ); + expect(flapping).to.eql(result); + }); + + it('should generate expected events for flapping alerts that settle on active where notifyWhen is not set to "on status change"', async () => { + await supertest + .post(`${getUrlPrefix(space.id)}/internal/alerting/rules/settings/_flapping`) + .set('kbn-xsrf', 'foo') + .auth('superuser', 'superuser') + .send({ + enabled: true, + look_back_window: 6, + status_change_threshold: 4, + }) + .expect(200); + const { body: createdAction } = await supertest + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'MY action', + connector_type_id: 'test.noop', + config: {}, + secrets: {}, + }) + .expect(200); + + // pattern of when the alert should fire + const instance = [true, false, false, true, false, true, false, true, false].concat( + ...new Array(8).fill(true), + false + ); + const pattern = { + instance, + }; + + const response = await supertest + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send( + getTestRuleData({ + rule_type_id: 'test.patternFiring', + schedule: { interval: '1s' }, + throttle: null, + params: { + pattern, + }, + actions: [ + { + id: createdAction.id, + group: 'default', + params: {}, + }, + { + id: createdAction.id, + group: 'recovered', + params: {}, + }, + ], + }) + ); + + expect(response.status).to.eql(200); + const alertId = response.body.id; + objectRemover.add(space.id, alertId, 'rule', 'alerting'); + + // get the events we're expecting + const events = await retry.try(async () => { + return await getEventLog({ + getService, + spaceId: space.id, + type: 'alert', + id: alertId, + provider: 'alerting', + actions: new Map([ + // make sure the counts of the # of events per type are as expected + ['execute-start', { gte: 15 }], + ['execute', { gte: 15 }], + ['execute-action', { equal: 15 }], + ['new-instance', { equal: 3 }], ['active-instance', { gte: 6 }], - ['recovered-instance', { equal: 1 }], + ['recovered-instance', { equal: 3 }], ]), }); }); @@ -868,19 +997,24 @@ export default function eventLogTests({ getService }: FtrProviderContext) { event?.event?.action === 'recovered-instance' ) .map((event) => event?.kibana?.alert?.flapping); - const result = [false, true, true, false, false, false, false]; + const result = [false, false, false, false, false].concat( + new Array(7).fill(true), + false, + false, + false + ); expect(flapping).to.eql(result); }); - it('should generate expected events for flapping alerts that are mainly recovered with notifyWhen not set to "on status change"', async () => { + it('should generate expected events for flapping alerts that settle on recovered where notifyWhen is not set to "on status change"', async () => { await supertest .post(`${getUrlPrefix(space.id)}/internal/alerting/rules/settings/_flapping`) .set('kbn-xsrf', 'foo') .auth('superuser', 'superuser') .send({ enabled: true, - look_back_window: 3, - status_change_threshold: 2, + look_back_window: 6, + status_change_threshold: 4, }) .expect(200); const { body: createdAction } = await supertest @@ -895,7 +1029,9 @@ export default function eventLogTests({ getService }: FtrProviderContext) { .expect(200); // pattern of when the alert should fire - const instance = [true, false, true, false, false, false, true]; + const instance = [true, false, false, true, false, true, false, true, false, true].concat( + new Array(11).fill(false) + ); const pattern = { instance, }; @@ -917,6 +1053,11 @@ export default function eventLogTests({ getService }: FtrProviderContext) { group: 'default', params: {}, }, + { + id: createdAction.id, + group: 'recovered', + params: {}, + }, ], }) ); @@ -935,12 +1076,12 @@ export default function eventLogTests({ getService }: FtrProviderContext) { provider: 'alerting', actions: new Map([ // make sure the counts of the # of events per type are as expected - ['execute-start', { gte: 5 }], - ['execute', { gte: 5 }], - ['execute-action', { equal: 3 }], - ['new-instance', { equal: 2 }], + ['execute-start', { gte: 8 }], + ['execute', { gte: 8 }], + ['execute-action', { equal: 8 }], + ['new-instance', { equal: 3 }], ['active-instance', { gte: 3 }], - ['recovered-instance', { equal: 2 }], + ['recovered-instance', { equal: 3 }], ]), }); }); @@ -952,7 +1093,7 @@ export default function eventLogTests({ getService }: FtrProviderContext) { event?.event?.action === 'recovered-instance' ) .map((event) => event?.kibana?.alert?.flapping); - expect(flapping).to.eql([false, true, true, true, true]); + expect(flapping).to.eql([false, false, false, false, false, true, true, true]); }); }); } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data.ts index 3ac2fdb93add8..ad2c33b079b0a 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data.ts @@ -5,20 +5,24 @@ * 2.0. */ -import { alertFieldMap } from '@kbn/alerting-plugin/common/alert_schema'; -import { mappingFromFieldMap } from '@kbn/alerting-plugin/common/alert_schema/field_maps/mapping_from_field_map'; +import { alertFieldMap, ecsFieldMap, legacyAlertFieldMap } from '@kbn/alerts-as-data-utils'; +import { mappingFromFieldMap } from '@kbn/alerting-plugin/common'; import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function createAlertsAsDataTest({ getService }: FtrProviderContext) { const es = getService('es'); - const commonFrameworkMappings = mappingFromFieldMap(alertFieldMap, 'strict'); + const frameworkMappings = mappingFromFieldMap(alertFieldMap, 'strict'); + const legacyAlertMappings = mappingFromFieldMap(legacyAlertFieldMap, 'strict'); + const ecsMappings = mappingFromFieldMap(ecsFieldMap, 'strict'); describe('alerts as data', () => { it('should install common alerts as data resources on startup', async () => { const ilmPolicyName = '.alerts-ilm-policy'; - const componentTemplateName = 'alerts-common-component-template'; + const frameworkComponentTemplateName = '.alerts-framework-mappings'; + const legacyComponentTemplateName = '.alerts-legacy-alert-mappings'; + const ecsComponentTemplateName = '.alerts-ecs-mappings'; const commonIlmPolicy = await es.ilm.getLifecycle({ name: ilmPolicyName, @@ -41,23 +45,65 @@ export default function createAlertsAsDataTest({ getService }: FtrProviderContex }, }); - const { component_templates: componentTemplates } = await es.cluster.getComponentTemplate({ - name: componentTemplateName, + const { component_templates: componentTemplates1 } = await es.cluster.getComponentTemplate({ + name: frameworkComponentTemplateName, }); - expect(componentTemplates.length).to.eql(1); - const commonComponentTemplate = componentTemplates[0]; + expect(componentTemplates1.length).to.eql(1); + const frameworkComponentTemplate = componentTemplates1[0]; + + expect(frameworkComponentTemplate.name).to.eql(frameworkComponentTemplateName); + expect(frameworkComponentTemplate.component_template.template.mappings).to.eql( + frameworkMappings + ); + expect(frameworkComponentTemplate.component_template.template.settings).to.eql({ + index: { + number_of_shards: 1, + mapping: { + total_fields: { + limit: 1500, + }, + }, + }, + }); + + const { component_templates: componentTemplates2 } = await es.cluster.getComponentTemplate({ + name: legacyComponentTemplateName, + }); + + expect(componentTemplates2.length).to.eql(1); + const legacyComponentTemplate = componentTemplates2[0]; - expect(commonComponentTemplate.name).to.eql(componentTemplateName); - expect(commonComponentTemplate.component_template.template.mappings).to.eql( - commonFrameworkMappings + expect(legacyComponentTemplate.name).to.eql(legacyComponentTemplateName); + expect(legacyComponentTemplate.component_template.template.mappings).to.eql( + legacyAlertMappings ); - expect(commonComponentTemplate.component_template.template.settings).to.eql({ + expect(legacyComponentTemplate.component_template.template.settings).to.eql({ + index: { + number_of_shards: 1, + mapping: { + total_fields: { + limit: 1500, + }, + }, + }, + }); + + const { component_templates: componentTemplates3 } = await es.cluster.getComponentTemplate({ + name: ecsComponentTemplateName, + }); + + expect(componentTemplates3.length).to.eql(1); + const ecsComponentTemplate = componentTemplates3[0]; + + expect(ecsComponentTemplate.name).to.eql(ecsComponentTemplateName); + expect(ecsComponentTemplate.component_template.template.mappings).to.eql(ecsMappings); + expect(ecsComponentTemplate.component_template.template.settings).to.eql({ index: { number_of_shards: 1, mapping: { total_fields: { - limit: 100, + limit: 2500, }, }, }, @@ -65,7 +111,7 @@ export default function createAlertsAsDataTest({ getService }: FtrProviderContex }); it('should install context specific alerts as data resources on startup', async () => { - const componentTemplateName = 'alerts-test.always-firing-component-template'; + const componentTemplateName = '.alerts-test.always-firing-mappings'; const indexTemplateName = '.alerts-test.always-firing-default-template'; const indexName = '.alerts-test.always-firing-default-000001'; const contextSpecificMappings = { @@ -98,7 +144,7 @@ export default function createAlertsAsDataTest({ getService }: FtrProviderContex number_of_shards: 1, mapping: { total_fields: { - limit: 100, + limit: 1500, }, }, }, @@ -114,8 +160,8 @@ export default function createAlertsAsDataTest({ getService }: FtrProviderContex '.alerts-test.always-firing-default-*', ]); expect(contextIndexTemplate.index_template.composed_of).to.eql([ - 'alerts-common-component-template', - 'alerts-test.always-firing-component-template', + '.alerts-test.always-firing-mappings', + '.alerts-framework-mappings', ]); expect(contextIndexTemplate.index_template.template!.mappings).to.eql({ dynamic: false, @@ -150,7 +196,7 @@ export default function createAlertsAsDataTest({ getService }: FtrProviderContex dynamic: 'false', properties: { ...contextSpecificMappings, - ...commonFrameworkMappings.properties, + ...frameworkMappings.properties, }, }); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/event_log_alerts.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/event_log_alerts.ts index 97ef276fef930..cc5f3108ddd54 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/event_log_alerts.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/event_log_alerts.ts @@ -132,7 +132,7 @@ export default function eventLogAlertTests({ getService }: FtrProviderContext) { break; } } - expect(flapping).to.eql(new Array(instanceEvents.length - 1).fill(false).concat([true])); + expect(flapping).to.eql(new Array(instanceEvents.length).fill(false)); }); }); } diff --git a/x-pack/test/api_integration/apis/cases/common/users.ts b/x-pack/test/api_integration/apis/cases/common/users.ts index 29c14c35a5b76..f37b13b98f31a 100644 --- a/x-pack/test/api_integration/apis/cases/common/users.ts +++ b/x-pack/test/api_integration/apis/cases/common/users.ts @@ -4,12 +4,6 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ import { User } from '../../../../cases_api_integration/common/lib/authentication/types'; import { diff --git a/x-pack/test/api_integration/apis/cases/files.ts b/x-pack/test/api_integration/apis/cases/files.ts new file mode 100644 index 0000000000000..95ad400459f02 --- /dev/null +++ b/x-pack/test/api_integration/apis/cases/files.ts @@ -0,0 +1,365 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { APP_ID as CASES_APP_ID } from '@kbn/cases-plugin/common/constants'; +import { APP_ID as SECURITY_SOLUTION_APP_ID } from '@kbn/security-solution-plugin/common/constants'; +import { observabilityFeatureId as OBSERVABILITY_APP_ID } from '@kbn/observability-plugin/common'; +import { Owner } from '@kbn/cases-plugin/common/constants/types'; +import { BaseFilesClient } from '@kbn/shared-ux-file-types'; +import { User } from '../../../cases_api_integration/common/lib/authentication/types'; +import { + createFile, + deleteFiles, + uploadFile, + downloadFile, + createAndUploadFile, + listFiles, + getFileById, + deleteAllFiles, +} from '../../../cases_api_integration/common/lib/api'; +import { FtrProviderContext } from '../../ftr_provider_context'; +import { + casesAllUser, + casesNoDeleteUser, + casesReadUser, + obsCasesAllUser, + obsCasesNoDeleteUser, + obsCasesReadUser, + secAllCasesNoDeleteUser, + secAllUser, + secReadCasesReadUser, +} from './common/users'; + +interface TestScenario { + user: User; + owner: Owner; +} + +export default ({ getService }: FtrProviderContext): void => { + const supertestWithoutAuth = getService('supertestWithoutAuth'); + const supertest = getService('supertest'); + + describe('files', () => { + describe('failure requests', () => { + const createFileFailure = async (scenario: TestScenario) => { + await createFile({ + supertest: supertestWithoutAuth, + auth: { user: scenario.user, space: null }, + params: { + kind: scenario.owner, + name: 'testFile', + mimeType: 'image/png', + }, + expectedHttpCode: 403, + }); + }; + + const deleteFileFailure = async (scenario: TestScenario) => { + await deleteFiles({ + supertest: supertestWithoutAuth, + auth: { user: scenario.user, space: null }, + files: [{ kind: scenario.owner, id: 'abc' }], + expectedHttpCode: 403, + }); + }; + + const uploadFileFailure = async (scenario: TestScenario) => { + await uploadFile({ + supertest: supertestWithoutAuth, + auth: { user: scenario.user, space: null }, + data: 'abc', + kind: scenario.owner, + mimeType: 'image/png', + fileId: '123', + expectedHttpCode: 403, + }); + }; + + const listFilesFailure = async (scenario: TestScenario) => { + await listFiles({ + supertest: supertestWithoutAuth, + auth: { user: scenario.user, space: null }, + params: { + kind: scenario.owner, + }, + expectedHttpCode: 403, + }); + }; + + const downloadFileFailure = async (scenario: TestScenario) => { + await downloadFile({ + supertest: supertestWithoutAuth, + auth: { user: scenario.user, space: null }, + fileId: 'abc', + fileName: '123', + mimeType: 'image/png', + kind: scenario.owner, + expectedHttpCode: 401, + }); + }; + + const getFileByIdFailure = async (scenario: TestScenario) => { + await getFileById({ + supertest: supertestWithoutAuth, + auth: { user: scenario.user, space: null }, + id: 'abc', + kind: scenario.owner, + expectedHttpCode: 403, + }); + }; + + describe('user not authorized for a delete operation', () => { + const testScenarios: TestScenario[] = [ + { user: secAllCasesNoDeleteUser, owner: SECURITY_SOLUTION_APP_ID }, + { user: casesNoDeleteUser, owner: CASES_APP_ID }, + { user: obsCasesNoDeleteUser, owner: OBSERVABILITY_APP_ID }, + ]; + + for (const scenario of testScenarios) { + it('should fail to delete a file', async () => { + await deleteFileFailure(scenario); + }); + } + }); + + describe('user not authorized for write operations', () => { + const testScenarios: TestScenario[] = [ + { user: secReadCasesReadUser, owner: SECURITY_SOLUTION_APP_ID }, + { user: casesReadUser, owner: CASES_APP_ID }, + { user: obsCasesReadUser, owner: OBSERVABILITY_APP_ID }, + ]; + + for (const scenario of testScenarios) { + it('should fail to create a file', async () => { + await createFileFailure(scenario); + }); + + it('should fail to upload a file', async () => { + await uploadFileFailure(scenario); + }); + + it('should fail to delete a file', async () => { + await deleteFileFailure(scenario); + }); + } + }); + + describe('user not authorized for file kind', () => { + const testScenarios: TestScenario[] = [ + { user: secAllUser, owner: CASES_APP_ID }, + { + user: casesAllUser, + owner: SECURITY_SOLUTION_APP_ID, + }, + { + user: obsCasesAllUser, + owner: CASES_APP_ID, + }, + ]; + + for (const scenario of testScenarios) { + describe(`scenario user: ${scenario.user.username} owner: ${scenario.owner}`, () => { + it('should fail to create a file', async () => { + await createFileFailure(scenario); + }); + + it('should fail to upload a file', async () => { + await uploadFileFailure(scenario); + }); + + it('should fail to delete a file', async () => { + await deleteFileFailure(scenario); + }); + + it('should fail to list files', async () => { + await listFilesFailure(scenario); + }); + + it('should fail to download a file', async () => { + await downloadFileFailure(scenario); + }); + + it('should fail to get a file by its id', async () => { + await getFileByIdFailure(scenario); + }); + }); + } + }); + }); + + describe('successful requests', () => { + describe('users with read privileges', () => { + const testScenarios: TestScenario[] = [ + { user: secReadCasesReadUser, owner: SECURITY_SOLUTION_APP_ID }, + { user: casesReadUser, owner: CASES_APP_ID }, + { user: obsCasesReadUser, owner: OBSERVABILITY_APP_ID }, + ]; + + for (const scenario of testScenarios) { + describe(`scenario user: ${scenario.user.username} owner: ${scenario.owner}`, () => { + let createdFile: Awaited>; + + beforeEach(async () => { + const { create } = await createAndUploadFile({ + supertest, + data: 'abc', + createFileParams: { + name: 'testFile', + mimeType: 'image/png', + kind: scenario.owner, + }, + }); + createdFile = create; + }); + + afterEach(async () => { + await deleteAllFiles({ + supertest, + kind: scenario.owner, + }); + }); + + it('should list files', async () => { + const files = await listFiles({ + supertest: supertestWithoutAuth, + params: { kind: scenario.owner }, + auth: { user: scenario.user, space: null }, + }); + + expect(files.total).to.be(1); + expect(files.files[0].name).to.be(createdFile.file.name); + }); + + it('should get a file by its id', async () => { + const file = await getFileById({ + supertest: supertestWithoutAuth, + id: createdFile.file.id, + kind: scenario.owner, + auth: { user: scenario.user, space: null }, + }); + + expect(file.file.name).to.be(createdFile.file.name); + }); + }); + } + }); + + describe('users with all privileges', () => { + const testScenarios: TestScenario[] = [ + { user: secAllUser, owner: SECURITY_SOLUTION_APP_ID }, + { user: casesAllUser, owner: CASES_APP_ID }, + { user: obsCasesAllUser, owner: OBSERVABILITY_APP_ID }, + ]; + + for (const scenario of testScenarios) { + describe(`scenario user: ${scenario.user.username} owner: ${scenario.owner}`, () => { + it('should create and delete a file', async () => { + const createResult = await createFile({ + supertest: supertestWithoutAuth, + auth: { user: scenario.user, space: null }, + params: { + kind: scenario.owner, + name: 'testFile', + mimeType: 'image/png', + }, + }); + + await deleteFiles({ + supertest: supertestWithoutAuth, + auth: { user: scenario.user, space: null }, + files: [{ kind: scenario.owner, id: createResult.file.id }], + }); + }); + + describe('delete created file after test', () => { + afterEach(async () => { + await deleteAllFiles({ + supertest, + kind: scenario.owner, + }); + }); + + it('should list files', async () => { + const { create } = await createAndUploadFile({ + supertest: supertestWithoutAuth, + data: 'abc', + createFileParams: { + name: 'testFile', + mimeType: 'image/png', + kind: scenario.owner, + }, + auth: { user: scenario.user, space: null }, + }); + + const files = await listFiles({ + supertest: supertestWithoutAuth, + params: { kind: scenario.owner }, + auth: { user: scenario.user, space: null }, + }); + + expect(files.total).to.be(1); + expect(files.files[0].name).to.be(create.file.name); + }); + + it('should download a file', async () => { + const { create } = await createAndUploadFile({ + supertest: supertestWithoutAuth, + data: 'abc', + createFileParams: { + name: 'testFile', + mimeType: 'image/png', + kind: scenario.owner, + }, + auth: { user: scenario.user, space: null }, + }); + + const { body: buffer, header } = await downloadFile({ + supertest, + auth: { user: scenario.user, space: null }, + fileId: create.file.id, + kind: scenario.owner, + mimeType: 'image/png', + fileName: 'test.png', + }); + + expect(header['content-type']).to.eql('image/png'); + expect(header['content-disposition']).to.eql('attachment; filename="test.png"'); + expect(buffer.toString('utf8')).to.eql('abc'); + }); + + it('should upload a file', async () => { + const createResult = await createFile({ + supertest: supertestWithoutAuth, + auth: { user: scenario.user, space: null }, + params: { + kind: scenario.owner, + name: 'testFile', + mimeType: 'image/png', + }, + }); + + const uploadResult = await uploadFile({ + supertest: supertestWithoutAuth, + auth: { user: scenario.user, space: null }, + data: 'abc', + kind: scenario.owner, + mimeType: 'image/png', + fileId: createResult.file.id, + }); + + expect(uploadResult.ok).to.be(true); + expect(uploadResult.size).to.be(3); + }); + }); + }); + } + }); + }); + }); +}; diff --git a/x-pack/test/api_integration/apis/cases/index.ts b/x-pack/test/api_integration/apis/cases/index.ts index f12e43b34d784..5bce534873f10 100644 --- a/x-pack/test/api_integration/apis/cases/index.ts +++ b/x-pack/test/api_integration/apis/cases/index.ts @@ -34,5 +34,6 @@ export default function ({ loadTestFile, getService }: FtrProviderContext) { loadTestFile(require.resolve('./privileges')); loadTestFile(require.resolve('./suggest_user_profiles')); loadTestFile(require.resolve('./bulk_get_user_profiles')); + loadTestFile(require.resolve('./files')); }); } diff --git a/x-pack/test/api_integration/apis/maps/maps_telemetry.ts b/x-pack/test/api_integration/apis/maps/maps_telemetry.ts index d4d4c48c60336..1d63f8872776f 100644 --- a/x-pack/test/api_integration/apis/maps/maps_telemetry.ts +++ b/x-pack/test/api_integration/apis/maps/maps_telemetry.ts @@ -6,6 +6,7 @@ */ import expect from '@kbn/expect'; +import { estypes } from '@elastic/elasticsearch'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { @@ -24,14 +25,26 @@ export default function ({ getService }: FtrProviderContext) { }) .expect(200); + const geoPointFieldStats = apiResponse.cluster_stats.indices.mappings.field_types.find( + (fieldStat: estypes.ClusterStatsFieldTypes) => { + return fieldStat.name === 'geo_point'; + } + ); + expect(geoPointFieldStats.count).to.be(7); + expect(geoPointFieldStats.index_count).to.be(6); + + const geoShapeFieldStats = apiResponse.cluster_stats.indices.mappings.field_types.find( + (fieldStat: estypes.ClusterStatsFieldTypes) => { + return fieldStat.name === 'geo_shape'; + } + ); + expect(geoShapeFieldStats.count).to.be(3); + expect(geoShapeFieldStats.index_count).to.be(3); + const mapUsage = apiResponse.stack_stats.kibana.plugins.maps; delete mapUsage.timeCaptured; expect(mapUsage).eql({ - geoShapeAggLayersCount: 1, - indexPatternsWithGeoFieldCount: 6, - indexPatternsWithGeoPointFieldCount: 4, - indexPatternsWithGeoShapeFieldCount: 2, mapsTotalCount: 27, basemaps: {}, joins: { term: { min: 1, max: 1, total: 3, avg: 0.1111111111111111 } }, diff --git a/x-pack/test/api_integration/apis/metrics_ui/log_entry_highlights.ts b/x-pack/test/api_integration/apis/metrics_ui/log_entry_highlights.ts index aba182274c9a3..2e328e91f8c25 100644 --- a/x-pack/test/api_integration/apis/metrics_ui/log_entry_highlights.ts +++ b/x-pack/test/api_integration/apis/metrics_ui/log_entry_highlights.ts @@ -54,7 +54,7 @@ export default function ({ getService }: FtrProviderContext) { .set(COMMON_HEADERS) .send( logEntriesHighlightsRequestRT.encode({ - sourceId: 'default', + logView: { type: 'log-view-reference', logViewId: 'default' }, startTimestamp: KEY_BEFORE_START.time, endTimestamp: KEY_AFTER_END.time, highlightTerms: ['some string that does not exist'], @@ -82,7 +82,7 @@ export default function ({ getService }: FtrProviderContext) { .set(COMMON_HEADERS) .send( logEntriesHighlightsRequestRT.encode({ - sourceId: 'default', + logView: { type: 'log-view-reference', logViewId: 'default' }, startTimestamp: KEY_BEFORE_START.time, endTimestamp: KEY_AFTER_END.time, highlightTerms: ['message of document 0'], @@ -130,7 +130,7 @@ export default function ({ getService }: FtrProviderContext) { .set(COMMON_HEADERS) .send( logEntriesHighlightsRequestRT.encode({ - sourceId: 'default', + logView: { type: 'log-view-reference', logViewId: 'default' }, startTimestamp: KEY_BEFORE_START.time, endTimestamp: KEY_AFTER_END.time, highlightTerms: ['generate_test_data/simple_logs'], @@ -166,7 +166,7 @@ export default function ({ getService }: FtrProviderContext) { .set(COMMON_HEADERS) .send( logEntriesHighlightsRequestRT.encode({ - sourceId: 'default', + logView: { type: 'log-view-reference', logViewId: 'default' }, startTimestamp: KEY_BEFORE_START.time, endTimestamp: KEY_AFTER_END.time, query: JSON.stringify({ diff --git a/x-pack/test/api_integration/apis/metrics_ui/log_summary.ts b/x-pack/test/api_integration/apis/metrics_ui/log_summary.ts index 4ac098f95764d..3ccfc4c267c7b 100644 --- a/x-pack/test/api_integration/apis/metrics_ui/log_summary.ts +++ b/x-pack/test/api_integration/apis/metrics_ui/log_summary.ts @@ -49,7 +49,7 @@ export default function ({ getService }: FtrProviderContext) { .set(COMMON_HEADERS) .send( logEntriesSummaryRequestRT.encode({ - sourceId: 'default', + logView: { type: 'log-view-reference', logViewId: 'default' }, startTimestamp, endTimestamp, bucketSize, diff --git a/x-pack/test/api_integration/apis/metrics_ui/snapshot.ts b/x-pack/test/api_integration/apis/metrics_ui/snapshot.ts index 9fa041108964e..8eef4dc32df1c 100644 --- a/x-pack/test/api_integration/apis/metrics_ui/snapshot.ts +++ b/x-pack/test/api_integration/apis/metrics_ui/snapshot.ts @@ -248,7 +248,7 @@ export default function ({ getService }: FtrProviderContext) { name: 'cpu', value: null, max: 0.47105555555555556, - avg: 0.0672936507936508, + avg: 0.47105555555555556, }; expect(snapshot).to.have.property('nodes'); diff --git a/x-pack/test/apm_api_integration/tests/service_maps/service_maps_kuery_filter.spec.ts b/x-pack/test/apm_api_integration/tests/service_maps/service_maps_kuery_filter.spec.ts index e2dcafc0af815..7a454ad5c9687 100644 --- a/x-pack/test/apm_api_integration/tests/service_maps/service_maps_kuery_filter.spec.ts +++ b/x-pack/test/apm_api_integration/tests/service_maps/service_maps_kuery_filter.spec.ts @@ -5,14 +5,13 @@ * 2.0. */ import expect from '@kbn/expect'; -import { apm, timerange } from '@kbn/apm-synthtrace-client'; +import { timerange, serviceMap } from '@kbn/apm-synthtrace-client'; import { APIClientRequestParamsOf, APIReturnType, } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; import { RecursivePartial } from '@kbn/apm-plugin/typings/common'; import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { generateTrace } from '../traces/generate_trace'; export default function ApiTest({ getService }: FtrProviderContext) { const registry = getService('registry'); @@ -44,30 +43,30 @@ export default function ApiTest({ getService }: FtrProviderContext) { registry.when('Service map', { config: 'trial', archives: [] }, () => { describe('optional kuery param', () => { before(async () => { - const go = apm - .service({ name: 'synthbeans-go', environment: 'test', agentName: 'go' }) - .instance('synthbeans-go'); - const java = apm - .service({ name: 'synthbeans-java', environment: 'test', agentName: 'java' }) - .instance('synthbeans-java'); - const node = apm - .service({ name: 'synthbeans-node', environment: 'test', agentName: 'nodejs' }) - .instance('synthbeans-node'); - const events = timerange(start, end) .interval('15m') .rate(1) - .generator((timestamp) => { - return [ - generateTrace(timestamp, [go, java]), - generateTrace(timestamp, [java, go], 'redis'), - generateTrace(timestamp, [node], 'redis'), - generateTrace(timestamp, [node, java, go], 'elasticsearch').defaults({ - 'labels.name': 'node-java-go-es', - }), - generateTrace(timestamp, [go, node, java]), - ]; - }); + .generator( + serviceMap({ + services: [ + { 'synthbeans-go': 'go' }, + { 'synthbeans-java': 'java' }, + { 'synthbeans-node': 'nodejs' }, + ], + definePaths([go, java, node]) { + return [ + [go, java], + [java, go, 'redis'], + [node, 'redis'], + { + path: [node, java, go, 'elasticsearch'], + transaction: (t) => t.defaults({ 'labels.name': 'node-java-go-es' }), + }, + [go, node, java], + ]; + }, + }) + ); await synthtraceEsClient.index(events); }); diff --git a/x-pack/test/cases_api_integration/common/lib/api/files.ts b/x-pack/test/cases_api_integration/common/lib/api/files.ts new file mode 100644 index 0000000000000..05450e9da2cc9 --- /dev/null +++ b/x-pack/test/cases_api_integration/common/lib/api/files.ts @@ -0,0 +1,226 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type SuperTest from 'supertest'; +import { apiRoutes as fileApiRoutes } from '@kbn/files-plugin/public/files_client/files_client'; +import { BaseFilesClient } from '@kbn/shared-ux-file-types'; +import { superUser } from '../authentication/users'; +import { User } from '../authentication/types'; +import { getSpaceUrlPrefix } from './helpers'; + +export const downloadFile = async ({ + supertest, + fileId, + kind, + mimeType, + fileName, + expectedHttpCode = 200, + auth = { user: superUser, space: null }, +}: { + supertest: SuperTest.SuperTest; + fileId: string; + kind: string; + mimeType: string; + fileName: string; + expectedHttpCode?: number; + auth?: { user: User; space: string | null }; +}): ReturnType => { + const result = await supertest + .get( + `${getSpaceUrlPrefix(auth.space)}${fileApiRoutes.getDownloadRoute(kind, fileId, fileName)}` + ) + .set('accept', mimeType) + .buffer() + .expect(expectedHttpCode); + + return result; +}; + +export interface FileDescriptor { + kind: string; + id: string; +} + +export const deleteFiles = async ({ + supertest, + files, + expectedHttpCode = 200, + auth = { user: superUser, space: null }, +}: { + supertest: SuperTest.SuperTest; + files: FileDescriptor[]; + expectedHttpCode?: number; + auth?: { user: User; space: string | null }; +}) => { + await Promise.all( + files.map(async (fileInfo) => { + return await supertest + .delete( + `${getSpaceUrlPrefix(auth.space)}${fileApiRoutes.getDeleteRoute( + fileInfo.kind, + fileInfo.id + )}` + ) + .set('kbn-xsrf', 'true') + .auth(auth.user.username, auth.user.password) + .send() + .expect(expectedHttpCode); + }) + ); +}; + +export const deleteAllFiles = async ({ + supertest, + kind, + auth = { user: superUser, space: null }, + expectedHttpCode = 200, +}: { + supertest: SuperTest.SuperTest; + kind: string; + expectedHttpCode?: number; + auth?: { user: User; space: string | null }; +}) => { + const files = await listFiles({ supertest, params: { kind }, auth, expectedHttpCode }); + + await deleteFiles({ + supertest, + files: files.files.map((fileInfo) => ({ kind, id: fileInfo.id })), + auth, + expectedHttpCode, + }); +}; + +export const getFileById = async ({ + supertest, + id, + kind, + expectedHttpCode = 200, + auth = { user: superUser, space: null }, +}: { + supertest: SuperTest.SuperTest; + id: string; + kind: string; + expectedHttpCode?: number; + auth?: { user: User; space: string | null }; +}): ReturnType => { + const { body } = await supertest + .get(`${getSpaceUrlPrefix(auth.space)}${fileApiRoutes.getByIdRoute(kind, id)}`) + .auth(auth.user.username, auth.user.password) + .expect(expectedHttpCode); + + return body; +}; + +export const listFiles = async ({ + supertest, + params, + expectedHttpCode = 200, + auth = { user: superUser, space: null }, +}: { + supertest: SuperTest.SuperTest; + params: Parameters[0]; + expectedHttpCode?: number; + auth?: { user: User; space: string | null }; +}): ReturnType => { + const { page, perPage, kind, ...rest } = params; + + const { body } = await supertest + .post(`${getSpaceUrlPrefix(auth.space)}${fileApiRoutes.getListRoute(kind)}`) + .auth(auth.user.username, auth.user.password) + .set('kbn-xsrf', 'true') + .query({ page, perPage }) + .send(rest) + .expect(expectedHttpCode); + + return body; +}; + +type CreateFileSchema = Omit[0], 'mimeType'> & { + mimeType: string; +}; + +export const createFile = async ({ + supertest, + params, + expectedHttpCode = 200, + auth = { user: superUser, space: null }, +}: { + supertest: SuperTest.SuperTest; + params: CreateFileSchema; + expectedHttpCode?: number; + auth?: { user: User; space: string | null }; +}): ReturnType => { + const { kind, ...rest } = params; + const { body } = await supertest + .post(`${getSpaceUrlPrefix(auth.space)}${fileApiRoutes.getCreateFileRoute(kind)}`) + .auth(auth.user.username, auth.user.password) + .set('kbn-xsrf', 'true') + .send(rest) + .expect(expectedHttpCode); + + return body; +}; + +export const uploadFile = async ({ + supertest, + data, + kind, + fileId, + mimeType, + expectedHttpCode = 200, + auth = { user: superUser, space: null }, +}: { + supertest: SuperTest.SuperTest; + data: string | object; + kind: string; + fileId: string; + mimeType: string; + expectedHttpCode?: number; + auth?: { user: User; space: string | null }; +}): ReturnType => { + const { body } = await supertest + .put(`${getSpaceUrlPrefix(auth.space)}${fileApiRoutes.getUploadRoute(kind, fileId)}`) + .auth(auth.user.username, auth.user.password) + .set('kbn-xsrf', 'true') + .set('Content-Type', mimeType) + .send(data) + .expect(expectedHttpCode); + + return body; +}; + +export const createAndUploadFile = async ({ + supertest, + data, + createFileParams, + expectedHttpCode = 200, + auth = { user: superUser, space: null }, +}: { + supertest: SuperTest.SuperTest; + data: string | object; + createFileParams: CreateFileSchema; + expectedHttpCode?: number; + auth?: { user: User; space: string | null }; +}) => { + const createFileResult = await createFile({ + supertest, + params: createFileParams, + expectedHttpCode, + auth, + }); + + const uploadFileResult = await uploadFile({ + supertest, + data, + fileId: createFileResult.file.id, + mimeType: createFileParams.mimeType, + kind: createFileParams.kind, + auth, + }); + + return { create: createFileResult, upload: uploadFileResult }; +}; diff --git a/x-pack/test/cases_api_integration/common/lib/api/index.ts b/x-pack/test/cases_api_integration/common/lib/api/index.ts index c15c19232edf6..3bf0c470b9ba2 100644 --- a/x-pack/test/cases_api_integration/common/lib/api/index.ts +++ b/x-pack/test/cases_api_integration/common/lib/api/index.ts @@ -54,6 +54,7 @@ export * from './user_actions'; export * from './user_profiles'; export * from './omit'; export * from './configuration'; +export * from './files'; export { getSpaceUrlPrefix } from './helpers'; function toArray(input: T | T[]): T[] { diff --git a/x-pack/test/cases_api_integration/common/lib/authentication/index.ts b/x-pack/test/cases_api_integration/common/lib/authentication/index.ts index d425eae5a373c..195549561571b 100644 --- a/x-pack/test/cases_api_integration/common/lib/authentication/index.ts +++ b/x-pack/test/cases_api_integration/common/lib/authentication/index.ts @@ -20,9 +20,8 @@ export const getUserInfo = (user: User): UserInfo => ({ export const createSpaces = async (getService: CommonFtrProviderContext['getService']) => { const spacesService = getService('spaces'); - for (const space of spaces) { - await spacesService.create(space); - } + + await Promise.all(spaces.map((space) => spacesService.create(space))); }; /** @@ -51,23 +50,16 @@ export const createUsersAndRoles = async ( }); }; - for (const role of rolesToCreate) { - await createRole(role); - } - - for (const user of usersToCreate) { - await createUser(user); - } + await Promise.all(rolesToCreate.map((role) => createRole(role))); + await Promise.all(usersToCreate.map((user) => createUser(user))); }; export const deleteSpaces = async (getService: CommonFtrProviderContext['getService']) => { const spacesService = getService('spaces'); - for (const space of spaces) { - try { - await spacesService.delete(space.id); - } catch (error) { - // ignore errors because if a migration is run it will delete the .kibana index which remove the spaces and users - } + try { + await Promise.allSettled(spaces.map((space) => spacesService.delete(space.id))); + } catch (error) { + // ignore errors because if a migration is run it will delete the .kibana index which remove the spaces and users } }; @@ -78,20 +70,16 @@ export const deleteUsersAndRoles = async ( ) => { const security = getService('security'); - for (const user of usersToDelete) { - try { - await security.user.delete(user.username); - } catch (error) { - // ignore errors because if a migration is run it will delete the .kibana index which remove the spaces and users - } + try { + await Promise.allSettled(usersToDelete.map((user) => security.user.delete(user.username))); + } catch (error) { + // ignore errors because if a migration is run it will delete the .kibana index which remove the spaces and users } - for (const role of rolesToDelete) { - try { - await security.role.delete(role.name); - } catch (error) { - // ignore errors because if a migration is run it will delete the .kibana index which remove the spaces and users - } + try { + await Promise.allSettled(rolesToDelete.map((role) => security.role.delete(role.name))); + } catch (error) { + // ignore errors because if a migration is run it will delete the .kibana index which remove the spaces and users } }; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/import_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/import_rules.ts index dd82fe27e726f..a15b6d799adea 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/import_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/import_rules.ts @@ -93,6 +93,84 @@ const getImportRuleBuffer = (connectorId: string) => { const buffer = Buffer.from(`${rule1String}\n`); return buffer; }; +const getImportRuleWithConnectorsBuffer = (connectorId: string) => { + const rule1 = { + id: '53aad690-544e-11ec-a349-11361cc441c4', + updated_at: '2021-12-03T15:33:13.271Z', + updated_by: 'elastic', + created_at: '2021-12-03T15:33:13.271Z', + created_by: 'elastic', + name: '7.16 test with action', + tags: [], + interval: '5m', + enabled: true, + description: 'test', + risk_score: 21, + severity: 'low', + license: '', + output_index: '', + meta: { from: '1m', kibana_siem_app_url: 'http://0.0.0.0:5601/s/7/app/security' }, + author: [], + false_positives: [], + from: 'now-360s', + rule_id: 'aa525d7c-8948-439f-b32d-27e00c750246', + max_signals: 100, + risk_score_mapping: [], + severity_mapping: [], + threat: [], + to: 'now', + references: [], + version: 1, + exceptions_list: [], + immutable: false, + type: 'query', + language: 'kuery', + index: [ + 'apm-*-transaction*', + 'traces-apm*', + 'auditbeat-*', + 'endgame-*', + 'filebeat-*', + 'logs-*', + 'packetbeat-*', + 'winlogbeat-*', + ], + query: '*:*', + filters: [], + throttle: '1h', + actions: [ + { + group: 'default', + id: connectorId, + params: { + message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', + }, + action_type_id: '.slack', + }, + ], + }; + const connector = { + id: connectorId, + type: 'action', + updated_at: '2023-01-25T14:35:52.852Z', + created_at: '2023-01-25T14:35:52.852Z', + version: 'WzUxNTksMV0=', + attributes: { + actionTypeId: '.slack', + name: 'slack', + isMissingSecrets: false, + config: {}, + secrets: {}, + }, + references: [], + migrationVersion: { action: '8.3.0' }, + coreMigrationVersion: '8.7.0', + }; + const rule1String = JSON.stringify(rule1); + const connectorString = JSON.stringify(connector); + const buffer = Buffer.from(`${rule1String}\n${connectorString}`); + return buffer; +}; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { @@ -1119,95 +1197,179 @@ export default ({ getService }: FtrProviderContext): void => { ); }); - it('importing a non-default-space 7.16 rule with a connector made in the non-default space should result in a 200', async () => { - const spaceId = '714-space'; - // connectorId is from the 7.x connector here - // x-pack/test/functional/es_archives/security_solution/import_rule_connector - const buffer = getImportRuleBuffer(space714ActionConnectorId); - - const { body } = await supertest - .post(`/s/${spaceId}${DETECTION_ENGINE_RULES_URL}/_import`) - .set('kbn-xsrf', 'true') - .attach('file', buffer, 'rules.ndjson') - .expect(200); - expect(body.success).to.eql(true); - expect(body.success_count).to.eql(1); - expect(body.errors.length).to.eql(0); - }); - - // When objects become share-capable we will either add / update this test - it('importing a non-default-space 7.16 rule with a connector made in the non-default space into the default space should result in a 404', async () => { - // connectorId is from the 7.x connector here - // x-pack/test/functional/es_archives/security_solution/import_rule_connector - const buffer = getImportRuleBuffer(space714ActionConnectorId); - - const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) - .set('kbn-xsrf', 'true') - .attach('file', buffer, 'rules.ndjson') - .expect(200); - expect(body.success).to.equal(false); - expect(body.errors[0].error.status_code).to.equal(404); - expect(body.errors[0].error.message).to.equal( - `1 connector is missing. Connector id missing is: ${space714ActionConnectorId}` - ); - }); - - // When objects become share-capable we will either add / update this test - it('importing a non-default-space 7.16 rule with a connector made in the non-default space into a different non-default space should result in a 404', async () => { - const spaceId = '4567-space'; - // connectorId is from the 7.x connector here - // x-pack/test/functional/es_archives/security_solution/import_rule_connector - // it - const buffer = getImportRuleBuffer(space714ActionConnectorId); - - const { body } = await supertest - .post(`/s/${spaceId}${DETECTION_ENGINE_RULES_URL}/_import`) - .set('kbn-xsrf', 'true') - .attach('file', buffer, 'rules.ndjson') - .expect(200); - expect(body.success).to.equal(false); - expect(body.errors[0].error.status_code).to.equal(404); - expect(body.errors[0].error.message).to.equal( - `1 connector is missing. Connector id missing is: ${space714ActionConnectorId}` - ); - }); - - it('importing a default-space 7.16 rule with a connector made in the default space into the default space should result in a 200', async () => { - // connectorId is from the 7.x connector here - // x-pack/test/functional/es_archives/security_solution/import_rule_connector - // it - const buffer = getImportRuleBuffer(defaultSpaceActionConnectorId); + describe('should be imported into the non-default space', () => { + it('importing a non-default-space 7.16 rule with a connector made in the non-default space should result in a 200', async () => { + const spaceId = '714-space'; + // connectorId is from the 7.x connector here + // x-pack/test/functional/es_archives/security_solution/import_rule_connector + const buffer = getImportRuleBuffer(space714ActionConnectorId); + + const { body } = await supertest + .post(`/s/${spaceId}${DETECTION_ENGINE_RULES_URL}/_import`) + .set('kbn-xsrf', 'true') + .attach('file', buffer, 'rules.ndjson') + .expect(200); + expect(body.success).to.eql(true); + expect(body.success_count).to.eql(1); + expect(body.errors.length).to.eql(0); + }); - const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) - .set('kbn-xsrf', 'true') - .attach('file', buffer, 'rules.ndjson') - .expect(200); - expect(body.success).to.equal(true); - expect(body.success_count).to.eql(1); - expect(body.errors.length).to.eql(0); + it('should import a non-default-space 7.16 rule with a connector made in the non-default space', async () => { + const spaceId = '714-space'; + const differentSpaceConnectorId = '5272d090-b111-11ed-b56a-a7991a8d8b32'; + + const buffer = getImportRuleWithConnectorsBuffer(differentSpaceConnectorId); + const { body } = await supertest + .post(`/s/${spaceId}${DETECTION_ENGINE_RULES_URL}/_import`) + .set('kbn-xsrf', 'true') + .attach('file', buffer, 'rules.ndjson') + .expect(200); + + expect(body).to.eql({ + success: true, + success_count: 1, + rules_count: 1, + errors: [], + exceptions_errors: [], + exceptions_success: true, + exceptions_success_count: 0, + action_connectors_success: true, + action_connectors_success_count: 1, + action_connectors_warnings: [], + action_connectors_errors: [], + }); + }); + it('should import a non-default-space 7.16 rule with a connector made in the non-default space into the default space successfully', async () => { + // connectorId is from the 7.x connector here + // x-pack/test/functional/es_archives/security_solution/import_rule_connector + const differentSpaceConnectorId = '963ec960-a21a-11ed-84a4-a33e4c2558c9'; + const buffer = getImportRuleWithConnectorsBuffer(differentSpaceConnectorId); + + const { body } = await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .set('kbn-xsrf', 'true') + .attach('file', buffer, 'rules.ndjson') + .expect(200); + expect(body).to.eql({ + success: true, + success_count: 1, + rules_count: 1, + errors: [], + exceptions_errors: [], + exceptions_success: true, + exceptions_success_count: 0, + action_connectors_success: true, + action_connectors_success_count: 1, + action_connectors_warnings: [], + action_connectors_errors: [], + }); + }); + it('importing a non-default-space 7.16 rule with a connector made in the non-default space into the default space should result in a 404 if the file does not contain connectors', async () => { + // connectorId is from the 7.x connector here + // x-pack/test/functional/es_archives/security_solution/import_rule_connector + const buffer = getImportRuleBuffer(space714ActionConnectorId); + + const { body } = await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .set('kbn-xsrf', 'true') + .attach('file', buffer, 'rules.ndjson') + .expect(200); + expect(body.success).to.equal(false); + expect(body.errors[0].error.status_code).to.equal(404); + expect(body.errors[0].error.message).to.equal( + `1 connector is missing. Connector id missing is: ${space714ActionConnectorId}` + ); + }); + // When objects become share-capable we will either add / update this test + it('importing a non-default-space 7.16 rule with a connector made in the non-default space into a different non-default space should result in a 404', async () => { + const spaceId = '4567-space'; + // connectorId is from the 7.x connector here + // x-pack/test/functional/es_archives/security_solution/import_rule_connector + // it + const buffer = getImportRuleBuffer(space714ActionConnectorId); + + const { body } = await supertest + .post(`/s/${spaceId}${DETECTION_ENGINE_RULES_URL}/_import`) + .set('kbn-xsrf', 'true') + .attach('file', buffer, 'rules.ndjson') + .expect(200); + expect(body.success).to.equal(false); + expect(body.errors[0].error.status_code).to.equal(404); + expect(body.errors[0].error.message).to.equal( + `1 connector is missing. Connector id missing is: ${space714ActionConnectorId}` + ); + }); }); - it('importing a default-space 7.16 rule with a connector made in the default space into a non-default space should result in a 404', async () => { - await esArchiver.load( - 'x-pack/test/functional/es_archives/security_solution/import_rule_connector' - ); - const spaceId = '4567-space'; - // connectorId is from the 7.x connector here - // x-pack/test/functional/es_archives/security_solution/import_rule_connector - // it - const buffer = getImportRuleBuffer(defaultSpaceActionConnectorId); - - const { body } = await supertest - .post(`/s/${spaceId}${DETECTION_ENGINE_RULES_URL}/_import`) - .set('kbn-xsrf', 'true') - .attach('file', buffer, 'rules.ndjson') - .expect(200); - expect(body.success).to.equal(false); - expect(body.errors[0].error.status_code).to.equal(404); - expect(body.errors[0].error.message).to.equal( - `1 connector is missing. Connector id missing is: ${defaultSpaceActionConnectorId}` - ); + describe('should be imported into the default space', () => { + it('should import a default-space 7.16 rule with a connector made in the default space into a non-default space successfully', async () => { + await esArchiver.load( + 'x-pack/test/functional/es_archives/security_solution/import_rule_connector' + ); + const defaultSpaceConnectorId = '8fbf6d10-a21a-11ed-84a4-a33e4c2558c9'; + + const spaceId = '4567-space'; + // connectorId is from the 7.x connector here + // x-pack/test/functional/es_archives/security_solution/import_rule_connector + // it + const buffer = getImportRuleWithConnectorsBuffer(defaultSpaceConnectorId); + + const { body } = await supertest + .post(`/s/${spaceId}${DETECTION_ENGINE_RULES_URL}/_import`) + .set('kbn-xsrf', 'true') + .attach('file', buffer, 'rules.ndjson') + .expect(200); + expect(body).to.eql({ + success: true, + success_count: 1, + rules_count: 1, + errors: [], + exceptions_errors: [], + exceptions_success: true, + exceptions_success_count: 0, + action_connectors_success: true, + action_connectors_success_count: 1, + action_connectors_warnings: [], + action_connectors_errors: [], + }); + }); + // When objects become share-capable we will either add / update this test + + it('importing a default-space 7.16 rule with a connector made in the default space into the default space should result in a 200', async () => { + // connectorId is from the 7.x connector here + // x-pack/test/functional/es_archives/security_solution/import_rule_connector + // it + const buffer = getImportRuleBuffer(defaultSpaceActionConnectorId); + + const { body } = await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .set('kbn-xsrf', 'true') + .attach('file', buffer, 'rules.ndjson') + .expect(200); + expect(body.success).to.equal(true); + expect(body.success_count).to.eql(1); + expect(body.errors.length).to.eql(0); + }); + it('importing a default-space 7.16 rule with a connector made in the default space into a non-default space should result in a 404', async () => { + await esArchiver.load( + 'x-pack/test/functional/es_archives/security_solution/import_rule_connector' + ); + const spaceId = '4567-space'; + // connectorId is from the 7.x connector here + // x-pack/test/functional/es_archives/security_solution/import_rule_connector + // it + const buffer = getImportRuleBuffer(defaultSpaceActionConnectorId); + + const { body } = await supertest + .post(`/s/${spaceId}${DETECTION_ENGINE_RULES_URL}/_import`) + .set('kbn-xsrf', 'true') + .attach('file', buffer, 'rules.ndjson') + .expect(200); + expect(body.success).to.equal(false); + expect(body.errors[0].error.status_code).to.equal(404); + expect(body.errors[0].error.message).to.equal( + `1 connector is missing. Connector id missing is: ${defaultSpaceActionConnectorId}` + ); + }); }); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/machine_learning.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/machine_learning.ts index fe12454cf4591..099104ad411f7 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/machine_learning.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/machine_learning.ts @@ -64,7 +64,8 @@ export default ({ getService }: FtrProviderContext) => { rule_id: 'ml-rule-id', }; - describe('Machine learning type rules', () => { + // FLAKY: https://github.com/elastic/kibana/issues/145776 + describe.skip('Machine learning type rules', () => { before(async () => { // Order is critical here: auditbeat data must be loaded before attempting to start the ML job, // as the job looks for certain indices on start diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/non_ecs_fields.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/non_ecs_fields.ts index 3c5368b7a23ad..0bcb05b5e1c40 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/non_ecs_fields.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/non_ecs_fields.ts @@ -323,5 +323,63 @@ export default ({ getService }: FtrProviderContext) => { // invalid ECS field is getting removed expect(alertSource).not.toHaveProperty('dll.code_signature.valid'); }); + + describe('multi-fields', () => { + it('should not add multi field .text to ecs compliant nested source', async () => { + const document = { + process: { + command_line: 'string longer than 10 characters', + }, + }; + + const { errors, alertSource } = await indexAndCreatePreviewAlert(document); + + expect(errors).toEqual([]); + + expect(alertSource).toHaveProperty('process', document.process); + expect(alertSource).not.toHaveProperty('process.command_line.text'); + }); + + it('should not add multi field .text to ecs compliant flattened source', async () => { + const document = { + 'process.command_line': 'string longer than 10 characters', + }; + + const { errors, alertSource } = await indexAndCreatePreviewAlert(document); + + expect(errors).toEqual([]); + + expect(alertSource?.['process.command_line']).toEqual(document['process.command_line']); + expect(alertSource).not.toHaveProperty('process.command_line.text'); + }); + + it('should not add multi field .text to ecs non compliant nested source', async () => { + const document = { + nonEcs: { + command_line: 'string longer than 10 characters', + }, + }; + + const { errors, alertSource } = await indexAndCreatePreviewAlert(document); + + expect(errors).toEqual([]); + + expect(alertSource).toHaveProperty('nonEcs', document.nonEcs); + expect(alertSource).not.toHaveProperty('nonEcs.command_line.text'); + }); + + it('should not add multi field .text to ecs non compliant flattened source', async () => { + const document = { + 'nonEcs.command_line': 'string longer than 10 characters', + }; + + const { errors, alertSource } = await indexAndCreatePreviewAlert(document); + + expect(errors).toEqual([]); + + expect(alertSource?.['nonEcs.command_line']).toEqual(document['nonEcs.command_line']); + expect(alertSource).not.toHaveProperty('nonEcs.command_line.text'); + }); + }); }); }; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/threat_match.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/threat_match.ts index 0f92db72d3c2a..8b3e4db6fa542 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/threat_match.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/threat_match.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { get, isEqual } from 'lodash'; +import { get, isEqual, omit } from 'lodash'; import expect from '@kbn/expect'; import { ALERT_REASON, @@ -19,6 +19,7 @@ import { VERSION, } from '@kbn/rule-data-utils'; import { flattenWithPrefix } from '@kbn/securitysolution-rules'; +import { ThreatMapping } from '@kbn/securitysolution-io-ts-alerting-types'; import { ThreatMatchRuleCreateProps } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; @@ -32,6 +33,7 @@ import { ALERT_ORIGINAL_EVENT_MODULE, ALERT_ORIGINAL_TIME, } from '@kbn/security-solution-plugin/common/field_maps/field_names'; +import { RuleExecutionStatus } from '@kbn/security-solution-plugin/common/detection_engine/rule_monitoring'; import { previewRule, getOpenSignals, @@ -41,7 +43,6 @@ import { createRule, } from '../../utils'; import { FtrProviderContext } from '../../common/ftr_provider_context'; - const format = (value: unknown): string => JSON.stringify(value, null, 2); // Asserts that each expected value is included in the subject, independent of @@ -54,6 +55,85 @@ const assertContains = (subject: unknown[], expected: unknown[]) => ) ); +const createThreatMatchRule = ({ + name = 'Query with a rule id', + index = ['auditbeat-*'], + query = '*:*', + // eslint-disable-next-line @typescript-eslint/naming-convention + rule_id = 'rule-1', + // eslint-disable-next-line @typescript-eslint/naming-convention + threat_indicator_path = 'threat.indicator', + // eslint-disable-next-line @typescript-eslint/naming-convention + threat_query = 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip + // eslint-disable-next-line @typescript-eslint/naming-convention + threat_index = ['auditbeat-*'], + // eslint-disable-next-line @typescript-eslint/naming-convention + threat_mapping = [ + // We match host.name against host.name + { + entries: [ + { + field: 'host.name', + value: 'host.name', + type: 'mapping', + }, + ], + }, + ], + override = {}, +}: { + threat_mapping?: ThreatMapping; + name?: string; + query?: string; + rule_id?: string; + index?: string[]; + threat_index?: string[]; + threat_query?: string; + threat_indicator_path?: string; + override?: any; +} = {}): ThreatMatchRuleCreateProps => ({ + description: 'Detecting root and admin users', + name, + severity: 'high', + index, + type: 'threat_match', + risk_score: 55, + language: 'kuery', + rule_id, + from: '1900-01-01T00:00:00.000Z', + query, + threat_query, + threat_index, + threat_mapping, + threat_filters: [], + threat_indicator_path, + ...override, +}); + +function alertsAreTheSame(alertsA: any[], alertsB: any[]): void { + const mapAlert = (alert: any) => { + return omit(alert._source, [ + '@timestamp', + 'kibana.alert.last_detected', + 'kibana.rule.created_at', + 'kibana.rule.execution.uuid', + 'kibana.name.execution.uuid', + 'kibana.alert.rule.parameters', + 'kibana.alert.rule.rule_id', + 'kibana.alert.rule.name', + 'kibana.alert.rule.created_at', + 'kibana.alert.rule.updated_at', + 'kibana.alert.rule.uuid', + 'kibana.alert.rule.execution.uuid', + 'kibana.alert.start', + 'kibana.alert.reason', + 'kibana.alert.uuid', + ]); + }; + + expect(alertsA.map(mapAlert)).to.eql(alertsB.map(mapAlert)); +} + // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); @@ -66,6 +146,8 @@ export default ({ getService }: FtrProviderContext) => { */ describe('Threat match type rules', () => { before(async () => { + // await deleteSignalsIndex(supertest, log); + // await deleteAllAlerts(supertest, log); await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); }); @@ -76,38 +158,19 @@ export default ({ getService }: FtrProviderContext) => { }); // First 2 test creates a real rule - remaining tests use preview API - it('should be able to execute and get 10 signals when doing a specific query (terms query)', async () => { - const rule: ThreatMatchRuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: '*:*', - threat_query: 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip - threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity - threat_mapping: [ - // We match host.name against host.name - { - entries: [ - { - field: 'host.name', - value: 'host.name', - type: 'mapping', - }, - ], - }, - ], - threat_filters: [], - }; + it('should be able to execute and get all signals when doing a specific query (terms query)', async () => { + const rule: ThreatMatchRuleCreateProps = createThreatMatchRule(); const createdRule = await createRule(supertest, log, rule); - const alerts = await getOpenSignals(supertest, log, es, createdRule); - expect(alerts.hits.hits.length).equal(10); + const alerts = await getOpenSignals( + supertest, + log, + es, + createdRule, + RuleExecutionStatus.succeeded, + 100 + ); + expect(alerts.hits.hits.length).equal(88); const fullSource = alerts.hits.hits.find( (signal) => (signal._source?.[ALERT_ANCESTORS] as Ancestor[])[0].id === '7yJ-B2kBR346wHgnhlMn' @@ -257,20 +320,8 @@ export default ({ getService }: FtrProviderContext) => { }), }); }); - it('should be able to execute and get 10 signals when doing a specific query (match query)', async () => { - const rule: ThreatMatchRuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: '*:*', - threat_query: 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip - threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity + it('should be able to execute and get all signals when doing a specific query (match query)', async () => { + const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({ threat_mapping: [ // We match host.name against host.name { @@ -288,12 +339,18 @@ export default ({ getService }: FtrProviderContext) => { ], }, ], - threat_filters: [], - }; + }); const createdRule = await createRule(supertest, log, rule); - const alerts = await getOpenSignals(supertest, log, es, createdRule); - expect(alerts.hits.hits.length).equal(10); + const alerts = await getOpenSignals( + supertest, + log, + es, + createdRule, + RuleExecutionStatus.succeeded, + 100 + ); + expect(alerts.hits.hits.length).equal(88); const fullSource = alerts.hits.hits.find( (signal) => (signal._source?.[ALERT_ANCESTORS] as Ancestor[])[0].id === '7yJ-B2kBR346wHgnhlMn' @@ -444,20 +501,64 @@ export default ({ getService }: FtrProviderContext) => { }); }); + it('terms and match should have the same alerts with pagination', async () => { + const termRule: ThreatMatchRuleCreateProps = createThreatMatchRule({ + override: { + items_per_search: 1, + concurrent_searches: 1, + }, + }); + + const matchRule: ThreatMatchRuleCreateProps = createThreatMatchRule({ + override: { + items_per_search: 1, + concurrent_searches: 1, + }, + name: 'Math rule', + rule_id: 'rule-2', + threat_mapping: [ + // We match host.name against host.name + { + entries: [ + { + field: 'host.name', + value: 'host.name', + type: 'mapping', + }, + { + field: 'host.name', + value: 'host.name', + type: 'mapping', + }, + ], + }, + ], + }); + + const createdRuleTerm = await createRule(supertest, log, termRule); + const createdRuleMatch = await createRule(supertest, log, matchRule); + const alertsTerm = await getOpenSignals( + supertest, + log, + es, + createdRuleTerm, + RuleExecutionStatus.succeeded, + 100 + ); + const alertsMatch = await getOpenSignals( + supertest, + log, + es, + createdRuleMatch, + RuleExecutionStatus.succeeded, + 100 + ); + + alertsAreTheSame(alertsTerm.hits.hits, alertsMatch.hits.hits); + }); + it('should return 0 matches if the mapping does not match against anything in the mapping', async () => { - const rule: ThreatMatchRuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: '*:*', - threat_query: 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip - threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity + const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({ threat_mapping: [ // We match host.name against host.name { @@ -470,8 +571,7 @@ export default ({ getService }: FtrProviderContext) => { ], }, ], - threat_filters: [], - }; + }); const { previewId } = await previewRule({ supertest, rule }); const previewAlerts = await getPreviewAlerts({ es, previewId }); @@ -479,19 +579,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should return 0 signals when using an AND and one of the clauses does not have data', async () => { - const rule: ThreatMatchRuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: '*:*', - threat_query: 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip - threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity + const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({ threat_mapping: [ { entries: [ @@ -508,8 +596,7 @@ export default ({ getService }: FtrProviderContext) => { ], }, ], - threat_filters: [], - }; + }); const { previewId } = await previewRule({ supertest, rule }); const previewAlerts = await getPreviewAlerts({ es, previewId }); @@ -517,19 +604,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should return 0 signals when using an AND and one of the clauses has a made up value that does not exist', async () => { - const rule: ThreatMatchRuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - type: 'threat_match', - index: ['auditbeat-*'], - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: '*:*', - threat_query: 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip - threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity + const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({ threat_mapping: [ { entries: [ @@ -546,8 +621,7 @@ export default ({ getService }: FtrProviderContext) => { ], }, ], - threat_filters: [], - }; + }); const { previewId } = await previewRule({ supertest, rule }); const previewAlerts = await getPreviewAlerts({ es, previewId }); @@ -557,35 +631,13 @@ export default ({ getService }: FtrProviderContext) => { describe('timeout behavior', () => { // TODO: unskip this and see if we can make it not flaky it.skip('will return an error if a rule execution exceeds the rule interval', async () => { - const rule: ThreatMatchRuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a short interval', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: '*:*', - threat_query: '*:*', // broad query to take more time - threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity - threat_mapping: [ - { - entries: [ - { - field: 'host.name', - value: 'host.name', - type: 'mapping', - }, - ], - }, - ], - threat_filters: [], - concurrent_searches: 1, - interval: '1s', // short interval - items_per_search: 1, // iterate only 1 threat item per loop to ensure we're slow - }; + const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({ + override: { + concurrent_searches: 1, + interval: '1s', // short interval + items_per_search: 1, // iterate only 1 threat item per loop to ensure we're slow + }, + }); const { logs } = await previewRule({ supertest, rule }); expect(logs[0].errors[0]).to.contain('execution has exceeded its allotted interval'); @@ -602,20 +654,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('enriches signals with the single indicator that matched', async () => { - const rule: ThreatMatchRuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: '*:*', // narrow events down to 2 with a destination.ip - threat_indicator_path: 'threat.indicator', - threat_query: 'threat.indicator.domain: 159.89.119.67', // narrow things down to indicators with a domain - threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module + const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({ threat_mapping: [ { entries: [ @@ -627,8 +666,9 @@ export default ({ getService }: FtrProviderContext) => { ], }, ], - threat_filters: [], - }; + threat_query: 'threat.indicator.domain: 159.89.119.67', // narrow things down to indicators with a domain + threat_index: ['filebeat-*'], + }); const { previewId } = await previewRule({ supertest, rule }); const previewAlerts = await getPreviewAlerts({ es, previewId }); @@ -690,18 +730,8 @@ export default ({ getService }: FtrProviderContext) => { }); it('enriches signals with multiple indicators if several matched', async () => { - const rule: ThreatMatchRuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', + const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({ query: 'NOT source.port:35326', // specify query to have signals more than treat indicators, but only 1 will match - threat_indicator_path: 'threat.indicator', threat_query: 'threat.indicator.ip: *', threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module threat_mapping: [ @@ -715,9 +745,7 @@ export default ({ getService }: FtrProviderContext) => { ], }, ], - threat_filters: [], - }; - + }); const { previewId } = await previewRule({ supertest, rule }); const previewAlerts = await getPreviewAlerts({ es, previewId }); expect(previewAlerts.length).equal(1); @@ -767,18 +795,8 @@ export default ({ getService }: FtrProviderContext) => { }); it('adds a single indicator that matched multiple fields', async () => { - const rule: ThreatMatchRuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', + const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({ query: 'NOT source.port:35326', // specify query to have signals more than treat indicators, but only 1 will match - threat_indicator_path: 'threat.indicator', threat_query: 'threat.indicator.port: 57324 or threat.indicator.ip:45.115.45.3', // narrow our query to a single indicator threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module threat_mapping: [ @@ -801,8 +819,7 @@ export default ({ getService }: FtrProviderContext) => { ], }, ], - threat_filters: [], - }; + }); const { previewId } = await previewRule({ supertest, rule }); const previewAlerts = await getPreviewAlerts({ es, previewId }); @@ -876,19 +893,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('generates multiple signals with multiple matches', async () => { - const rule: ThreatMatchRuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - threat_language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: '*:*', // narrow our query to a single record that matches two indicators - threat_indicator_path: 'threat.indicator', + const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({ threat_query: '*:*', threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module threat_mapping: [ @@ -916,8 +921,7 @@ export default ({ getService }: FtrProviderContext) => { ], }, ], - threat_filters: [], - }; + }); const { previewId } = await previewRule({ supertest, rule }); const previewAlerts = await getPreviewAlerts({ es, previewId }); @@ -1023,18 +1027,8 @@ export default ({ getService }: FtrProviderContext) => { }); it('enriches signals with the single indicator that matched', async () => { - const rule: ThreatMatchRuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', + const termRule: ThreatMatchRuleCreateProps = createThreatMatchRule({ query: 'destination.ip:159.89.119.67', - threat_indicator_path: 'threat.indicator', threat_query: 'threat.indicator.domain: *', // narrow things down to indicators with a domain threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module threat_mapping: [ @@ -1048,14 +1042,36 @@ export default ({ getService }: FtrProviderContext) => { ], }, ], - threat_filters: [], - }; + }); + const matchRule: ThreatMatchRuleCreateProps = createThreatMatchRule({ + query: 'destination.ip:159.89.119.67', + threat_query: 'threat.indicator.domain: *', // narrow things down to indicators with a domain + threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module + threat_mapping: [ + { + entries: [ + { + value: 'threat.indicator.domain', + field: 'destination.ip', + type: 'mapping', + }, + { + value: 'threat.indicator.domain', + field: 'destination.ip', + type: 'mapping', + }, + ], + }, + ], + }); - const { previewId } = await previewRule({ supertest, rule }); - const previewAlerts = await getPreviewAlerts({ es, previewId }); - expect(previewAlerts.length).equal(2); + const { previewId: termPrevieId } = await previewRule({ supertest, rule: termRule }); + const termPreviewAlerts = await getPreviewAlerts({ es, previewId: termPrevieId }); + const { previewId: matchPreviewId } = await previewRule({ supertest, rule: matchRule }); + const matchPrevieAlerts = await getPreviewAlerts({ es, previewId: matchPreviewId }); + expect(termPreviewAlerts.length).equal(2); - const threats = previewAlerts.map((hit) => hit._source?.threat); + const threats = termPreviewAlerts.map((hit) => hit._source?.threat); expect(threats).to.eql([ { enrichments: [ @@ -1108,21 +1124,12 @@ export default ({ getService }: FtrProviderContext) => { ], }, ]); + alertsAreTheSame(termPreviewAlerts, matchPrevieAlerts); }); it('enriches signals with multiple indicators if several matched', async () => { - const rule: ThreatMatchRuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: 'source.port: 57324', // narrow our query to a single record that matches two indicators - threat_indicator_path: 'threat.indicator', + const termRule: ThreatMatchRuleCreateProps = createThreatMatchRule({ + query: 'source.port: 57324', // narrow our query to a single record that matches two indicatorsthreat_query: 'threat.indicator.ip: *', threat_query: 'threat.indicator.ip: *', threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module threat_mapping: [ @@ -1136,14 +1143,36 @@ export default ({ getService }: FtrProviderContext) => { ], }, ], - threat_filters: [], - }; + }); + const matchRule: ThreatMatchRuleCreateProps = createThreatMatchRule({ + query: 'source.port: 57324', // narrow our query to a single record that matches two indicatorsthreat_query: 'threat.indicator.ip: *', + threat_query: 'threat.indicator.ip: *', + threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module + threat_mapping: [ + { + entries: [ + { + value: 'threat.indicator.ip', + field: 'source.ip', + type: 'mapping', + }, + { + value: 'threat.indicator.ip', + field: 'source.ip', + type: 'mapping', + }, + ], + }, + ], + }); - const { previewId } = await previewRule({ supertest, rule }); - const previewAlerts = await getPreviewAlerts({ es, previewId }); - expect(previewAlerts.length).equal(1); + const { previewId: termPrevieId } = await previewRule({ supertest, rule: termRule }); + const termPreviewAlerts = await getPreviewAlerts({ es, previewId: termPrevieId }); + const { previewId: matchPreviewId } = await previewRule({ supertest, rule: matchRule }); + const matchPrevieAlerts = await getPreviewAlerts({ es, previewId: matchPreviewId }); + expect(termPreviewAlerts.length).equal(1); - const [threat] = previewAlerts.map((hit) => hit._source?.threat) as Array<{ + const [threat] = termPreviewAlerts.map((hit) => hit._source?.threat) as Array<{ enrichments: unknown[]; }>; @@ -1185,21 +1214,12 @@ export default ({ getService }: FtrProviderContext) => { }, }, ]); + alertsAreTheSame(termPreviewAlerts, matchPrevieAlerts); }); it('adds a single indicator that matched multiple fields', async () => { - const rule: ThreatMatchRuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', + const termRule: ThreatMatchRuleCreateProps = createThreatMatchRule({ query: 'source.port: 57324', // narrow our query to a single record that matches two indicators - threat_indicator_path: 'threat.indicator', threat_query: 'threat.indicator.ip: *', threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module threat_mapping: [ @@ -1222,18 +1242,57 @@ export default ({ getService }: FtrProviderContext) => { ], }, ], - threat_filters: [], - }; + }); + const matchRule: ThreatMatchRuleCreateProps = createThreatMatchRule({ + query: 'source.port: 57324', // narrow our query to a single record that matches two indicators + threat_query: 'threat.indicator.ip: *', + threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module + threat_mapping: [ + { + entries: [ + { + value: 'threat.indicator.port', + field: 'source.port', + type: 'mapping', + }, + { + value: 'threat.indicator.port', + field: 'source.port', + type: 'mapping', + }, + ], + }, + { + entries: [ + { + value: 'threat.indicator.ip', + field: 'source.ip', + type: 'mapping', + }, + { + value: 'threat.indicator.ip', + field: 'source.ip', + type: 'mapping', + }, + ], + }, + ], + }); - const { previewId } = await previewRule({ supertest, rule }); - const previewAlerts = await getPreviewAlerts({ es, previewId }); - expect(previewAlerts.length).equal(1); + const { previewId: termPrevieId } = await previewRule({ supertest, rule: termRule }); + const termPreviewAlerts = await getPreviewAlerts({ es, previewId: termPrevieId }); + const { previewId: matchPreviewId } = await previewRule({ supertest, rule: matchRule }); + const matchPrevieAlerts = await getPreviewAlerts({ es, previewId: matchPreviewId }); + expect(termPreviewAlerts.length).equal(1); - const [threat] = previewAlerts.map((hit) => hit._source?.threat) as Array<{ + const [threatTerm] = termPreviewAlerts.map((hit) => hit._source?.threat) as Array<{ enrichments: unknown[]; }>; - assertContains(threat.enrichments, [ + const [threatMatch] = matchPrevieAlerts.map((hit) => hit._source?.threat) as Array<{ + enrichments: unknown[]; + }>; + assertContains(threatTerm.enrichments, [ { feed: {}, indicator: { @@ -1294,22 +1353,23 @@ export default ({ getService }: FtrProviderContext) => { }, }, ]); + const sortEnrichments = (a: any, b: any) => { + const atomicA = a.matched.atomic.toString(); + const atomicB = b.matched.atomic.toString(); + if (atomicA === atomicB) { + return a.indicator.description > b.indicator.description ? 1 : -1; + } + return atomicA > atomicB ? 1 : -1; + }; + + expect(threatTerm.enrichments.sort(sortEnrichments)).to.be.eql( + threatMatch.enrichments.sort(sortEnrichments) + ); }); it('generates multiple signals with multiple matches', async () => { - const rule: ThreatMatchRuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - threat_language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', + const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({ query: '(source.port:57324 and source.ip:45.115.45.3) or destination.ip:159.89.119.67', // narrow our query to a single record that matches two indicators - threat_indicator_path: 'threat.indicator', threat_query: '*:*', threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module threat_mapping: [ @@ -1337,8 +1397,7 @@ export default ({ getService }: FtrProviderContext) => { ], }, ], - threat_filters: [], - }; + }); const { previewId } = await previewRule({ supertest, rule }); const previewAlerts = await getPreviewAlerts({ es, previewId }); @@ -1444,19 +1503,9 @@ export default ({ getService }: FtrProviderContext) => { }); it('should be enriched with host risk score', async () => { - const rule: ThreatMatchRuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', + const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({ query: '*:*', threat_query: 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip - threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity threat_mapping: [ // We match host.name against host.name { @@ -1469,8 +1518,7 @@ export default ({ getService }: FtrProviderContext) => { ], }, ], - threat_filters: [], - }; + }); const { previewId } = await previewRule({ supertest, rule }); const previewAlerts = await getPreviewAlerts({ es, previewId, size: 100 }); diff --git a/x-pack/test/fleet_api_integration/apis/agents/reassign.ts b/x-pack/test/fleet_api_integration/apis/agents/reassign.ts index 2dd546511dd9a..571a0fee11f54 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/reassign.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/reassign.ts @@ -37,7 +37,7 @@ export default function (providerContext: FtrProviderContext) { describe('reassign single agent', () => { it('should allow to reassign single agent', async () => { await supertest - .put(`/api/fleet/agents/agent1/reassign`) + .post(`/api/fleet/agents/agent1/reassign`) .set('kbn-xsrf', 'xxx') .send({ policy_id: 'policy2', @@ -49,7 +49,7 @@ export default function (providerContext: FtrProviderContext) { it('should throw an error for invalid policy id for single reassign', async () => { await supertest - .put(`/api/fleet/agents/agent1/reassign`) + .post(`/api/fleet/agents/agent1/reassign`) .set('kbn-xsrf', 'xxx') .send({ policy_id: 'INVALID_ID', @@ -61,7 +61,7 @@ export default function (providerContext: FtrProviderContext) { // policy2 is not hosted // reassign succeeds await supertest - .put(`/api/fleet/agents/agent1/reassign`) + .post(`/api/fleet/agents/agent1/reassign`) .set('kbn-xsrf', 'xxx') .send({ policy_id: 'policy2', @@ -79,7 +79,7 @@ export default function (providerContext: FtrProviderContext) { // reassign fails await supertest - .put(`/api/fleet/agents/agent1/reassign`) + .post(`/api/fleet/agents/agent1/reassign`) .set('kbn-xsrf', 'xxx') .send({ policy_id: 'policy2', diff --git a/x-pack/test/functional/apps/dashboard/group2/dashboard_maps_by_value.ts b/x-pack/test/functional/apps/dashboard/group2/dashboard_maps_by_value.ts index 949aaa65008c5..9a65bd0f2f95f 100644 --- a/x-pack/test/functional/apps/dashboard/group2/dashboard_maps_by_value.ts +++ b/x-pack/test/functional/apps/dashboard/group2/dashboard_maps_by_value.ts @@ -76,7 +76,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.dashboard.clickNewDashboard(); } - describe('dashboard maps by value', function () { + // Failing: See https://github.com/elastic/kibana/issues/152476 + describe.skip('dashboard maps by value', function () { before(async () => { await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional'); await kibanaServer.importExport.load( diff --git a/x-pack/test/functional/apps/infra/constants.ts b/x-pack/test/functional/apps/infra/constants.ts index 248c4f49a16c7..b1739f9a489d7 100644 --- a/x-pack/test/functional/apps/infra/constants.ts +++ b/x-pack/test/functional/apps/infra/constants.ts @@ -42,3 +42,5 @@ export const ML_JOB_IDS = [ ]; export const HOSTS_LINK_LOCAL_STORAGE_KEY = 'inventoryUI:hostsLinkClicked'; + +export const HOSTS_VIEW_PATH = 'metrics/hosts'; diff --git a/x-pack/test/functional/apps/infra/hosts_view.ts b/x-pack/test/functional/apps/infra/hosts_view.ts index 4f3166c80afb9..1f1773505ffef 100644 --- a/x-pack/test/functional/apps/infra/hosts_view.ts +++ b/x-pack/test/functional/apps/infra/hosts_view.ts @@ -6,9 +6,11 @@ */ import expect from '@kbn/expect'; +import { enableInfrastructureHostsView } from '@kbn/observability-plugin/common'; +import { ALERT_STATUS_ACTIVE, ALERT_STATUS_RECOVERED } from '@kbn/rule-data-utils'; import moment from 'moment'; import { FtrProviderContext } from '../../ftr_provider_context'; -import { DATES, HOSTS_LINK_LOCAL_STORAGE_KEY } from './constants'; +import { DATES, HOSTS_LINK_LOCAL_STORAGE_KEY, HOSTS_VIEW_PATH } from './constants'; const START_DATE = moment.utc(DATES.metricsAndLogs.hosts.min); const END_DATE = moment.utc(DATES.metricsAndLogs.hosts.max); @@ -18,6 +20,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const kibanaServer = getService('kibanaServer'); const esArchiver = getService('esArchiver'); const browser = getService('browser'); + const find = getService('find'); const security = getService('security'); const pageObjects = getPageObjects([ 'common', @@ -29,9 +32,11 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { ]); // Helpers + const setHostViewEnabled = (value: boolean = true) => + kibanaServer.uiSettings.update({ [enableInfrastructureHostsView]: value }); - const loginWithReadOnlyUserAndNavigateToInfra = async () => { - await security.role.create('global_hosts_read_privileges_role', { + const loginWithReadOnlyUser = async () => { + const roleCreation = security.role.create('global_hosts_read_privileges_role', { elasticsearch: { indices: [{ names: ['metricbeat-*'], privileges: ['read', 'view_index_metadata'] }], }, @@ -46,13 +51,15 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { ], }); - await security.user.create('global_hosts_read_privileges_user', { + const userCreation = security.user.create('global_hosts_read_privileges_user', { password: 'global_hosts_read_privileges_user-password', roles: ['global_hosts_read_privileges_role'], full_name: 'test user', }); - await pageObjects.security.forceLogout(); + const logout = pageObjects.security.forceLogout(); + + await Promise.all([roleCreation, userCreation, logout]); await pageObjects.security.login( 'global_hosts_read_privileges_user', @@ -61,87 +68,55 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { expectSpaceSelector: false, } ); - - await browser.removeLocalStorageItem(HOSTS_LINK_LOCAL_STORAGE_KEY); - await pageObjects.common.navigateToApp('infraOps'); }; - const logoutAndDeleteReadOnlyUser = async () => { - await pageObjects.security.forceLogout(); - await Promise.all([ + const logoutAndDeleteReadOnlyUser = () => + Promise.all([ + pageObjects.security.forceLogout(), security.role.delete('global_hosts_read_privileges_role'), security.user.delete('global_hosts_read_privileges_user'), ]); - }; - const navigateAndDisableHostView = async () => { - await esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs'); - await browser.removeLocalStorageItem(HOSTS_LINK_LOCAL_STORAGE_KEY); - await pageObjects.common.navigateToApp('infraOps'); - await pageObjects.common.navigateToUrl('management', 'kibana/settings', { - basePath: `/s/default`, - ensureCurrentUrl: false, - shouldLoginIfPrompted: false, - shouldUseHashForSubUrl: false, - }); - await pageObjects.settings.toggleAdvancedSettingCheckbox( - 'observability:enableInfrastructureHostsView', - false - ); - return esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs'); - }; - - const navigateAndEnableHostView = async () => { - await esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs'); - await browser.removeLocalStorageItem(HOSTS_LINK_LOCAL_STORAGE_KEY); - await pageObjects.common.navigateToApp('infraOps'); - await pageObjects.infraHome.clickDismissKubernetesTourButton(); - await pageObjects.infraHostsView.clickTryHostViewLink(); - await pageObjects.infraHostsView.clickEnableHostViewButton(); - }; + const enableHostView = () => pageObjects.infraHostsView.clickEnableHostViewButton(); // Tests - describe('Hosts view', function () { + describe('Hosts View', function () { this.tags('includeFirefox'); + before(async () => { - await kibanaServer.savedObjects.cleanStandardList(); + await Promise.all([ + esArchiver.load('x-pack/test/functional/es_archives/infra/alerts'), + esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs'), + kibanaServer.savedObjects.cleanStandardList(), + ]); }); - describe('shows hosts view landing page for admin', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs'); - await pageObjects.common.navigateToApp('infraOps'); - await pageObjects.infraHome.clickDismissKubernetesTourButton(); - await pageObjects.infraHostsView.clickTryHostViewBadge(); - }); - after(async () => { - await browser.removeLocalStorageItem(HOSTS_LINK_LOCAL_STORAGE_KEY); - return esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs'); - }); + after(() => { + esArchiver.unload('x-pack/test/functional/es_archives/infra/alerts'); + esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs'); + browser.removeLocalStorageItem(HOSTS_LINK_LOCAL_STORAGE_KEY); + }); - it('should show hosts landing page with enable button when the hosts view is disabled', async () => { - const landingPageEnableButton = - await pageObjects.infraHostsView.getHostsLandingPageEnableButton(); - const landingPageEnableButtonText = await landingPageEnableButton.getVisibleText(); - expect(landingPageEnableButtonText).to.eql('Enable hosts view'); - }); + it('should be accessible from the Inventory page', async () => { + await pageObjects.common.navigateToApp('infraOps'); + await pageObjects.infraHome.clickDismissKubernetesTourButton(); + await pageObjects.infraHostsView.clickTryHostViewBadge(); + + const pageUrl = await browser.getCurrentUrl(); + + expect(pageUrl).to.contain(HOSTS_VIEW_PATH); }); - describe('should show hosts view landing page for user with read permission', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs'); - await loginWithReadOnlyUserAndNavigateToInfra(); - await pageObjects.infraHome.clickDismissKubernetesTourButton(); - await pageObjects.infraHostsView.clickTryHostViewBadge(); - }); - after(async () => { - // NOTE: Logout needs to happen before anything else to avoid flaky behavior - await logoutAndDeleteReadOnlyUser(); - return esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs'); + describe('#Landing page', () => { + beforeEach(() => { + setHostViewEnabled(false); }); - it('should show hosts landing page with callout when the hosts view is disabled', async () => { + it('as a user with read permission, should show hosts landing page with callout when the hosts view is disabled', async () => { + await loginWithReadOnlyUser(); + await pageObjects.common.navigateToApp(HOSTS_VIEW_PATH); + const landingPageDisabled = await pageObjects.infraHostsView.getHostsLandingPageDisabled(); const learnMoreDocsUrl = await pageObjects.infraHostsView.getHostsLandingPageDocsLink(); const parsedUrl = new URL(learnMoreDocsUrl); @@ -151,33 +126,57 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { expect(landingPageDisabled).to.contain( 'Your user role doesn’t have sufficient privileges to enable this feature' ); + + await logoutAndDeleteReadOnlyUser(); + }); + + it('as an admin, should see an enable button when the hosts view is disabled', async () => { + await pageObjects.common.navigateToApp(HOSTS_VIEW_PATH); + + const landingPageEnableButton = + await pageObjects.infraHostsView.getHostsLandingPageEnableButton(); + const landingPageEnableButtonText = await landingPageEnableButton.getVisibleText(); + expect(landingPageEnableButtonText).to.eql('Enable hosts view'); + }); + + it('as an admin, should be able to enable the hosts view feature', async () => { + await pageObjects.common.navigateToApp(HOSTS_VIEW_PATH); + await enableHostView(); + + const titleElement = await find.byCssSelector('h1'); + const title = await titleElement.getVisibleText(); + + expect(title).to.contain('Hosts'); }); }); - describe('enables hosts view page and checks content', () => { + describe('#Page Content', () => { before(async () => { - await navigateAndEnableHostView(); + await setHostViewEnabled(true); + await loginWithReadOnlyUser(); + await pageObjects.common.navigateToApp(HOSTS_VIEW_PATH); await pageObjects.timePicker.setAbsoluteRange( START_DATE.format(timepickerFormat), END_DATE.format(timepickerFormat) ); }); + after(async () => { - await navigateAndDisableHostView(); + await logoutAndDeleteReadOnlyUser(); }); - describe('should show hosts page for admin user and see the page content', async () => { - it('should render the correct page title', async () => { - const documentTitle = await browser.getTitle(); - expect(documentTitle).to.contain('Hosts - Infrastructure - Observability - Elastic'); - }); + it('should render the correct page title', async () => { + const documentTitle = await browser.getTitle(); + expect(documentTitle).to.contain('Hosts - Infrastructure - Observability - Elastic'); + }); - it('should have six hosts', async () => { - const hosts = await pageObjects.infraHostsView.getHostsTableData(); - expect(hosts.length).to.equal(6); - }); + it('should render a table with 6 hosts', async () => { + const hosts = await pageObjects.infraHostsView.getHostsTableData(); + expect(hosts.length).to.equal(6); + }); - it('should load 5 metrics trend tiles', async () => { + describe('KPI tiles', () => { + it('should render 5 metrics trend tiles', async () => { const hosts = await pageObjects.infraHostsView.getAllMetricsTrendTiles(); expect(hosts.length).to.equal(5); }); @@ -195,56 +194,80 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); }); }); - }); - describe('should show hosts page for read only user and see the page content', async () => { - before(async () => { - await navigateAndEnableHostView(); - await loginWithReadOnlyUserAndNavigateToInfra(); - await browser.removeLocalStorageItem(HOSTS_LINK_LOCAL_STORAGE_KEY); - await pageObjects.infraHostsView.clickTryHostViewLink(); - await pageObjects.timePicker.setAbsoluteRange( - START_DATE.format(timepickerFormat), - END_DATE.format(timepickerFormat) - ); - }); - after(async () => { - // NOTE: Logout needs to happen before anything else to avoid flaky behavior - await logoutAndDeleteReadOnlyUser(); - await navigateAndDisableHostView(); - }); + describe('Metrics Tab', () => { + it('should load 8 lens metric charts', async () => { + const metricCharts = await pageObjects.infraHostsView.getAllMetricsCharts(); + expect(metricCharts.length).to.equal(8); + }); - it('should have six hosts', async () => { - const hosts = await pageObjects.infraHostsView.getHostsTableData(); - expect(hosts.length).to.equal(6); + it('should have an option to open the chart in lens', async () => { + await pageObjects.infraHostsView.getOpenInLensOption(); + }); }); - it('should load 5 metrics trend tiles', async () => { - const hosts = await pageObjects.infraHostsView.getAllMetricsTrendTiles(); - expect(hosts.length).to.equal(5); - }); + describe('Alerts Tab', () => { + const observability = getService('observability'); + const testSubjects = getService('testSubjects'); + const retry = getService('retry'); + + const ACTIVE_ALERTS = 6; + const RECOVERED_ALERTS = 4; + const ALL_ALERTS = ACTIVE_ALERTS + RECOVERED_ALERTS; + const COLUMNS = 5; - [ - { metric: 'hosts', value: '6' }, - { metric: 'cpu', value: '0.8%' }, - { metric: 'memory', value: '16.8%' }, - { metric: 'tx', value: '0 bit/s' }, - { metric: 'rx', value: '0 bit/s' }, - ].forEach(({ metric, value }) => { - it(`${metric} tile should show ${value}`, async () => { - const tileValue = await pageObjects.infraHostsView.getMetricsTrendTileValue(metric); - expect(tileValue).to.eql(value); + before(async () => { + await pageObjects.infraHostsView.visitAlertTab(); }); - }); - describe('Lens charts', () => { - it('should load 8 lens metric charts', async () => { - const metricCharts = await pageObjects.infraHostsView.getAllMetricsCharts(); - expect(metricCharts.length).to.equal(8); + it('should correctly load the Alerts tab section when clicking on it', async () => { + testSubjects.existOrFail('hostsView-alerts'); }); - it('should have an option to open the chart in lens', async () => { - await pageObjects.infraHostsView.getOpenInLensOption(); + it('should correctly render a badge with the active alerts count', async () => { + const alertsCountBadge = await pageObjects.infraHostsView.getAlertsTabCountBadge(); + const alertsCount = await alertsCountBadge.getVisibleText(); + + expect(alertsCount).to.be('6'); + }); + + describe('#FilterButtonGroup', () => { + it('can be filtered to only show "active" alerts using the filter button', async () => { + await pageObjects.infraHostsView.setAlertStatusFilter(ALERT_STATUS_ACTIVE); + await retry.try(async () => { + const tableRows = await observability.alerts.common.getTableCellsInRows(); + expect(tableRows.length).to.be(ACTIVE_ALERTS); + }); + }); + + it('can be filtered to only show "recovered" alerts using the filter button', async () => { + await pageObjects.infraHostsView.setAlertStatusFilter(ALERT_STATUS_RECOVERED); + await retry.try(async () => { + const tableRows = await observability.alerts.common.getTableCellsInRows(); + expect(tableRows.length).to.be(RECOVERED_ALERTS); + }); + }); + + it('can be filtered to only show "all" alerts using the filter button', async () => { + await pageObjects.infraHostsView.setAlertStatusFilter(); + await retry.try(async () => { + const tableRows = await observability.alerts.common.getTableCellsInRows(); + expect(tableRows.length).to.be(ALL_ALERTS); + }); + }); + }); + + describe('#AlertsTable', () => { + it('should correctly render', async () => { + await observability.alerts.common.getTableOrFail(); + }); + + it('should renders the correct number of cells', async () => { + await retry.try(async () => { + const cells = await observability.alerts.common.getTableCells(); + expect(cells.length).to.be(ALL_ALERTS * COLUMNS); + }); + }); }); }); }); diff --git a/x-pack/test/functional/es_archives/infra/alerts/data.json.gz b/x-pack/test/functional/es_archives/infra/alerts/data.json.gz new file mode 100644 index 0000000000000..5410bb6abe5b0 Binary files /dev/null and b/x-pack/test/functional/es_archives/infra/alerts/data.json.gz differ diff --git a/x-pack/test/functional/es_archives/infra/alerts/mappings.json b/x-pack/test/functional/es_archives/infra/alerts/mappings.json new file mode 100644 index 0000000000000..89ab07bc009f3 --- /dev/null +++ b/x-pack/test/functional/es_archives/infra/alerts/mappings.json @@ -0,0 +1,776 @@ +{ + "type": "index", + "value": { + "aliases": { + ".alerts-observability.apm.alerts-default": { + "is_write_index": true + } + }, + "index": ".internal.alerts-observability.apm.alerts-default-000001", + "mappings": { + "_meta": { + "kibana": { + "version": "8.0.0" + }, + "namespace": "default" + }, + "dynamic": "false", + "properties": { + "@timestamp": { + "type": "date" + }, + "host": { + "properties": { + "name": { + "type": "keyword" + } + } + }, + "ecs": { + "properties": { + "version": { + "type": "keyword" + } + } + }, + "event": { + "properties": { + "action": { + "type": "keyword" + }, + "kind": { + "type": "keyword" + } + } + }, + "kibana": { + "properties": { + "alert": { + "properties": { + "action_group": { + "type": "keyword" + }, + "duration": { + "properties": { + "us": { + "type": "long" + } + } + }, + "end": { + "type": "date" + }, + "evaluation": { + "properties": { + "threshold": { + "scaling_factor": 100, + "type": "scaled_float" + }, + "value": { + "scaling_factor": 100, + "type": "scaled_float" + } + } + }, + "instance": { + "properties": { + "id": { + "type": "keyword" + } + } + }, + "reason": { + "type": "keyword" + }, + "rule": { + "properties": { + "author": { + "type": "keyword" + }, + "category": { + "type": "keyword" + }, + "consumer": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "enabled": { + "type": "keyword" + }, + "from": { + "type": "keyword" + }, + "interval": { + "type": "keyword" + }, + "license": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "note": { + "type": "keyword" + }, + "params": { + "index": false, + "type": "keyword" + }, + "producer": { + "type": "keyword" + }, + "references": { + "type": "keyword" + }, + "risk_score": { + "type": "float" + }, + "risk_score_mapping": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "value": { + "type": "keyword" + } + } + }, + "rule_id": { + "type": "keyword" + }, + "rule_name_override": { + "type": "keyword" + }, + "rule_type_id": { + "type": "keyword" + }, + "severity": { + "type": "keyword" + }, + "severity_mapping": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "severity": { + "type": "keyword" + }, + "value": { + "type": "keyword" + } + } + }, + "tags": { + "type": "keyword" + }, + "to": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + }, + "uuid": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "severity": { + "type": "keyword" + }, + "start": { + "type": "date" + }, + "status": { + "type": "keyword" + }, + "system_status": { + "type": "keyword" + }, + "uuid": { + "type": "keyword" + }, + "workflow_reason": { + "type": "keyword" + }, + "workflow_status": { + "type": "keyword" + }, + "workflow_user": { + "type": "keyword" + } + } + }, + "space_ids": { + "type": "keyword" + }, + "version": { + "type": "version" + } + } + }, + "processor": { + "properties": { + "event": { + "type": "keyword" + } + } + }, + "service": { + "properties": { + "environment": { + "type": "keyword" + }, + "name": { + "type": "keyword" + } + } + }, + "tags": { + "type": "keyword" + }, + "transaction": { + "properties": { + "type": { + "type": "keyword" + } + } + } + } + }, + "settings": { + "index": { + "lifecycle": { + "name": ".alerts-ilm-policy", + "rollover_alias": ".alerts-observability.apm.alerts-default" + }, + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} + +{ + "type": "index", + "value": { + "aliases": { + ".alerts-observability.logs.alerts-default": { + "is_write_index": true + } + }, + "index": ".internal.alerts-observability.logs.alerts-default-000001", + "mappings": { + "_meta": { + "kibana": { + "version": "8.0.0" + }, + "namespace": "default" + }, + "dynamic": "false", + "properties": { + "@timestamp": { + "type": "date" + }, + "host": { + "properties": { + "name": { + "type": "keyword" + } + } + }, + "ecs": { + "properties": { + "version": { + "type": "keyword" + } + } + }, + "event": { + "properties": { + "action": { + "type": "keyword" + }, + "kind": { + "type": "keyword" + } + } + }, + "kibana": { + "properties": { + "alert": { + "properties": { + "action_group": { + "type": "keyword" + }, + "duration": { + "properties": { + "us": { + "type": "long" + } + } + }, + "end": { + "type": "date" + }, + "evaluation": { + "properties": { + "threshold": { + "scaling_factor": 100, + "type": "scaled_float" + }, + "value": { + "scaling_factor": 100, + "type": "scaled_float" + } + } + }, + "instance": { + "properties": { + "id": { + "type": "keyword" + } + } + }, + "reason": { + "type": "keyword" + }, + "rule": { + "properties": { + "author": { + "type": "keyword" + }, + "category": { + "type": "keyword" + }, + "consumer": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "enabled": { + "type": "keyword" + }, + "from": { + "type": "keyword" + }, + "interval": { + "type": "keyword" + }, + "license": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "note": { + "type": "keyword" + }, + "params": { + "index": false, + "type": "keyword" + }, + "producer": { + "type": "keyword" + }, + "references": { + "type": "keyword" + }, + "risk_score": { + "type": "float" + }, + "risk_score_mapping": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "value": { + "type": "keyword" + } + } + }, + "rule_id": { + "type": "keyword" + }, + "rule_name_override": { + "type": "keyword" + }, + "rule_type_id": { + "type": "keyword" + }, + "severity": { + "type": "keyword" + }, + "severity_mapping": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "severity": { + "type": "keyword" + }, + "value": { + "type": "keyword" + } + } + }, + "tags": { + "type": "keyword" + }, + "to": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + }, + "uuid": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "severity": { + "type": "keyword" + }, + "start": { + "type": "date" + }, + "status": { + "type": "keyword" + }, + "system_status": { + "type": "keyword" + }, + "uuid": { + "type": "keyword" + }, + "workflow_reason": { + "type": "keyword" + }, + "workflow_status": { + "type": "keyword" + }, + "workflow_user": { + "type": "keyword" + } + } + }, + "space_ids": { + "type": "keyword" + }, + "version": { + "type": "version" + } + } + }, + "tags": { + "type": "keyword" + } + } + }, + "settings": { + "index": { + "lifecycle": { + "name": ".alerts-ilm-policy", + "rollover_alias": ".alerts-observability.logs.alerts-default" + }, + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} + +{ + "type": "index", + "value": { + "aliases": { + ".alerts-observability.metrics.alerts-default": { + "is_write_index": true + } + }, + "index": ".internal.alerts-observability.metrics.alerts-default-000001", + "mappings": { + "_meta": { + "kibana": { + "version": "8.0.0" + }, + "namespace": "default" + }, + "dynamic": "false", + "properties": { + "@timestamp": { + "type": "date" + }, + "host": { + "properties": { + "name": { + "type": "keyword" + } + } + }, + "ecs": { + "properties": { + "version": { + "type": "keyword" + } + } + }, + "event": { + "properties": { + "action": { + "type": "keyword" + }, + "kind": { + "type": "keyword" + } + } + }, + "kibana": { + "properties": { + "alert": { + "properties": { + "action_group": { + "type": "keyword" + }, + "duration": { + "properties": { + "us": { + "type": "long" + } + } + }, + "end": { + "type": "date" + }, + "evaluation": { + "properties": { + "threshold": { + "scaling_factor": 100, + "type": "scaled_float" + }, + "value": { + "scaling_factor": 100, + "type": "scaled_float" + } + } + }, + "instance": { + "properties": { + "id": { + "type": "keyword" + } + } + }, + "reason": { + "type": "keyword" + }, + "rule": { + "properties": { + "author": { + "type": "keyword" + }, + "category": { + "type": "keyword" + }, + "consumer": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "enabled": { + "type": "keyword" + }, + "from": { + "type": "keyword" + }, + "interval": { + "type": "keyword" + }, + "license": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "note": { + "type": "keyword" + }, + "params": { + "index": false, + "type": "keyword" + }, + "producer": { + "type": "keyword" + }, + "references": { + "type": "keyword" + }, + "risk_score": { + "type": "float" + }, + "risk_score_mapping": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "value": { + "type": "keyword" + } + } + }, + "rule_id": { + "type": "keyword" + }, + "rule_name_override": { + "type": "keyword" + }, + "rule_type_id": { + "type": "keyword" + }, + "severity": { + "type": "keyword" + }, + "severity_mapping": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "severity": { + "type": "keyword" + }, + "value": { + "type": "keyword" + } + } + }, + "tags": { + "type": "keyword" + }, + "to": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + }, + "uuid": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "severity": { + "type": "keyword" + }, + "start": { + "type": "date" + }, + "status": { + "type": "keyword" + }, + "system_status": { + "type": "keyword" + }, + "uuid": { + "type": "keyword" + }, + "workflow_reason": { + "type": "keyword" + }, + "workflow_status": { + "type": "keyword" + }, + "workflow_user": { + "type": "keyword" + } + } + }, + "space_ids": { + "type": "keyword" + }, + "version": { + "type": "version" + } + } + }, + "tags": { + "type": "keyword" + } + } + }, + "settings": { + "index": { + "lifecycle": { + "name": ".alerts-ilm-policy", + "rollover_alias": ".alerts-observability.metrics.alerts-default" + }, + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} \ No newline at end of file diff --git a/x-pack/test/functional/es_archives/security_solution/ecs_non_compliant/mappings.json b/x-pack/test/functional/es_archives/security_solution/ecs_non_compliant/mappings.json index 42d23e794ba23..40408d65b6d89 100644 --- a/x-pack/test/functional/es_archives/security_solution/ecs_non_compliant/mappings.json +++ b/x-pack/test/functional/es_archives/security_solution/ecs_non_compliant/mappings.json @@ -55,6 +55,24 @@ } } } + }, + "process.command_line": { + "type": "keyword", + "ignore_above": 10, + "fields": { + "text": { + "type": "text" + } + } + }, + "nonEcs.command_line": { + "type": "keyword", + "ignore_above": 10, + "fields": { + "text": { + "type": "text" + } + } } } }, diff --git a/x-pack/test/functional/page_objects/infra_hosts_view.ts b/x-pack/test/functional/page_objects/infra_hosts_view.ts index ddc7f24029d46..b070a1267d4f0 100644 --- a/x-pack/test/functional/page_objects/infra_hosts_view.ts +++ b/x-pack/test/functional/page_objects/infra_hosts_view.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { AlertStatus, ALERT_STATUS_ACTIVE, ALERT_STATUS_RECOVERED } from '@kbn/rule-data-utils'; import { FtrProviderContext } from '../ftr_provider_context'; export function InfraHostsViewProvider({ getService }: FtrProviderContext) { @@ -79,5 +80,31 @@ export function InfraHostsViewProvider({ getService }: FtrProviderContext) { await testSubjects.existOrFail('embeddablePanelContextMenuOpen'); return testSubjects.existOrFail('embeddablePanelAction-openInLens'); }, + + // Alerts Tab + getAlertsTab() { + return testSubjects.find('hostsView-tabs-alerts'); + }, + + getAlertsTabCountBadge() { + return testSubjects.find('hostsView-tabs-alerts-count'); + }, + + async visitAlertTab() { + const alertsTab = await this.getAlertsTab(); + alertsTab.click(); + }, + + setAlertStatusFilter(alertStatus?: AlertStatus) { + const buttons = { + [ALERT_STATUS_ACTIVE]: 'hostsView-alert-status-filter-active-button', + [ALERT_STATUS_RECOVERED]: 'hostsView-alert-status-filter-recovered-button', + all: 'hostsView-alert-status-filter-show-all-button', + }; + + const buttonSubject = alertStatus ? buttons[alertStatus] : buttons.all; + + return testSubjects.click(buttonSubject); + }, }; } diff --git a/x-pack/test/functional/services/rules/api.ts b/x-pack/test/functional/services/rules/api.ts index e4952adb1e473..0fdb40cc918a3 100644 --- a/x-pack/test/functional/services/rules/api.ts +++ b/x-pack/test/functional/services/rules/api.ts @@ -60,7 +60,7 @@ export function RulesAPIServiceProvider({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'foo') .expect(200); - for (const rule of body) { + for (const rule of body.data) { await this.deleteRule(rule.id); } }, diff --git a/x-pack/test/osquery_cypress/agent.ts b/x-pack/test/osquery_cypress/agent.ts index 2bdecd90efdff..e35b2f1bc4d37 100644 --- a/x-pack/test/osquery_cypress/agent.ts +++ b/x-pack/test/osquery_cypress/agent.ts @@ -7,7 +7,7 @@ import { ToolingLog } from '@kbn/tooling-log'; import axios, { AxiosRequestConfig } from 'axios'; -import { ChildProcess, spawn } from 'child_process'; +import execa from 'execa'; import { getLatestVersion } from './artifact_manager'; import { Manager } from './resource_manager'; @@ -22,7 +22,7 @@ export interface AgentManagerParams { export class AgentManager extends Manager { private params: AgentManagerParams; private log: ToolingLog; - private agentProcess?: ChildProcess; + private agentContainerId?: string; private requestOptions: AxiosRequestConfig; constructor(params: AgentManagerParams, log: ToolingLog, requestOptions: AxiosRequestConfig) { super(); @@ -33,34 +33,40 @@ export class AgentManager extends Manager { public async setup() { this.log.info('Running agent preconfig'); - return await axios.post( - `${this.params.kibanaUrl}/api/fleet/agents/setup`, - {}, + + await axios.post( + `${this.params.kibanaUrl}/api/fleet/agent_policies?sys_monitoring=true`, + { + name: 'Osquery policy', + description: '', + namespace: 'default', + monitoring_enabled: ['logs', 'metrics'], + inactivity_timeout: 1209600, + }, this.requestOptions ); - } - public async startAgent() { this.log.info('Getting agent enrollment key'); const { data: apiKeys } = await axios.get( this.params.kibanaUrl + '/api/fleet/enrollment_api_keys', this.requestOptions ); - const policy = apiKeys.items[1]; + const policy = apiKeys.items[0]; this.log.info('Running the agent'); const artifact = `docker.elastic.co/beats/elastic-agent:${await getLatestVersion()}`; this.log.info(artifact); - const args = [ + const dockerArgs = [ 'run', + '--detach', '--add-host', 'host.docker.internal:host-gateway', '--env', 'FLEET_ENROLL=1', '--env', - `FLEET_URL=http://host.docker.internal:8220`, + `FLEET_URL=https://host.docker.internal:8220`, '--env', `FLEET_ENROLLMENT_TOKEN=${policy.api_key}`, '--env', @@ -69,7 +75,7 @@ export class AgentManager extends Manager { artifact, ]; - this.agentProcess = spawn('docker', args, { stdio: 'inherit' }); + this.agentContainerId = (await execa('docker', dockerArgs)).stdout; // Wait til we see the agent is online let done = false; @@ -92,15 +98,11 @@ export class AgentManager extends Manager { protected _cleanup() { this.log.info('Cleaning up the agent process'); - if (this.agentProcess) { - if (!this.agentProcess.kill(9)) { - this.log.warning('Unable to kill agent process'); - } + if (this.agentContainerId) { + this.log.info('Closing agent process'); - this.agentProcess.on('close', () => { - this.log.info('Agent process closed'); - }); - delete this.agentProcess; + execa.sync('docker', ['kill', this.agentContainerId]); + this.log.info('Agent process closed'); } return; } diff --git a/x-pack/test/osquery_cypress/artifact_manager.ts b/x-pack/test/osquery_cypress/artifact_manager.ts index 7ee2680e21f83..2a520c22203c0 100644 --- a/x-pack/test/osquery_cypress/artifact_manager.ts +++ b/x-pack/test/osquery_cypress/artifact_manager.ts @@ -6,5 +6,5 @@ */ export async function getLatestVersion(): Promise { - return '8.6.0-SNAPSHOT'; + return '8.8.0-SNAPSHOT'; } diff --git a/x-pack/test/osquery_cypress/config.ts b/x-pack/test/osquery_cypress/config.ts index 37f7b3f63b36c..76b5c3eca9f89 100644 --- a/x-pack/test/osquery_cypress/config.ts +++ b/x-pack/test/osquery_cypress/config.ts @@ -35,9 +35,11 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...xpackFunctionalTestsConfig.get('kbnTestServer'), serverArgs: [ ...xpackFunctionalTestsConfig.get('kbnTestServer.serverArgs'), + '--csp.warnLegacyBrowsers=false', '--csp.strict=false', // define custom kibana server args here `--elasticsearch.ssl.certificateAuthorities=${CA_CERT_PATH}`, + `--xpack.fleet.agents.fleet_server.hosts=["https://host.docker.internal:8220"]`, `--xpack.fleet.agents.elasticsearch.host=http://host.docker.internal:${kibanaCommonTestsConfig.get( 'servers.elasticsearch.port' )}`, diff --git a/x-pack/test/osquery_cypress/fleet_server.ts b/x-pack/test/osquery_cypress/fleet_server.ts index 77ec56cf20960..ce1ff24adc32a 100644 --- a/x-pack/test/osquery_cypress/fleet_server.ts +++ b/x-pack/test/osquery_cypress/fleet_server.ts @@ -5,15 +5,15 @@ * 2.0. */ -import { ChildProcess, spawn } from 'child_process'; import { ToolingLog } from '@kbn/tooling-log'; import axios, { AxiosRequestConfig } from 'axios'; +import execa from 'execa'; import { Manager } from './resource_manager'; import { getLatestVersion } from './artifact_manager'; import { AgentManagerParams } from './agent'; export class FleetManager extends Manager { - private fleetProcess?: ChildProcess; + private fleetContainerId?: string; private config: AgentManagerParams; private log: ToolingLog; private requestOptions: AxiosRequestConfig; @@ -25,77 +25,65 @@ export class FleetManager extends Manager { } public async setup(): Promise { this.log.info('Setting fleet up'); - return new Promise(async (res, rej) => { - try { - // default fleet server policy no longer created by default - const { - data: { - item: { id: policyId }, - }, - } = await axios.post( - `${this.config.kibanaUrl}/api/fleet/agent_policies`, - { - name: 'Default Fleet Server policy', - description: '', - namespace: 'default', - monitoring_enabled: ['logs', 'metrics'], - has_fleet_server: true, - }, - this.requestOptions - ); - const response = await axios.post( - `${this.config.kibanaUrl}/api/fleet/service_tokens`, - {}, - this.requestOptions - ); - const serviceToken = response.data.value; - const artifact = `docker.elastic.co/beats/elastic-agent:${await getLatestVersion()}`; - this.log.info(artifact); + // default fleet server policy no longer created by default + const { + data: { + item: { id: policyId }, + }, + } = await axios.post( + `${this.config.kibanaUrl}/api/fleet/agent_policies`, + { + name: 'Default Fleet Server policy', + description: '', + namespace: 'default', + monitoring_enabled: ['logs', 'metrics'], + has_fleet_server: true, + }, + this.requestOptions + ); - const host = 'host.docker.internal'; + const response = await axios.post( + `${this.config.kibanaUrl}/api/fleet/service_tokens`, + {}, + this.requestOptions + ); + const serviceToken = response.data.value; + const artifact = `docker.elastic.co/beats/elastic-agent:${await getLatestVersion()}`; + this.log.info(artifact); - const args = [ - 'run', - '-p', - `8220:8220`, - '--add-host', - 'host.docker.internal:host-gateway', - '--env', - 'FLEET_SERVER_ENABLE=true', - '--env', - `FLEET_SERVER_ELASTICSEARCH_HOST=http://${host}:${this.config.esPort}`, - '--env', - `FLEET_SERVER_SERVICE_TOKEN=${serviceToken}`, - '--env', - `FLEET_SERVER_POLICY=${policyId}`, - '--rm', - artifact, - ]; - this.log.info('docker ' + args.join(' ')); - this.fleetProcess = spawn('docker', args, { - stdio: 'inherit', - }); - this.fleetProcess.on('error', rej); - setTimeout(res, 15000); - } catch (error) { - rej(error); - } - }); + const host = 'host.docker.internal'; + + const dockerArgs = [ + 'run', + '--detach', + '-p', + `8220:8220`, + '--add-host', + 'host.docker.internal:host-gateway', + '--env', + 'FLEET_SERVER_ENABLE=true', + '--env', + `FLEET_SERVER_ELASTICSEARCH_HOST=http://${host}:${this.config.esPort}`, + '--env', + `FLEET_SERVER_SERVICE_TOKEN=${serviceToken}`, + '--env', + `FLEET_SERVER_POLICY=${policyId}`, + '--rm', + artifact, + ]; + + this.log.info('docker ' + dockerArgs.join(' ')); + this.fleetContainerId = (await execa('docker', dockerArgs)).stdout; } protected _cleanup() { this.log.info('Removing old fleet config'); - if (this.fleetProcess) { + if (this.fleetContainerId) { this.log.info('Closing fleet process'); - if (!this.fleetProcess.kill(9)) { - this.log.warning('Unable to kill fleet server process'); - } - this.fleetProcess.on('close', () => { - this.log.info('Fleet server process closed'); - }); - delete this.fleetProcess; + execa.sync('docker', ['kill', this.fleetContainerId]); + this.log.info('Fleet server process closed'); } } } diff --git a/x-pack/test/osquery_cypress/runner.ts b/x-pack/test/osquery_cypress/runner.ts index 2c97d08b682c3..2cd194c14ea53 100644 --- a/x-pack/test/osquery_cypress/runner.ts +++ b/x-pack/test/osquery_cypress/runner.ts @@ -12,10 +12,7 @@ import { withProcRunner } from '@kbn/dev-proc-runner'; import { FtrProviderContext } from './ftr_provider_context'; -import { - // AgentManager, - AgentManagerParams, -} from './agent'; +import { AgentManager, AgentManagerParams } from './agent'; import { FleetManager } from './fleet_server'; async function withFleetAgent( @@ -47,8 +44,7 @@ async function withFleetAgent( }, }; const fleetManager = new FleetManager(params, log, requestOptions); - - // const agentManager = new AgentManager(params, log, requestOptions); + const agentManager = new AgentManager(params, log, requestOptions); // Since the managers will create uncaughtException event handlers we need to exit manually process.on('uncaughtException', (err) => { @@ -57,13 +53,13 @@ async function withFleetAgent( process.exit(1); }); - // await agentManager.setup(); await fleetManager.setup(); + await agentManager.setup(); try { await runner({}); } finally { + agentManager.cleanup(); fleetManager.cleanup(); - // agentManager.cleanup(); } } diff --git a/x-pack/test/rule_registry/spaces_only/tests/trial/get_summarized_alerts.ts b/x-pack/test/rule_registry/spaces_only/tests/trial/get_summarized_alerts.ts index 02765aa8c2a77..b62cf7b39965c 100644 --- a/x-pack/test/rule_registry/spaces_only/tests/trial/get_summarized_alerts.ts +++ b/x-pack/test/rule_registry/spaces_only/tests/trial/get_summarized_alerts.ts @@ -10,7 +10,7 @@ import type { ElasticsearchClient, Logger, LogMeta } from '@kbn/core/server'; import sinon from 'sinon'; import { v4 as uuidv4 } from 'uuid'; import expect from '@kbn/expect'; -import { mappingFromFieldMap } from '@kbn/rule-registry-plugin/common/mapping_from_field_map'; +import { mappingFromFieldMap } from '@kbn/alerting-plugin/common'; import { AlertConsumers, ALERT_REASON, diff --git a/x-pack/test/rule_registry/spaces_only/tests/trial/lifecycle_executor.ts b/x-pack/test/rule_registry/spaces_only/tests/trial/lifecycle_executor.ts index dc5752417bfb4..10351fc6cf2ef 100644 --- a/x-pack/test/rule_registry/spaces_only/tests/trial/lifecycle_executor.ts +++ b/x-pack/test/rule_registry/spaces_only/tests/trial/lifecycle_executor.ts @@ -9,7 +9,7 @@ import { type Subject, ReplaySubject } from 'rxjs'; import type { ElasticsearchClient, Logger, LogMeta } from '@kbn/core/server'; import sinon from 'sinon'; import expect from '@kbn/expect'; -import { mappingFromFieldMap } from '@kbn/rule-registry-plugin/common/mapping_from_field_map'; +import { mappingFromFieldMap } from '@kbn/alerting-plugin/common'; import { AlertConsumers, ALERT_REASON, diff --git a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/index.ts b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/index.ts index 85e118756c4b7..32f0e2ea49f92 100644 --- a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/index.ts +++ b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/index.ts @@ -10,6 +10,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ loadTestFile, getService }: FtrProviderContext) { const browser = getService('browser'); const actions = getService('actions'); + const rules = getService('rules'); describe('stack alerting', function () { before(async () => { @@ -23,10 +24,12 @@ export default function ({ loadTestFile, getService }: FtrProviderContext) { }); after(async () => { + await rules.api.deleteAllRules(); await actions.api.deleteAllConnectors(); }); loadTestFile(require.resolve('./list_view')); loadTestFile(require.resolve('./connector_types')); + loadTestFile(require.resolve('./index_threshold_rule')); }); } diff --git a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/index_threshold_rule.ts b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/index_threshold_rule.ts new file mode 100644 index 0000000000000..ec27e67f0874e --- /dev/null +++ b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/index_threshold_rule.ts @@ -0,0 +1,141 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const comboBox = getService('comboBox'); + const commonScreenshots = getService('commonScreenshots'); + const find = getService('find'); + const rules = getService('rules'); + const testSubjects = getService('testSubjects'); + const pageObjects = getPageObjects(['common', 'header']); + const screenshotDirectories = ['response_ops_docs', 'stack_alerting']; + + describe('index threshold rule', function () { + it('create rule screenshot', async () => { + await pageObjects.common.navigateToApp('triggersActions'); + await pageObjects.header.waitUntilLoadingHasFinished(); + await rules.common.clickCreateAlertButton(); + await testSubjects.setValue('ruleNameInput', 'kibana sites - high egress'); + await testSubjects.click('tagsComboBox'); + await testSubjects.setValue('tagsComboBox', 'sample-data'); + await testSubjects.click('solutionsFilterButton'); + await testSubjects.click('solutionstackAlertsFilterOption'); + await testSubjects.setValue('solutionsFilterButton', 'solutionstackAlertsFilterOption'); + await commonScreenshots.takeScreenshot( + 'rule-types-index-threshold-select', + screenshotDirectories, + 1400, + 1024 + ); + + await testSubjects.click('.index-threshold-SelectOption'); + await commonScreenshots.takeScreenshot( + 'rule-types-index-threshold-conditions', + screenshotDirectories, + 1400, + 1024 + ); + + await testSubjects.scrollIntoView('selectIndexExpression'); + await testSubjects.click('selectIndexExpression'); + const indexComboBox = await find.byCssSelector('#indexSelectSearchBox'); + await indexComboBox.click(); + await indexComboBox.type('kibana_sample_data_logs '); + const filterSelectItem = await find.byCssSelector(`.euiFilterSelectItem`); + await filterSelectItem.click(); + await testSubjects.click('thresholdAlertTimeFieldSelect'); + await testSubjects.setValue('thresholdAlertTimeFieldSelect', '@timestamp'); + await commonScreenshots.takeScreenshot( + 'rule-types-index-threshold-example-index', + screenshotDirectories, + 1400, + 1024 + ); + await testSubjects.click('closePopover'); + + await testSubjects.click('whenExpression'); + await testSubjects.click('whenExpressionSelect'); + await testSubjects.setValue('whenExpressionSelect', 'sum()'); + await testSubjects.click('ofExpressionPopover'); + const ofComboBox = await find.byCssSelector('#ofField'); + await ofComboBox.click(); + await commonScreenshots.takeScreenshot( + 'rule-types-index-threshold-example-aggregation', + screenshotDirectories, + 1400, + 1024 + ); + await ofComboBox.type('bytes'); + const ofOptionsString = await comboBox.getOptionsList('availablefieldsOptionsComboBox'); + const ofOptions = ofOptionsString.trim().split('\n'); + expect(ofOptions.length > 0).to.be(true); + await comboBox.set('availablefieldsOptionsComboBox', ofOptions[0]); + + await testSubjects.click('groupByExpression'); + await testSubjects.click('overExpressionSelect'); + await testSubjects.setValue('overExpressionSelect', 'top'); + await testSubjects.setValue('fieldsNumberSelect', '4'); + await testSubjects.setValue('fieldsExpressionSelect', 'host.keyword'); + await commonScreenshots.takeScreenshot( + 'rule-types-index-threshold-example-grouping', + screenshotDirectories, + 1400, + 1024 + ); + // need this two out of popup clicks to close them + const nameInput1 = await testSubjects.find('ruleNameInput'); + await nameInput1.click(); + + await testSubjects.click('thresholdPopover'); + await testSubjects.setValue('alertThresholdInput', '420000'); + await testSubjects.click('forLastExpression'); + await testSubjects.setValue('timeWindowSizeNumber', '24'); + await testSubjects.setValue('timeWindowUnitSelect', 'hours'); + // need this two out of popup clicks to close them + const nameInput2 = await testSubjects.find('ruleNameInput'); + await nameInput2.click(); + await testSubjects.scrollIntoView('thresholdPopover'); + await commonScreenshots.takeScreenshot( + 'rule-types-index-threshold-example-threshold', + screenshotDirectories, + 1400, + 1024 + ); + + await testSubjects.setValue('intervalInput', '4'); + await testSubjects.setValue('intervalInputUnit', 'hours'); + // need this two out of popup clicks to close them + const nameInput3 = await testSubjects.find('ruleNameInput'); + await nameInput3.click(); + await testSubjects.scrollIntoView('alertVisualizationChart'); + await commonScreenshots.takeScreenshot( + 'rule-types-index-threshold-example-preview', + screenshotDirectories, + 1400, + 1024 + ); + + await testSubjects.click('.server-log-alerting-ActionTypeSelectOption'); + await testSubjects.scrollIntoView('addAlertActionButton'); + await commonScreenshots.takeScreenshot( + 'rule-types-index-threshold-example-action', + screenshotDirectories, + 1400, + 1024 + ); + /* + * const saveButton = await testSubjects.find('saveRuleButton'); + * await saveButton.click(); + */ + const flyOutCancelButton = await testSubjects.find('euiFlyoutCloseButton'); + await flyOutCancelButton.click(); + }); + }); +} diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts index 589a61295a541..6aab2457c5278 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts @@ -15,8 +15,7 @@ import { export default function (providerContext: FtrProviderContext) { const { loadTestFile, getService } = providerContext; - // FLAKY: https://github.com/elastic/kibana/issues/72874 - describe.skip('endpoint', function () { + describe('endpoint', function () { const ingestManager = getService('ingestManager'); const log = getService('log'); const endpointTestResources = getService('endpointTestResources'); diff --git a/x-pack/test/tsconfig.json b/x-pack/test/tsconfig.json index 476b6223b9591..bf4fe75140b6f 100644 --- a/x-pack/test/tsconfig.json +++ b/x-pack/test/tsconfig.json @@ -115,6 +115,10 @@ "@kbn/cloud-security-posture-plugin", "@kbn/cloud-integration-saml-provider-plugin", "@kbn/security-api-integration-helpers", + "@kbn/alerts-as-data-utils", "@kbn/discover-plugin", + "@kbn/files-plugin", + "@kbn/shared-ux-file-types", + "@kbn/securitysolution-io-ts-alerting-types", ] } diff --git a/yarn.lock b/yarn.lock index 1fd92f43a8fdb..dd63058020b48 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7,12 +7,13 @@ resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.0.1.tgz#b38b444ad3aa5fedbb15f2f746dcd934226a12dd" integrity sha512-+u76oB43nOHrF4DDWRLWDCtci7f3QJoEBigemIdIeTi1ODqjx6Tad9NCVnPRwewWlKkVab5PlK8DCtPTyX7S8g== -"@ampproject/remapping@^2.1.0": - version "2.1.2" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.1.2.tgz#4edca94973ded9630d20101cd8559cedb8d8bd34" - integrity sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg== +"@ampproject/remapping@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" + integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w== dependencies: - "@jridgewell/trace-mapping" "^0.3.0" + "@jridgewell/gen-mapping" "^0.1.0" + "@jridgewell/trace-mapping" "^0.3.9" "@apidevtools/json-schema-ref-parser@^9.0.6": version "9.0.9" @@ -56,12 +57,12 @@ resolved "https://registry.yarnpkg.com/@assemblyscript/loader/-/loader-0.10.1.tgz#70e45678f06c72fa2e350e8553ec4a4d72b92e06" integrity sha512-H71nDOOL8Y7kWRLqf6Sums+01Q5msqBW2KhDUTemh1tvY04eSkSXrK0uj/4mmY0Xr16/3zyZmsrxN7CKuRbNRg== -"@babel/cli@^7.20.7": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.20.7.tgz#8fc12e85c744a1a617680eacb488fab1fcd35b7c" - integrity sha512-WylgcELHB66WwQqItxNILsMlaTd8/SO6SgTTjMp4uCI7P4QyH1r3nqgFmO3BfM4AtfniHgFMH3EpYFj/zynBkQ== +"@babel/cli@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.21.0.tgz#1868eb70e9824b427fc607610cce8e9e7889e7e1" + integrity sha512-xi7CxyS8XjSyiwUGCfwf+brtJxjW1/ZTcBUkP10xawIEXLX5HzLn+3aXkgxozcP2UhRhtKTmQurw9Uaes7jZrA== dependencies: - "@jridgewell/trace-mapping" "^0.3.8" + "@jridgewell/trace-mapping" "^0.3.17" commander "^4.0.1" convert-source-map "^1.1.0" fs-readdir-recursive "^1.1.0" @@ -113,21 +114,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.12", "@babel/core@^7.7.2", "@babel/core@^7.7.5": - version "7.20.12" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.20.12.tgz#7930db57443c6714ad216953d1356dac0eb8496d" - integrity sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg== +"@babel/core@^7.1.0", "@babel/core@^7.11.6", "@babel/core@^7.12.10", "@babel/core@^7.12.3", "@babel/core@^7.21.0", "@babel/core@^7.7.2", "@babel/core@^7.7.5": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.21.0.tgz#1341aefdcc14ccc7553fcc688dd8986a2daffc13" + integrity sha512-PuxUbxcW6ZYe656yL3EAhpy7qXKq0DmYsrJLpbB8XrsCP9Nm+XCg9XFMb5vIDliPD7+U/+M+QJlH17XOcB7eXA== dependencies: - "@ampproject/remapping" "^2.1.0" + "@ampproject/remapping" "^2.2.0" "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.20.7" + "@babel/generator" "^7.21.0" "@babel/helper-compilation-targets" "^7.20.7" - "@babel/helper-module-transforms" "^7.20.11" - "@babel/helpers" "^7.20.7" - "@babel/parser" "^7.20.7" + "@babel/helper-module-transforms" "^7.21.0" + "@babel/helpers" "^7.21.0" + "@babel/parser" "^7.21.0" "@babel/template" "^7.20.7" - "@babel/traverse" "^7.20.12" - "@babel/types" "^7.20.7" + "@babel/traverse" "^7.21.0" + "@babel/types" "^7.21.0" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" @@ -150,13 +151,14 @@ dependencies: eslint-rule-composer "^0.3.0" -"@babel/generator@^7.12.11", "@babel/generator@^7.12.5", "@babel/generator@^7.20.14", "@babel/generator@^7.20.7", "@babel/generator@^7.7.2": - version "7.20.14" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.20.14.tgz#9fa772c9f86a46c6ac9b321039400712b96f64ce" - integrity sha512-AEmuXHdcD3A52HHXxaTmYlb8q/xMEhoRP67B3T4Oq7lbmSoqroMZzjnGj3+i1io3pdnF8iBYVu4Ilj+c4hBxYg== +"@babel/generator@^7.12.11", "@babel/generator@^7.12.5", "@babel/generator@^7.21.0", "@babel/generator@^7.21.1", "@babel/generator@^7.7.2": + version "7.21.1" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.21.1.tgz#951cc626057bc0af2c35cd23e9c64d384dea83dd" + integrity sha512-1lT45bAYlQhFn/BHivJs43AiW2rg3/UbLyShGfF3C0KmHvO5fSghWd5kBJy30kpRRucGzXStvnnCFniCR2kXAA== dependencies: - "@babel/types" "^7.20.7" + "@babel/types" "^7.21.0" "@jridgewell/gen-mapping" "^0.3.2" + "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" "@babel/helper-annotate-as-pure@^7.16.0", "@babel/helper-annotate-as-pure@^7.18.6": @@ -185,17 +187,18 @@ lru-cache "^5.1.1" semver "^6.3.0" -"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.19.0", "@babel/helper-create-class-features-plugin@^7.20.2": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.20.2.tgz#3c08a5b5417c7f07b5cf3dfb6dc79cbec682e8c2" - integrity sha512-k22GoYRAHPYr9I+Gvy2ZQlAe5mGy8BqWst2wRt8cwIufWTxrsVshhIBvYNqC80N0GSFWTsqRVexOtfzlgOEDvA== +"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.20.2", "@babel/helper-create-class-features-plugin@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.21.0.tgz#64f49ecb0020532f19b1d014b03bccaa1ab85fb9" + integrity sha512-Q8wNiMIdwsv5la5SPxNYzzkPnjgC0Sy0i7jLkVOCdllu/xcVNkr3TeZzbHBJrj+XXRqzX5uCyCoV9eu6xUG7KQ== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.19.0" - "@babel/helper-member-expression-to-functions" "^7.18.9" + "@babel/helper-function-name" "^7.21.0" + "@babel/helper-member-expression-to-functions" "^7.21.0" "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/helper-replace-supers" "^7.19.1" + "@babel/helper-replace-supers" "^7.20.7" + "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" "@babel/helper-split-export-declaration" "^7.18.6" "@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.19.0": @@ -244,13 +247,13 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-function-name@^7.18.9", "@babel/helper-function-name@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz#941574ed5390682e872e52d3f38ce9d1bef4648c" - integrity sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w== +"@babel/helper-function-name@^7.18.9", "@babel/helper-function-name@^7.19.0", "@babel/helper-function-name@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz#d552829b10ea9f120969304023cd0645fa00b1b4" + integrity sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg== dependencies: - "@babel/template" "^7.18.10" - "@babel/types" "^7.19.0" + "@babel/template" "^7.20.7" + "@babel/types" "^7.21.0" "@babel/helper-hoist-variables@^7.18.6": version "7.18.6" @@ -259,12 +262,12 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-member-expression-to-functions@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz#1531661e8375af843ad37ac692c132841e2fd815" - integrity sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg== +"@babel/helper-member-expression-to-functions@^7.20.7", "@babel/helper-member-expression-to-functions@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.21.0.tgz#319c6a940431a133897148515877d2f3269c3ba5" + integrity sha512-Muu8cdZwNN6mRRNG6lAYErJ5X3bRevgYR2O8wN0yn7jJSnGDu6eG59RfT29JHxGUovyfrh6Pj0XzmR7drNVL3Q== dependencies: - "@babel/types" "^7.18.9" + "@babel/types" "^7.21.0" "@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.16.0", "@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.18.6": version "7.18.6" @@ -273,10 +276,10 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-module-transforms@^7.12.1", "@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.19.6", "@babel/helper-module-transforms@^7.20.11": - version "7.20.11" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.20.11.tgz#df4c7af713c557938c50ea3ad0117a7944b2f1b0" - integrity sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg== +"@babel/helper-module-transforms@^7.12.1", "@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.19.6", "@babel/helper-module-transforms@^7.21.0": + version "7.21.2" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz#160caafa4978ac8c00ac66636cb0fa37b024e2d2" + integrity sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ== dependencies: "@babel/helper-environment-visitor" "^7.18.9" "@babel/helper-module-imports" "^7.18.6" @@ -284,8 +287,8 @@ "@babel/helper-split-export-declaration" "^7.18.6" "@babel/helper-validator-identifier" "^7.19.1" "@babel/template" "^7.20.7" - "@babel/traverse" "^7.20.10" - "@babel/types" "^7.20.7" + "@babel/traverse" "^7.21.2" + "@babel/types" "^7.21.2" "@babel/helper-optimise-call-expression@^7.18.6": version "7.18.6" @@ -314,16 +317,17 @@ "@babel/helper-wrap-function" "^7.18.9" "@babel/types" "^7.18.9" -"@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.19.1": - version "7.19.1" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz#e1592a9b4b368aa6bdb8784a711e0bcbf0612b78" - integrity sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw== +"@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.19.1", "@babel/helper-replace-supers@^7.20.7": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.20.7.tgz#243ecd2724d2071532b2c8ad2f0f9f083bcae331" + integrity sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A== dependencies: "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-member-expression-to-functions" "^7.18.9" + "@babel/helper-member-expression-to-functions" "^7.20.7" "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/traverse" "^7.19.1" - "@babel/types" "^7.19.0" + "@babel/template" "^7.20.7" + "@babel/traverse" "^7.20.7" + "@babel/types" "^7.20.7" "@babel/helper-simple-access@^7.19.4", "@babel/helper-simple-access@^7.20.2": version "7.20.2" @@ -356,10 +360,10 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== -"@babel/helper-validator-option@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8" - integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== +"@babel/helper-validator-option@^7.18.6", "@babel/helper-validator-option@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz#8224c7e13ace4bafdc4004da2cf064ef42673180" + integrity sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ== "@babel/helper-wrap-function@^7.18.9": version "7.19.0" @@ -371,14 +375,14 @@ "@babel/traverse" "^7.19.0" "@babel/types" "^7.19.0" -"@babel/helpers@^7.12.5", "@babel/helpers@^7.20.7": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.20.7.tgz#04502ff0feecc9f20ecfaad120a18f011a8e6dce" - integrity sha512-PBPjs5BppzsGaxHQCDKnZ6Gd9s6xl8bBCluz3vEInLGRJmnZan4F6BYCeqtyXqkk4W5IlPmjK4JlOuZkpJ3xZA== +"@babel/helpers@^7.12.5", "@babel/helpers@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.21.0.tgz#9dd184fb5599862037917cdc9eecb84577dc4e7e" + integrity sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA== dependencies: "@babel/template" "^7.20.7" - "@babel/traverse" "^7.20.7" - "@babel/types" "^7.20.7" + "@babel/traverse" "^7.21.0" + "@babel/types" "^7.21.0" "@babel/highlight@^7.10.4", "@babel/highlight@^7.18.6": version "7.18.6" @@ -389,10 +393,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.20.13", "@babel/parser@^7.20.15", "@babel/parser@^7.20.7": - version "7.20.15" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.15.tgz#eec9f36d8eaf0948bb88c87a46784b5ee9fd0c89" - integrity sha512-DI4a1oZuf8wC+oAJA9RW6ga3Zbe8RZFt7kD9i4qAspz3I/yHet1VvC3DiSy/fsUvv5pvJuNPh0LPOdCcqinDPg== +"@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.20.7", "@babel/parser@^7.21.0", "@babel/parser@^7.21.2": + version "7.21.2" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.2.tgz#dacafadfc6d7654c3051a66d6fe55b6cb2f2a0b3" + integrity sha512-URpaIJQwEkEC2T9Kn+Ai6Xe/02iNaVCuT/PtoRz3GPVJVDpPd7mLo+VddTbhCRU9TXqW5mSrQfXZyi8kDKOVpQ== "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": version "7.18.6" @@ -532,10 +536,10 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" -"@babel/plugin-proposal-optional-chaining@^7.12.7", "@babel/plugin-proposal-optional-chaining@^7.18.9", "@babel/plugin-proposal-optional-chaining@^7.20.7": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.20.7.tgz#49f2b372519ab31728cc14115bb0998b15bfda55" - integrity sha512-T+A7b1kfjtRM51ssoOfS1+wbyCVqorfyZhT99TvxxLMirPShD8CzKMRepMlCBGM5RpHMbn8s+5MMHnPstJH6mQ== +"@babel/plugin-proposal-optional-chaining@^7.12.7", "@babel/plugin-proposal-optional-chaining@^7.18.9", "@babel/plugin-proposal-optional-chaining@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz#886f5c8978deb7d30f678b2e24346b287234d3ea" + integrity sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA== dependencies: "@babel/helper-plugin-utils" "^7.20.2" "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" @@ -968,13 +972,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-runtime@^7.19.6": - version "7.19.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.19.6.tgz#9d2a9dbf4e12644d6f46e5e75bfbf02b5d6e9194" - integrity sha512-PRH37lz4JU156lYFW1p8OxE5i7d6Sl/zV58ooyr+q1J1lnQPyg5tIiXlIwNVhJaY4W3TmOtdc8jqdXQcB1v5Yw== +"@babel/plugin-transform-runtime@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.21.0.tgz#2a884f29556d0a68cd3d152dcc9e6c71dfb6eee8" + integrity sha512-ReY6pxwSzEU0b3r2/T/VhqMKg/AkceBT19X0UptA3/tYi5Pe2eXgEUH+NNMC5nok6c6XQz5tyVTUpuezRfSMSg== dependencies: "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-plugin-utils" "^7.20.2" babel-plugin-polyfill-corejs2 "^0.3.3" babel-plugin-polyfill-corejs3 "^0.6.0" babel-plugin-polyfill-regenerator "^0.4.1" @@ -1016,13 +1020,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.9" -"@babel/plugin-transform-typescript@^7.18.6": - version "7.20.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.20.0.tgz#2c7ec62b8bfc21482f3748789ba294a46a375169" - integrity sha512-xOAsAFaun3t9hCwZ13Qe7gq423UgMZ6zAgmLxeGGapFqlT/X3L5qT2btjiVLlFn7gWtMaVyceS5VxGAuKbgizw== +"@babel/plugin-transform-typescript@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.21.0.tgz#f0956a153679e3b377ae5b7f0143427151e4c848" + integrity sha512-xo///XTPp3mDzTtrqXoBlK9eiAYW3wv9JXglcn/u1bi60RW11dEUxIgA8cbnDhutS1zacjMRmAwxE0gMklLnZg== dependencies: - "@babel/helper-create-class-features-plugin" "^7.19.0" - "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-create-class-features-plugin" "^7.21.0" + "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-syntax-typescript" "^7.20.0" "@babel/plugin-transform-unicode-escapes@^7.18.10": @@ -1153,19 +1157,19 @@ "@babel/plugin-transform-react-jsx-development" "^7.18.6" "@babel/plugin-transform-react-pure-annotations" "^7.18.6" -"@babel/preset-typescript@^7.12.7", "@babel/preset-typescript@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.18.6.tgz#ce64be3e63eddc44240c6358daefac17b3186399" - integrity sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ== +"@babel/preset-typescript@^7.12.7", "@babel/preset-typescript@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.21.0.tgz#bcbbca513e8213691fe5d4b23d9251e01f00ebff" + integrity sha512-myc9mpoVA5m1rF8K8DgLEatOYFDpwC+RkMkjZ0Du6uI62YvDe8uxIEYVs/VCdSJ097nlALiU/yBC7//3nI+hNg== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/helper-validator-option" "^7.18.6" - "@babel/plugin-transform-typescript" "^7.18.6" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-validator-option" "^7.21.0" + "@babel/plugin-transform-typescript" "^7.21.0" -"@babel/register@^7.12.1", "@babel/register@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.18.9.tgz#1888b24bc28d5cc41c412feb015e9ff6b96e439c" - integrity sha512-ZlbnXDcNYHMR25ITwwNKT88JiaukkdVj/nG7r3wnuXkOTHc60Uy05PwMCPre0hSkY68E6zK3xz+vUJSP2jWmcw== +"@babel/register@^7.12.1", "@babel/register@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.21.0.tgz#c97bf56c2472e063774f31d344c592ebdcefa132" + integrity sha512-9nKsPmYDi5DidAqJaQooxIhsLJiNMkGr8ypQ8Uic7cIox7UCDsM7HuUGxdGT7mSDTYbqzIdsOWzfBton/YJrMw== dependencies: clone-deep "^4.0.1" find-cache-dir "^2.0.0" @@ -1181,10 +1185,10 @@ core-js-pure "^3.25.1" regenerator-runtime "^0.13.10" -"@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.13", "@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.13" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.13.tgz#7055ab8a7cff2b8f6058bf6ae45ff84ad2aded4b" - integrity sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA== +"@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.21.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.0.tgz#5b55c9d394e5fcf304909a8b00c07dc217b56673" + integrity sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw== dependencies: regenerator-runtime "^0.13.11" @@ -1197,26 +1201,26 @@ "@babel/parser" "^7.20.7" "@babel/types" "^7.20.7" -"@babel/traverse@^7.10.3", "@babel/traverse@^7.12.11", "@babel/traverse@^7.12.9", "@babel/traverse@^7.13.0", "@babel/traverse@^7.19.0", "@babel/traverse@^7.19.1", "@babel/traverse@^7.20.10", "@babel/traverse@^7.20.12", "@babel/traverse@^7.20.13", "@babel/traverse@^7.20.7", "@babel/traverse@^7.4.5", "@babel/traverse@^7.7.2": - version "7.20.13" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.20.13.tgz#817c1ba13d11accca89478bd5481b2d168d07473" - integrity sha512-kMJXfF0T6DIS9E8cgdLCSAL+cuCK+YEZHWiLK0SXpTo8YRj5lpJu3CDNKiIBCne4m9hhTIqUg6SYTAI39tAiVQ== +"@babel/traverse@^7.10.3", "@babel/traverse@^7.12.11", "@babel/traverse@^7.12.9", "@babel/traverse@^7.13.0", "@babel/traverse@^7.19.0", "@babel/traverse@^7.20.7", "@babel/traverse@^7.21.0", "@babel/traverse@^7.21.2", "@babel/traverse@^7.4.5", "@babel/traverse@^7.7.2": + version "7.21.2" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.21.2.tgz#ac7e1f27658750892e815e60ae90f382a46d8e75" + integrity sha512-ts5FFU/dSUPS13tv8XiEObDu9K+iagEKME9kAbaP7r0Y9KtZJZ+NGndDvWoRAYNpeWafbpFeki3q9QoMD6gxyw== dependencies: "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.20.7" + "@babel/generator" "^7.21.1" "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.19.0" + "@babel/helper-function-name" "^7.21.0" "@babel/helper-hoist-variables" "^7.18.6" "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.20.13" - "@babel/types" "^7.20.7" + "@babel/parser" "^7.21.2" + "@babel/types" "^7.21.2" 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.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.20.0", "@babel/types@^7.20.2", "@babel/types@^7.20.7", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.7.tgz#54ec75e252318423fc07fb644dc6a58a64c09b7f" - integrity sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg== +"@babel/types@^7.0.0", "@babel/types@^7.10.3", "@babel/types@^7.12.11", "@babel/types@^7.12.7", "@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.20.7", "@babel/types@^7.21.0", "@babel/types@^7.21.2", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": + version "7.21.2" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.2.tgz#92246f6e00f91755893c2876ad653db70c8310d1" + integrity sha512-3wRZSs7jiFaB8AjxiiD+VqN5DTG2iRvJGQ+qYFrs/654lg6kGTQWIOFjlBo5RaXuAZjBmP3+OQH4dmhqiiyYxw== dependencies: "@babel/helper-string-parser" "^7.19.4" "@babel/helper-validator-identifier" "^7.19.1" @@ -1539,10 +1543,10 @@ resolved "https://registry.yarnpkg.com/@elastic/eslint-plugin-eui/-/eslint-plugin-eui-0.0.2.tgz#56b9ef03984a05cc213772ae3713ea8ef47b0314" integrity sha512-IoxURM5zraoQ7C8f+mJb9HYSENiZGgRVcG4tLQxE61yHNNRDXtGDWTZh8N1KIHcsqN1CEPETjuzBXkJYF/fDiQ== -"@elastic/eui@75.1.0": - version "75.1.0" - resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-75.1.0.tgz#6bdb2a12e5dd503258e74d5585803f52b826b83e" - integrity sha512-HJgoARNsXeYDIGO9sKV+wwfmFA2IKL9hjOMj8B0PZ4fA6Euprw7KPLkakUbwjTCm0rqYUf/6zmXRafvzvdKLmA== +"@elastic/eui@75.1.2": + version "75.1.2" + resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-75.1.2.tgz#c8ccb1728162b131e49a16833468ab2b0228f1bf" + integrity sha512-J6u16NR3BD5snje2CSWnk+JvEQ7y/8tzpmi2Ul+WWfzQwvf7DsKtouSIs91jdzC1QGSN26S1D3wKZvzaszXacg== dependencies: "@types/chroma-js" "^2.0.0" "@types/lodash" "^4.14.160" @@ -2680,6 +2684,14 @@ "@types/yargs" "^17.0.8" chalk "^4.0.0" +"@jridgewell/gen-mapping@^0.1.0": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" + integrity sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w== + dependencies: + "@jridgewell/set-array" "^1.0.0" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": version "0.3.2" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" @@ -2689,15 +2701,15 @@ "@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/trace-mapping" "^0.3.9" -"@jridgewell/resolve-uri@^3.0.3": - version "3.0.5" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz#68eb521368db76d040a6315cdb24bf2483037b9c" - integrity sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew== +"@jridgewell/resolve-uri@3.1.0", "@jridgewell/resolve-uri@^3.0.3": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" + integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== -"@jridgewell/set-array@^1.0.1": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.1.tgz#36a6acc93987adcf0ba50c66908bd0b70de8afea" - integrity sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ== +"@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== "@jridgewell/source-map@^0.3.2": version "0.3.2" @@ -2707,10 +2719,10 @@ "@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/trace-mapping" "^0.3.9" -"@jridgewell/sourcemap-codec@^1.4.10": - version "1.4.11" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz#771a1d8d744eeb71b6adb35808e1a6c7b9b8c8ec" - integrity sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg== +"@jridgewell/sourcemap-codec@1.4.14", "@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.14" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" + integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== "@jridgewell/trace-mapping@0.3.9": version "0.3.9" @@ -2720,13 +2732,13 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" -"@jridgewell/trace-mapping@^0.3.0", "@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.14", "@jridgewell/trace-mapping@^0.3.15", "@jridgewell/trace-mapping@^0.3.8", "@jridgewell/trace-mapping@^0.3.9": - version "0.3.15" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz#aba35c48a38d3fd84b37e66c9c0423f9744f9774" - integrity sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g== +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.14", "@jridgewell/trace-mapping@^0.3.15", "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9": + version "0.3.17" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz#793041277af9073b0951a7fe0f0d8c4c98c36985" + integrity sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g== dependencies: - "@jridgewell/resolve-uri" "^3.0.3" - "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/resolve-uri" "3.1.0" + "@jridgewell/sourcemap-codec" "1.4.14" "@jsdevtools/ono@^7.1.3": version "7.1.3" @@ -2785,6 +2797,10 @@ version "0.0.0" uid "" +"@kbn/alerts-as-data-utils@link:packages/kbn-alerts-as-data-utils": + version "0.0.0" + uid "" + "@kbn/alerts-restricted-fixtures-plugin@link:x-pack/test/alerting_api_integration/common/plugins/alerts_restricted": version "0.0.0" uid "" @@ -3041,6 +3057,10 @@ version "0.0.0" uid "" +"@kbn/content-management-examples-plugin@link:examples/content_management_examples": + version "0.0.0" + uid "" + "@kbn/content-management-plugin@link:src/plugins/content_management": version "0.0.0" uid "" @@ -4057,6 +4077,10 @@ version "0.0.0" uid "" +"@kbn/expandable-flyout@link:packages/kbn-expandable-flyout": + version "0.0.0" + uid "" + "@kbn/expect@link:packages/kbn-expect": version "0.0.0" uid "" @@ -17569,9 +17593,9 @@ inquirer@^8.2.3: wrap-ansi "^7.0.0" install-artifact-from-github@^1.3.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/install-artifact-from-github/-/install-artifact-from-github-1.3.1.tgz#eefaad9af35d632e5d912ad1569c1de38c3c2462" - integrity sha512-3l3Bymg2eKDsN5wQuMfgGEj2x6l5MCAv0zPL6rxHESufFVlEAKW/6oY9F1aGgvY/EgWm5+eWGRjINveL4X7Hgg== + version "1.3.2" + resolved "https://registry.yarnpkg.com/install-artifact-from-github/-/install-artifact-from-github-1.3.2.tgz#1a16d9508e40330523a3017ae0d4713ccc64de82" + integrity sha512-yCFcLvqk0yQdxx0uJz4t9Z3adDMLAYrcGYv546uRXCSvxE+GqNYhhz/KmrGcUKGI/gVLR9n/e/zM9jX/+ASMJQ== internal-slot@^1.0.3: version "1.0.3" @@ -23960,10 +23984,10 @@ react-grid-layout@^1.3.4: react-draggable "^4.0.0" react-resizable "^3.0.4" -react-hook-form@^7.43.1: - version "7.43.1" - resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.43.1.tgz#0d0d7822f3f7fc05ffc41d5f012b49b90fcfa0f0" - integrity sha512-+s3+s8LLytRMriwwuSqeLStVjRXFGxgjjx2jED7Z+wz1J/88vpxieRQGvJVvzrzVxshZ0BRuocFERb779m2kNg== +react-hook-form@^7.43.2: + version "7.43.2" + resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.43.2.tgz#d8ff71956dc3de258dce19d4b1c7e1c6a0188e67" + integrity sha512-NvD3Oe2Y9hhqo2R4I4iJigDzSLpdMnzUpNMxlnzTbdiT7NT3BW0GxWCzEtwPudZMUPbZhNcSy1EcGAygyhDORg== react-input-autosize@^3.0.0: version "3.0.0" @@ -25489,10 +25513,10 @@ select-hose@^2.0.0: resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo= -selenium-webdriver@^4.8.0: - version "4.8.0" - resolved "https://registry.yarnpkg.com/selenium-webdriver/-/selenium-webdriver-4.8.0.tgz#386d57f23fe8edf5178f5bd06aae9ffaffbcb692" - integrity sha512-s/HL8WNwy1ggHR244+tAhjhyKMJnZLt1HKJ6Gn7nQgVjB/ybDF+46Uui0qI2J7AjPNJzlUmTncdC/jg/kKkn0A== +selenium-webdriver@^4.8.1: + version "4.8.1" + resolved "https://registry.yarnpkg.com/selenium-webdriver/-/selenium-webdriver-4.8.1.tgz#4b0a546c4ea747c44e9688c108f7a46b8d8244ab" + integrity sha512-p4MtfhCQdcV6xxkS7eI0tQN6+WNReRULLCAuT4RDGkrjfObBNXMJ3WT8XdK+aXTr5nnBKuh+PxIevM0EjJgkxA== dependencies: jszip "^3.10.0" tmp "^0.2.1" @@ -27102,10 +27126,10 @@ terser@^4.1.2, terser@^4.6.3: source-map "~0.6.1" source-map-support "~0.5.12" -terser@^5.14.1, terser@^5.16.3, terser@^5.3.4, terser@^5.9.0: - version "5.16.3" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.16.3.tgz#3266017a9b682edfe019b8ecddd2abaae7b39c6b" - integrity sha512-v8wWLaS/xt3nE9dgKEWhNUFP6q4kngO5B8eYFUuebsu7Dw/UNAnpUod6UHo04jSSkv8TzKHjZDSd7EXdDQAl8Q== +terser@^5.14.1, terser@^5.16.5, terser@^5.3.4, terser@^5.9.0: + version "5.16.5" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.16.5.tgz#1c285ca0655f467f92af1bbab46ab72d1cb08e5a" + integrity sha512-qcwfg4+RZa3YvlFh0qjifnzBHjKGNbtDo9yivMqMFDy9Q6FSaQWSB/j1xKhsoUFJIqDOM3TsN6D5xbrMrFcHbg== dependencies: "@jridgewell/source-map" "^0.3.2" acorn "^8.5.0" @@ -27738,9 +27762,9 @@ unc-path-regex@^0.1.2: integrity sha1-5z3T17DXxe2G+6xrCufYxqadUPo= undici@^5.11.0, undici@^5.5.1: - version "5.14.0" - resolved "https://registry.yarnpkg.com/undici/-/undici-5.14.0.tgz#1169d0cdee06a4ffdd30810f6228d57998884d00" - integrity sha512-yJlHYw6yXPPsuOH0x2Ib1Km61vu4hLiRRQoafs+WUgX1vO64vgnxiCEN9dpIrhZyHFsai3F0AEj4P9zy19enEQ== + version "5.20.0" + resolved "https://registry.yarnpkg.com/undici/-/undici-5.20.0.tgz#6327462f5ce1d3646bcdac99da7317f455bcc263" + integrity sha512-J3j60dYzuo6Eevbawwp1sdg16k5Tf768bxYK4TUJRH7cBM4kFCbf3mOnM/0E3vQYXvpxITbbWmBafaDbxLDz3g== dependencies: busboy "^1.6.0"