diff --git a/.buildkite/ftr_configs.yml b/.buildkite/ftr_configs.yml index aa35797d1f98..cc625a09fadd 100644 --- a/.buildkite/ftr_configs.yml +++ b/.buildkite/ftr_configs.yml @@ -142,6 +142,7 @@ enabled: - x-pack/test/detection_engine_api_integration/security_and_spaces/group8/config.ts - x-pack/test/detection_engine_api_integration/security_and_spaces/group9/config.ts - x-pack/test/detection_engine_api_integration/security_and_spaces/group10/config.ts + - x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/config.ts - x-pack/test/encrypted_saved_objects_api_integration/config.ts - x-pack/test/endpoint_api_integration_no_ingest/config.ts - x-pack/test/examples/config.ts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 04cd0d29b76d..63fdf3044be5 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -47,8 +47,6 @@ /x-pack/test/functional/apps/lens @elastic/kibana-vis-editors /x-pack/test/api_integration/apis/lens/ @elastic/kibana-vis-editors /test/functional/apps/visualize/ @elastic/kibana-vis-editors -/src/plugins/unified_field_list/ @elastic/kibana-vis-editors -/test/api_integration/apis/unified_field_list/ @elastic/kibana-vis-editors # Application Services /examples/bfetch_explorer/ @elastic/kibana-app-services diff --git a/.gitignore b/.gitignore index 82a13e661a5b..81b0d437f812 100644 --- a/.gitignore +++ b/.gitignore @@ -79,6 +79,12 @@ npm-debug.log* ## @cypress/snapshot from apm plugin /snapshots.js +# transpiled cypress config +x-pack/plugins/fleet/cypress.config.d.ts +x-pack/plugins/fleet/cypress.config.js +x-pack/plugins/osquery/cypress.config.d.ts +x-pack/plugins/osquery/cypress.config.js + # release notes script output report.csv report.asciidoc diff --git a/api_docs/actions.mdx b/api_docs/actions.mdx index fd854701b7da..d644934bb674 100644 --- a/api_docs/actions.mdx +++ b/api_docs/actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/actions title: "actions" image: https://source.unsplash.com/400x175/?github description: API docs for the actions plugin -date: 2022-10-24 +date: 2022-10-26 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 7fe0d5d40235..7a76f4d13c19 100644 --- a/api_docs/advanced_settings.mdx +++ b/api_docs/advanced_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/advancedSettings title: "advancedSettings" image: https://source.unsplash.com/400x175/?github description: API docs for the advancedSettings plugin -date: 2022-10-24 +date: 2022-10-26 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 dc79f19c864a..2e6699edb8a1 100644 --- a/api_docs/aiops.mdx +++ b/api_docs/aiops.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/aiops title: "aiops" image: https://source.unsplash.com/400x175/?github description: API docs for the aiops plugin -date: 2022-10-24 +date: 2022-10-26 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 3178e4112dea..56a1f7d4b752 100644 --- a/api_docs/alerting.devdocs.json +++ b/api_docs/alerting.devdocs.json @@ -2848,7 +2848,11 @@ "GetGlobalExecutionKPIParams", ") => Promise<{ success: number; unknown: number; failure: number; warning: number; activeAlerts: number; newAlerts: number; recoveredAlerts: number; erroredActions: number; triggeredActions: number; }>; getRuleExecutionKPI: ({ id, dateStart, dateEnd, filter }: ", "GetRuleExecutionKPIParams", - ") => Promise<{ success: number; unknown: number; failure: number; warning: number; activeAlerts: number; newAlerts: number; recoveredAlerts: number; erroredActions: number; triggeredActions: number; }>; bulkEdit: Promise<{ success: number; unknown: number; failure: number; warning: number; activeAlerts: number; newAlerts: number; recoveredAlerts: number; erroredActions: number; triggeredActions: number; }>; bulkDeleteRules: (options: ", + "BulkDeleteOptions", + ") => Promise<{ errors: ", + "BulkDeleteError", + "[]; total: number; taskIdsFailedToBeDeleted: string[]; }>; bulkEdit: ; \"GET /internal/apm/services/{serviceName}/metrics/serverless/active_instances\": ", + "ServerRoute", + "<\"GET /internal/apm/services/{serviceName}/metrics/serverless/active_instances\", ", + "TypeC", + "<{ path: ", + "TypeC", + "<{ serviceName: ", + "StringC", + "; }>; query: ", + "IntersectionC", + "<[", + "TypeC", + "<{ environment: ", + "UnionC", + "<[", + "LiteralC", + "<\"ENVIRONMENT_NOT_DEFINED\">, ", + "LiteralC", + "<\"ENVIRONMENT_ALL\">, ", + "BrandC", + "<", + "StringC", + ", ", + "NonEmptyStringBrand", + ">]>; }>, ", + "TypeC", + "<{ kuery: ", + "StringC", + "; }>, ", + "TypeC", + "<{ start: ", + "Type", + "; end: ", + "Type", + "; }>, ", + "PartialC", + "<{ serverlessId: ", + "StringC", + "; }>]>; }>, ", + { + "pluginId": "apm", + "scope": "server", + "docId": "kibApmPluginApi", + "section": "def-server.APMRouteHandlerResources", + "text": "APMRouteHandlerResources" + }, + ", { activeInstances: ", + "ActiveInstanceOverview", + "[]; timeseries: ", + "Coordinate", + "[]; }, ", + "APMRouteCreateOptions", + ">; \"GET /internal/apm/services/{serviceName}/metrics/serverless/functions_overview\": ", + "ServerRoute", + "<\"GET /internal/apm/services/{serviceName}/metrics/serverless/functions_overview\", ", + "TypeC", + "<{ path: ", + "TypeC", + "<{ serviceName: ", + "StringC", + "; }>; query: ", + "IntersectionC", + "<[", + "TypeC", + "<{ environment: ", + "UnionC", + "<[", + "LiteralC", + "<\"ENVIRONMENT_NOT_DEFINED\">, ", + "LiteralC", + "<\"ENVIRONMENT_ALL\">, ", + "BrandC", + "<", + "StringC", + ", ", + "NonEmptyStringBrand", + ">]>; }>, ", + "TypeC", + "<{ kuery: ", + "StringC", + "; }>, ", + "TypeC", + "<{ start: ", + "Type", + "; end: ", + "Type", + "; }>]>; }>, ", + { + "pluginId": "apm", + "scope": "server", + "docId": "kibApmPluginApi", + "section": "def-server.APMRouteHandlerResources", + "text": "APMRouteHandlerResources" + }, + ", { serverlessFunctionsOverview: { serverlessId: string; serverlessFunctionName: string; serverlessDurationAvg: number | null; billedDurationAvg: number | null; coldStartCount: number | null; avgMemoryUsed: number | undefined; memorySize: number | null; }[]; }, ", + "APMRouteCreateOptions", + ">; \"GET /internal/apm/services/{serviceName}/metrics/serverless/summary\": ", + "ServerRoute", + "<\"GET /internal/apm/services/{serviceName}/metrics/serverless/summary\", ", + "TypeC", + "<{ path: ", + "TypeC", + "<{ serviceName: ", + "StringC", + "; }>; query: ", + "IntersectionC", + "<[", + "TypeC", + "<{ environment: ", + "UnionC", + "<[", + "LiteralC", + "<\"ENVIRONMENT_NOT_DEFINED\">, ", + "LiteralC", + "<\"ENVIRONMENT_ALL\">, ", + "BrandC", + "<", + "StringC", + ", ", + "NonEmptyStringBrand", + ">]>; }>, ", + "TypeC", + "<{ kuery: ", + "StringC", + "; }>, ", + "TypeC", + "<{ start: ", + "Type", + "; end: ", + "Type", + "; }>, ", + "PartialC", + "<{ serverlessId: ", + "StringC", + "; }>]>; }>, ", + { + "pluginId": "apm", + "scope": "server", + "docId": "kibApmPluginApi", + "section": "def-server.APMRouteHandlerResources", + "text": "APMRouteHandlerResources" + }, + ", { memoryUsageAvgRate: number | undefined; serverlessFunctionsTotal: number | undefined; serverlessDurationAvg: number | null | undefined; billedDurationAvg: number | null | undefined; }, ", + "APMRouteCreateOptions", + ">; \"GET /internal/apm/services/{serviceName}/metrics/serverless/charts\": ", + "ServerRoute", + "<\"GET /internal/apm/services/{serviceName}/metrics/serverless/charts\", ", + "TypeC", + "<{ path: ", + "TypeC", + "<{ serviceName: ", + "StringC", + "; }>; query: ", + "IntersectionC", + "<[", + "TypeC", + "<{ environment: ", + "UnionC", + "<[", + "LiteralC", + "<\"ENVIRONMENT_NOT_DEFINED\">, ", + "LiteralC", + "<\"ENVIRONMENT_ALL\">, ", + "BrandC", + "<", + "StringC", + ", ", + "NonEmptyStringBrand", + ">]>; }>, ", + "TypeC", + "<{ kuery: ", + "StringC", + "; }>, ", + "TypeC", + "<{ start: ", + "Type", + "; end: ", + "Type", + "; }>, ", + "PartialC", + "<{ serverlessId: ", + "StringC", + "; }>]>; }>, ", + { + "pluginId": "apm", + "scope": "server", + "docId": "kibApmPluginApi", + "section": "def-server.APMRouteHandlerResources", + "text": "APMRouteHandlerResources" + }, + ", { charts: [", + "FetchAndTransformMetrics", + ", ", + "FetchAndTransformMetrics", + ", { series: { overallValue: number; data: { x: number; y: number | null | undefined; }[]; title: string; key: string; type: ", + "ChartType", + "; color: string; }[]; title: string; key: string; yUnit: ", + "YUnit", + "; description?: string | undefined; }, ", + "FetchAndTransformMetrics", + ", ", + "FetchAndTransformMetrics", + "]; }, ", + "APMRouteCreateOptions", ">; \"GET /internal/apm/services/{serviceName}/metrics/nodes\": ", "ServerRoute", "<\"GET /internal/apm/services/{serviceName}/metrics/nodes\", ", @@ -5065,8 +5269,6 @@ "PartialC", "<{ serviceNodeName: ", "StringC", - "; serviceRuntimeName: ", - "StringC", "; }>, ", "TypeC", "<{ environment: ", diff --git a/api_docs/apm.mdx b/api_docs/apm.mdx index dc40e7853ce2..913c686a7a00 100644 --- a/api_docs/apm.mdx +++ b/api_docs/apm.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/apm title: "apm" image: https://source.unsplash.com/400x175/?github description: API docs for the apm plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'apm'] --- import apmObj from './apm.devdocs.json'; @@ -21,7 +21,7 @@ Contact [APM UI](https://github.com/orgs/elastic/teams/apm-ui) for questions reg | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 38 | 0 | 38 | 53 | +| 38 | 0 | 38 | 56 | ## Client diff --git a/api_docs/banners.mdx b/api_docs/banners.mdx index 6b019f3a5956..0188bd4bbd56 100644 --- a/api_docs/banners.mdx +++ b/api_docs/banners.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/banners title: "banners" image: https://source.unsplash.com/400x175/?github description: API docs for the banners plugin -date: 2022-10-24 +date: 2022-10-26 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 987ca5ff1107..3a48b2eb21a9 100644 --- a/api_docs/bfetch.mdx +++ b/api_docs/bfetch.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/bfetch title: "bfetch" image: https://source.unsplash.com/400x175/?github description: API docs for the bfetch plugin -date: 2022-10-24 +date: 2022-10-26 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 fc205f73fabe..f9ea21275a29 100644 --- a/api_docs/canvas.mdx +++ b/api_docs/canvas.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/canvas title: "canvas" image: https://source.unsplash.com/400x175/?github description: API docs for the canvas plugin -date: 2022-10-24 +date: 2022-10-26 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 8119cb3966c0..17c2f0bc24b9 100644 --- a/api_docs/cases.mdx +++ b/api_docs/cases.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cases title: "cases" image: https://source.unsplash.com/400x175/?github description: API docs for the cases plugin -date: 2022-10-24 +date: 2022-10-26 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 4d9ddf5e4dde..1292beac03c9 100644 --- a/api_docs/charts.mdx +++ b/api_docs/charts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/charts title: "charts" image: https://source.unsplash.com/400x175/?github description: API docs for the charts plugin -date: 2022-10-24 +date: 2022-10-26 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 38641d23ed38..0be814b32854 100644 --- a/api_docs/cloud.mdx +++ b/api_docs/cloud.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloud title: "cloud" image: https://source.unsplash.com/400x175/?github description: API docs for the cloud plugin -date: 2022-10-24 +date: 2022-10-26 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 426269cb3139..c59647a5538b 100644 --- a/api_docs/cloud_chat.mdx +++ b/api_docs/cloud_chat.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudChat title: "cloudChat" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudChat plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudChat'] --- import cloudChatObj from './cloud_chat.devdocs.json'; diff --git a/api_docs/cloud_experiments.mdx b/api_docs/cloud_experiments.mdx index 1e421a3f35ea..8427d221e4f6 100644 --- a/api_docs/cloud_experiments.mdx +++ b/api_docs/cloud_experiments.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudExperiments title: "cloudExperiments" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudExperiments plugin -date: 2022-10-24 +date: 2022-10-26 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 a22d44919bd8..58e7f8a3c0e9 100644 --- a/api_docs/cloud_security_posture.mdx +++ b/api_docs/cloud_security_posture.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudSecurityPosture title: "cloudSecurityPosture" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudSecurityPosture plugin -date: 2022-10-24 +date: 2022-10-26 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 abb27f5b269f..cd10bdee5aeb 100644 --- a/api_docs/console.mdx +++ b/api_docs/console.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/console title: "console" image: https://source.unsplash.com/400x175/?github description: API docs for the console plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'console'] --- import consoleObj from './console.devdocs.json'; diff --git a/api_docs/controls.devdocs.json b/api_docs/controls.devdocs.json index c83780deefca..09686e7655b5 100644 --- a/api_docs/controls.devdocs.json +++ b/api_docs/controls.devdocs.json @@ -258,7 +258,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">>" + ", any>>" ], "path": "src/plugins/controls/public/control_group/embeddable/control_group_container.tsx", "deprecated": false, @@ -939,7 +939,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">, unknown>" + ", any>, unknown>" ], "path": "src/plugins/controls/public/control_group/embeddable/control_group_container_factory.ts", "deprecated": false, @@ -1337,7 +1337,7 @@ "section": "def-public.ControlOutput", "text": "ControlOutput" }, - ">" + ", any>" ], "path": "src/plugins/controls/public/options_list/embeddable/options_list_embeddable.tsx", "deprecated": false, @@ -1632,7 +1632,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">, unknown>,", + ", any>, unknown>,", { "pluginId": "controls", "scope": "public", @@ -2245,7 +2245,7 @@ "section": "def-public.ControlOutput", "text": "ControlOutput" }, - ">" + ", any>" ], "path": "src/plugins/controls/public/range_slider/embeddable/range_slider_embeddable.tsx", "deprecated": false, @@ -2540,7 +2540,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">, unknown>,", + ", any>, unknown>,", { "pluginId": "controls", "scope": "public", @@ -3739,6 +3739,34 @@ "path": "src/plugins/controls/common/options_list/types.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "controls", + "id": "def-public.OptionsListEmbeddableInput.hideExclude", + "type": "CompoundType", + "tags": [], + "label": "hideExclude", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/controls/common/options_list/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "controls", + "id": "def-public.OptionsListEmbeddableInput.exclude", + "type": "CompoundType", + "tags": [], + "label": "exclude", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/controls/common/options_list/types.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -3885,7 +3913,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - " & { isChained?: (() => boolean) | undefined; renderPrepend?: (() => React.ReactNode) | undefined; }" + " & { isChained?: (() => boolean) | undefined; renderPrepend?: (() => React.ReactNode) | undefined; }" ], "path": "src/plugins/controls/public/types.ts", "deprecated": false, @@ -4838,6 +4866,34 @@ "path": "src/plugins/controls/common/options_list/types.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "controls", + "id": "def-common.OptionsListEmbeddableInput.hideExclude", + "type": "CompoundType", + "tags": [], + "label": "hideExclude", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/controls/common/options_list/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "controls", + "id": "def-common.OptionsListEmbeddableInput.exclude", + "type": "CompoundType", + "tags": [], + "label": "exclude", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/controls/common/options_list/types.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false diff --git a/api_docs/controls.mdx b/api_docs/controls.mdx index 1fd8dba3211e..12050905e2c7 100644 --- a/api_docs/controls.mdx +++ b/api_docs/controls.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/controls title: "controls" image: https://source.unsplash.com/400x175/?github description: API docs for the controls plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'controls'] --- import controlsObj from './controls.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-prese | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 229 | 0 | 220 | 7 | +| 233 | 0 | 224 | 7 | ## Client diff --git a/api_docs/core.devdocs.json b/api_docs/core.devdocs.json index a84a901314a3..969cd53cae5b 100644 --- a/api_docs/core.devdocs.json +++ b/api_docs/core.devdocs.json @@ -548,6 +548,10 @@ "plugin": "@kbn/core-analytics-browser-internal", "path": "packages/core/analytics/core-analytics-browser-internal/src/track_clicks.ts" }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.ts" + }, { "plugin": "@kbn/core-analytics-browser-internal", "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts" @@ -684,6 +688,14 @@ "plugin": "@kbn/core-analytics-browser-internal", "path": "packages/core/analytics/core-analytics-browser-internal/src/track_clicks.test.ts" }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts" + }, { "plugin": "@kbn/core-analytics-server-internal", "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.test.mocks.ts" @@ -922,6 +934,10 @@ "plugin": "telemetry", "path": "src/plugins/telemetry/server/plugin.ts" }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.ts" + }, { "plugin": "@kbn/core-analytics-browser-internal", "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts" @@ -1076,19 +1092,19 @@ }, { "plugin": "@kbn/core-analytics-browser-internal", - "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.ts" + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts" }, { "plugin": "@kbn/core-analytics-browser-internal", - "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.ts" + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts" }, { "plugin": "@kbn/core-analytics-browser-internal", - "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.ts" + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts" }, { "plugin": "@kbn/core-analytics-browser-internal", - "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.ts" + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts" }, { "plugin": "@kbn/core-analytics-server-internal", @@ -4138,6 +4154,64 @@ ], "returnComment": [] }, + { + "parentPluginId": "core", + "id": "def-public.ChromeStart.getGlobalHelpExtensionMenuLinks$", + "type": "Function", + "tags": [], + "label": "getGlobalHelpExtensionMenuLinks$", + "description": [ + "\nGet the list of the registered global help extension menu links" + ], + "signature": [ + "() => ", + "Observable", + "<", + "ChromeGlobalHelpExtensionMenuLink", + "[]>" + ], + "path": "node_modules/@types/kbn__core-chrome-browser/index.d.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "core", + "id": "def-public.ChromeStart.registerGlobalHelpExtensionMenuLink", + "type": "Function", + "tags": [], + "label": "registerGlobalHelpExtensionMenuLink", + "description": [ + "\nAppend a global help extension menu link" + ], + "signature": [ + "(globalHelpExtensionMenuLink: ", + "ChromeGlobalHelpExtensionMenuLink", + ") => void" + ], + "path": "node_modules/@types/kbn__core-chrome-browser/index.d.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "core", + "id": "def-public.ChromeStart.registerGlobalHelpExtensionMenuLink.$1", + "type": "Object", + "tags": [], + "label": "globalHelpExtensionMenuLink", + "description": [], + "signature": [ + "ChromeGlobalHelpExtensionMenuLink" + ], + "path": "node_modules/@types/kbn__core-chrome-browser/index.d.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, { "parentPluginId": "core", "id": "def-public.ChromeStart.getHelpExtension$", @@ -4145,7 +4219,7 @@ "tags": [], "label": "getHelpExtension$", "description": [ - "\nGet an observable of the current custom help conttent" + "\nGet an observable of the current custom help content" ], "signature": [ "() => ", @@ -19349,6 +19423,10 @@ "plugin": "@kbn/core-analytics-browser-internal", "path": "packages/core/analytics/core-analytics-browser-internal/src/track_clicks.ts" }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.ts" + }, { "plugin": "@kbn/core-analytics-browser-internal", "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts" @@ -19485,6 +19563,14 @@ "plugin": "@kbn/core-analytics-browser-internal", "path": "packages/core/analytics/core-analytics-browser-internal/src/track_clicks.test.ts" }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts" + }, { "plugin": "@kbn/core-analytics-server-internal", "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.test.mocks.ts" @@ -19723,6 +19809,10 @@ "plugin": "telemetry", "path": "src/plugins/telemetry/server/plugin.ts" }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.ts" + }, { "plugin": "@kbn/core-analytics-browser-internal", "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts" @@ -19877,19 +19967,19 @@ }, { "plugin": "@kbn/core-analytics-browser-internal", - "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.ts" + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts" }, { "plugin": "@kbn/core-analytics-browser-internal", - "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.ts" + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts" }, { "plugin": "@kbn/core-analytics-browser-internal", - "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.ts" + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts" }, { "plugin": "@kbn/core-analytics-browser-internal", - "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.ts" + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts" }, { "plugin": "@kbn/core-analytics-server-internal", diff --git a/api_docs/core.mdx b/api_docs/core.mdx index 8f9cee2a6279..b5366b9e06b7 100644 --- a/api_docs/core.mdx +++ b/api_docs/core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/core title: "core" image: https://source.unsplash.com/400x175/?github description: API docs for the core plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'core'] --- import coreObj from './core.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) for que | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 2697 | 0 | 23 | 0 | +| 2700 | 0 | 23 | 0 | ## Client diff --git a/api_docs/custom_integrations.mdx b/api_docs/custom_integrations.mdx index 203d145d5ec5..9e79e37851ad 100644 --- a/api_docs/custom_integrations.mdx +++ b/api_docs/custom_integrations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/customIntegrations title: "customIntegrations" image: https://source.unsplash.com/400x175/?github description: API docs for the customIntegrations plugin -date: 2022-10-24 +date: 2022-10-26 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 e7433c21e928..50bc34c4657b 100644 --- a/api_docs/dashboard.mdx +++ b/api_docs/dashboard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dashboard title: "dashboard" image: https://source.unsplash.com/400x175/?github description: API docs for the dashboard plugin -date: 2022-10-24 +date: 2022-10-26 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 bb048786661f..f5ce0d2f7bbc 100644 --- a/api_docs/dashboard_enhanced.mdx +++ b/api_docs/dashboard_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dashboardEnhanced title: "dashboardEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the dashboardEnhanced plugin -date: 2022-10-24 +date: 2022-10-26 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 05b50b545877..5b1041ce4f85 100644 --- a/api_docs/data.devdocs.json +++ b/api_docs/data.devdocs.json @@ -1400,6 +1400,36 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "data", + "id": "def-public.AggConfigs.samplerConfig", + "type": "Object", + "tags": [], + "label": "samplerConfig", + "description": [], + "signature": [ + "{ probability: number; seed: number | undefined; }" + ], + "path": "src/plugins/data/common/search/aggs/agg_configs.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-public.AggConfigs.isSamplingEnabled", + "type": "Function", + "tags": [], + "label": "isSamplingEnabled", + "description": [], + "signature": [ + "() => boolean" + ], + "path": "src/plugins/data/common/search/aggs/agg_configs.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, { "parentPluginId": "data", "id": "def-public.AggConfigs.setTimeFields", @@ -11501,10 +11531,6 @@ "deprecated": true, "trackAdoption": false, "references": [ - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/kibana_services.ts" - }, { "plugin": "infra", "path": "x-pack/plugins/infra/public/hooks/use_kibana_index_patterns.ts" @@ -12255,50 +12281,6 @@ "plugin": "infra", "path": "x-pack/plugins/infra/common/log_views/resolved_log_view.ts" }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/util/scaling_form.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/top_hits_form.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/trigger_actions/visualize_geo_field_action.ts" - }, { "plugin": "dataVisualizer", "path": "x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/full_time_range_selector/full_time_range_selector_service.ts" @@ -12531,6 +12513,14 @@ "plugin": "apm", "path": "x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts" }, + { + "plugin": "canvas", + "path": "x-pack/plugins/canvas/public/services/kibana/data_views.ts" + }, + { + "plugin": "canvas", + "path": "x-pack/plugins/canvas/public/services/kibana/data_views.ts" + }, { "plugin": "reporting", "path": "x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.ts" @@ -12731,6 +12721,38 @@ "plugin": "ml", "path": "x-pack/plugins/ml/public/application/services/new_job_capabilities/remove_nested_field_children.test.ts" }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_kpi.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_with_reference_lines.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/test_formula_metric_attribute.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes/single_metric_attributes.test.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes/single_metric_attributes.test.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/mobile_test_attribute.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_cwv.ts" + }, { "plugin": "transform", "path": "x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/common.test.ts" @@ -19727,50 +19749,6 @@ "plugin": "infra", "path": "x-pack/plugins/infra/common/log_views/resolved_log_view.ts" }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/util/scaling_form.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/top_hits_form.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/trigger_actions/visualize_geo_field_action.ts" - }, { "plugin": "dataVisualizer", "path": "x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/full_time_range_selector/full_time_range_selector_service.ts" @@ -20003,6 +19981,14 @@ "plugin": "apm", "path": "x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts" }, + { + "plugin": "canvas", + "path": "x-pack/plugins/canvas/public/services/kibana/data_views.ts" + }, + { + "plugin": "canvas", + "path": "x-pack/plugins/canvas/public/services/kibana/data_views.ts" + }, { "plugin": "reporting", "path": "x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.ts" @@ -20203,6 +20189,38 @@ "plugin": "ml", "path": "x-pack/plugins/ml/public/application/services/new_job_capabilities/remove_nested_field_children.test.ts" }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_kpi.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_with_reference_lines.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/test_formula_metric_attribute.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes/single_metric_attributes.test.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes/single_metric_attributes.test.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/mobile_test_attribute.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_cwv.ts" + }, { "plugin": "transform", "path": "x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/common.test.ts" diff --git a/api_docs/data.mdx b/api_docs/data.mdx index 4c69b81b57a2..acd1bab4863e 100644 --- a/api_docs/data.mdx +++ b/api_docs/data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data title: "data" image: https://source.unsplash.com/400x175/?github description: API docs for the data plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data'] --- import dataObj from './data.devdocs.json'; @@ -21,7 +21,7 @@ Contact [App Services](https://github.com/orgs/elastic/teams/kibana-app-services | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 3245 | 33 | 2517 | 24 | +| 3251 | 33 | 2523 | 24 | ## Client diff --git a/api_docs/data_query.mdx b/api_docs/data_query.mdx index 3a2f06a82a30..86aee5fe6fdc 100644 --- a/api_docs/data_query.mdx +++ b/api_docs/data_query.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data-query title: "data.query" image: https://source.unsplash.com/400x175/?github description: API docs for the data.query plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.query'] --- import dataQueryObj from './data_query.devdocs.json'; @@ -21,7 +21,7 @@ Contact [App Services](https://github.com/orgs/elastic/teams/kibana-app-services | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 3245 | 33 | 2517 | 24 | +| 3251 | 33 | 2523 | 24 | ## Client diff --git a/api_docs/data_search.devdocs.json b/api_docs/data_search.devdocs.json index d3cf37cb35cd..9aa18ac22057 100644 --- a/api_docs/data_search.devdocs.json +++ b/api_docs/data_search.devdocs.json @@ -5052,6 +5052,36 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "data", + "id": "def-common.AggConfigs.samplerConfig", + "type": "Object", + "tags": [], + "label": "samplerConfig", + "description": [], + "signature": [ + "{ probability: number; seed: number | undefined; }" + ], + "path": "src/plugins/data/common/search/aggs/agg_configs.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggConfigs.isSamplingEnabled", + "type": "Function", + "tags": [], + "label": "isSamplingEnabled", + "description": [], + "signature": [ + "() => boolean" + ], + "path": "src/plugins/data/common/search/aggs/agg_configs.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, { "parentPluginId": "data", "id": "def-common.AggConfigs.setTimeFields", @@ -16775,6 +16805,34 @@ "path": "src/plugins/data/common/search/aggs/agg_configs.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggConfigsOptions.probability", + "type": "number", + "tags": [], + "label": "probability", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "src/plugins/data/common/search/aggs/agg_configs.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggConfigsOptions.samplerSeed", + "type": "number", + "tags": [], + "label": "samplerSeed", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "src/plugins/data/common/search/aggs/agg_configs.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false diff --git a/api_docs/data_search.mdx b/api_docs/data_search.mdx index 81ed7ca0a25f..4a3174554418 100644 --- a/api_docs/data_search.mdx +++ b/api_docs/data_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data-search title: "data.search" image: https://source.unsplash.com/400x175/?github description: API docs for the data.search plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.search'] --- import dataSearchObj from './data_search.devdocs.json'; @@ -21,7 +21,7 @@ Contact [App Services](https://github.com/orgs/elastic/teams/kibana-app-services | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 3245 | 33 | 2517 | 24 | +| 3251 | 33 | 2523 | 24 | ## Client diff --git a/api_docs/data_view_editor.mdx b/api_docs/data_view_editor.mdx index 66cdf9555592..6df101f582fa 100644 --- a/api_docs/data_view_editor.mdx +++ b/api_docs/data_view_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewEditor title: "dataViewEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewEditor plugin -date: 2022-10-24 +date: 2022-10-26 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 0a19c0b9a13d..07b0de3504aa 100644 --- a/api_docs/data_view_field_editor.mdx +++ b/api_docs/data_view_field_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewFieldEditor title: "dataViewFieldEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewFieldEditor plugin -date: 2022-10-24 +date: 2022-10-26 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 30c65e7cb62d..f9201d249710 100644 --- a/api_docs/data_view_management.mdx +++ b/api_docs/data_view_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewManagement title: "dataViewManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewManagement plugin -date: 2022-10-24 +date: 2022-10-26 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 38860d23cee6..e66691e1282d 100644 --- a/api_docs/data_views.devdocs.json +++ b/api_docs/data_views.devdocs.json @@ -253,50 +253,6 @@ "plugin": "infra", "path": "x-pack/plugins/infra/common/log_views/resolved_log_view.ts" }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/util/scaling_form.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/top_hits_form.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/trigger_actions/visualize_geo_field_action.ts" - }, { "plugin": "dataVisualizer", "path": "x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/full_time_range_selector/full_time_range_selector_service.ts" @@ -529,6 +485,14 @@ "plugin": "apm", "path": "x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts" }, + { + "plugin": "canvas", + "path": "x-pack/plugins/canvas/public/services/kibana/data_views.ts" + }, + { + "plugin": "canvas", + "path": "x-pack/plugins/canvas/public/services/kibana/data_views.ts" + }, { "plugin": "reporting", "path": "x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.ts" @@ -729,6 +693,38 @@ "plugin": "ml", "path": "x-pack/plugins/ml/public/application/services/new_job_capabilities/remove_nested_field_children.test.ts" }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_kpi.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_with_reference_lines.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/test_formula_metric_attribute.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes/single_metric_attributes.test.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes/single_metric_attributes.test.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/mobile_test_attribute.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_cwv.ts" + }, { "plugin": "transform", "path": "x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/common.test.ts" @@ -8168,50 +8164,6 @@ "plugin": "infra", "path": "x-pack/plugins/infra/common/log_views/resolved_log_view.ts" }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/util/scaling_form.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/top_hits_form.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/trigger_actions/visualize_geo_field_action.ts" - }, { "plugin": "dataVisualizer", "path": "x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/full_time_range_selector/full_time_range_selector_service.ts" @@ -8444,6 +8396,14 @@ "plugin": "apm", "path": "x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts" }, + { + "plugin": "canvas", + "path": "x-pack/plugins/canvas/public/services/kibana/data_views.ts" + }, + { + "plugin": "canvas", + "path": "x-pack/plugins/canvas/public/services/kibana/data_views.ts" + }, { "plugin": "reporting", "path": "x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.ts" @@ -8644,6 +8604,38 @@ "plugin": "ml", "path": "x-pack/plugins/ml/public/application/services/new_job_capabilities/remove_nested_field_children.test.ts" }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_kpi.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_with_reference_lines.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/test_formula_metric_attribute.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes/single_metric_attributes.test.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes/single_metric_attributes.test.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/mobile_test_attribute.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_cwv.ts" + }, { "plugin": "transform", "path": "x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/common.test.ts" @@ -15230,50 +15222,6 @@ "plugin": "infra", "path": "x-pack/plugins/infra/common/log_views/resolved_log_view.ts" }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/util/scaling_form.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/top_hits_form.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/trigger_actions/visualize_geo_field_action.ts" - }, { "plugin": "dataVisualizer", "path": "x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/full_time_range_selector/full_time_range_selector_service.ts" @@ -15506,6 +15454,14 @@ "plugin": "apm", "path": "x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts" }, + { + "plugin": "canvas", + "path": "x-pack/plugins/canvas/public/services/kibana/data_views.ts" + }, + { + "plugin": "canvas", + "path": "x-pack/plugins/canvas/public/services/kibana/data_views.ts" + }, { "plugin": "reporting", "path": "x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.ts" @@ -15706,6 +15662,38 @@ "plugin": "ml", "path": "x-pack/plugins/ml/public/application/services/new_job_capabilities/remove_nested_field_children.test.ts" }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_kpi.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_with_reference_lines.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/test_formula_metric_attribute.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes/single_metric_attributes.test.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes/single_metric_attributes.test.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/mobile_test_attribute.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_cwv.ts" + }, { "plugin": "transform", "path": "x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/common.test.ts" diff --git a/api_docs/data_views.mdx b/api_docs/data_views.mdx index 858ae4272790..5699d90b9ec6 100644 --- a/api_docs/data_views.mdx +++ b/api_docs/data_views.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViews title: "dataViews" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViews plugin -date: 2022-10-24 +date: 2022-10-26 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 a2a80c5154d4..6a11fbd62ab9 100644 --- a/api_docs/data_visualizer.mdx +++ b/api_docs/data_visualizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataVisualizer title: "dataVisualizer" image: https://source.unsplash.com/400x175/?github description: API docs for the dataVisualizer plugin -date: 2022-10-24 +date: 2022-10-26 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 5b11da9cc889..b4638d22c126 100644 --- a/api_docs/deprecations_by_api.mdx +++ b/api_docs/deprecations_by_api.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsByApi slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-api title: Deprecated API usage by API description: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -27,13 +27,13 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | savedObjects, embeddable, fleet, visualizations, dashboard, infra, canvas, graph, actions, alerting, enterpriseSearch, securitySolution, taskManager, savedSearch, ml, @kbn/core-saved-objects-server-internal | - | | | savedObjects, embeddable, fleet, visualizations, dashboard, infra, canvas, graph, actions, alerting, enterpriseSearch, securitySolution, taskManager, savedSearch, ml, @kbn/core-saved-objects-server-internal | - | | | discover, maps, monitoring | - | -| | securitySolution, timelines, lists, threatIntelligence, dataViews, dataViewEditor, unifiedSearch, triggersActionsUi, savedObjectsManagement, unifiedFieldList, aiops, presentationUtil, controls, lens, observability, infra, maps, dataVisualizer, ml, fleet, visTypeTimeseries, apm, reporting, graph, stackAlerts, synthetics, transform, upgradeAssistant, ux, dataViewManagement, inputControlVis, visDefaultEditor, visTypeTimelion, visTypeVega, discover, data | - | -| | securitySolution, timelines, lists, threatIntelligence, dataViews, dataViewEditor, unifiedSearch, triggersActionsUi, savedObjectsManagement, unifiedFieldList, aiops, presentationUtil, controls, lens, observability, infra, maps, dataVisualizer, ml, fleet, visTypeTimeseries, apm, reporting, graph, stackAlerts, synthetics, transform, upgradeAssistant, ux, dataViewManagement, inputControlVis, visDefaultEditor, visTypeTimelion, visTypeVega, discover, data | - | -| | securitySolution, timelines, lists, threatIntelligence, data, dataViewEditor, unifiedSearch, triggersActionsUi, savedObjectsManagement, unifiedFieldList, aiops, presentationUtil, controls, lens, observability, infra, maps, dataVisualizer, ml, fleet, visTypeTimeseries, apm, reporting, graph, stackAlerts, synthetics, transform, upgradeAssistant, ux, dataViewManagement, inputControlVis, visDefaultEditor, visTypeTimelion, visTypeVega, discover | - | +| | securitySolution, timelines, lists, threatIntelligence, dataViews, dataViewEditor, unifiedSearch, triggersActionsUi, savedObjectsManagement, unifiedFieldList, aiops, presentationUtil, controls, lens, observability, infra, dataVisualizer, ml, fleet, visTypeTimeseries, apm, canvas, reporting, graph, stackAlerts, synthetics, transform, upgradeAssistant, ux, maps, dataViewManagement, inputControlVis, visDefaultEditor, visTypeTimelion, visTypeVega, discover, data | - | +| | securitySolution, timelines, lists, threatIntelligence, dataViews, dataViewEditor, unifiedSearch, triggersActionsUi, savedObjectsManagement, unifiedFieldList, aiops, presentationUtil, controls, lens, observability, infra, dataVisualizer, ml, fleet, visTypeTimeseries, apm, canvas, reporting, graph, stackAlerts, synthetics, transform, upgradeAssistant, ux, maps, dataViewManagement, inputControlVis, visDefaultEditor, visTypeTimelion, visTypeVega, discover, data | - | +| | securitySolution, timelines, lists, threatIntelligence, data, dataViewEditor, unifiedSearch, triggersActionsUi, savedObjectsManagement, unifiedFieldList, aiops, presentationUtil, controls, lens, observability, infra, dataVisualizer, ml, fleet, visTypeTimeseries, apm, canvas, reporting, graph, stackAlerts, synthetics, transform, upgradeAssistant, ux, maps, dataViewManagement, inputControlVis, visDefaultEditor, visTypeTimelion, visTypeVega, discover | - | | | data, discover, embeddable | - | | | advancedSettings, discover | - | | | advancedSettings, discover | - | -| | maps, infra, graph, securitySolution, stackAlerts, inputControlVis, savedObjects | - | +| | infra, graph, securitySolution, stackAlerts, inputControlVis, savedObjects | - | | | securitySolution | - | | | encryptedSavedObjects, actions, data, ml, logstash, securitySolution, cloudChat | - | | | dashboard, dataVisualizer, stackAlerts, expressionPartitionVis | - | @@ -56,8 +56,6 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | spaces, savedObjectsManagement | - | | | spaces, ml, canvas, osquery | - | | | actions, ml, savedObjectsTagging, enterpriseSearch | - | -| | dataViewManagement | - | -| | dataViewManagement | - | | | canvas | - | | | canvas | - | | | canvas | - | @@ -68,6 +66,8 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | canvas | - | | | canvas | - | | | canvas | - | +| | dataViewManagement | - | +| | dataViewManagement | - | | | enterpriseSearch | - | | | console, @kbn/core-elasticsearch-server-internal | - | | | spaces, security, alerting | 8.8.0 | diff --git a/api_docs/deprecations_by_plugin.mdx b/api_docs/deprecations_by_plugin.mdx index 8855f9150836..26f9d29a9ef0 100644 --- a/api_docs/deprecations_by_plugin.mdx +++ b/api_docs/deprecations_by_plugin.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsByPlugin slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-plugin title: Deprecated API usage by plugin description: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -170,6 +170,9 @@ so TS and code-reference navigation might not highlight them. | | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| +| | [data_views.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/public/services/kibana/data_views.ts#:~:text=title), [data_views.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/public/services/kibana/data_views.ts#:~:text=title), [data_views.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/public/services/kibana/data_views.ts#:~:text=title), [data_views.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/public/services/kibana/data_views.ts#:~:text=title) | - | +| | [data_views.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/public/services/kibana/data_views.ts#:~:text=title), [data_views.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/public/services/kibana/data_views.ts#:~:text=title), [data_views.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/public/services/kibana/data_views.ts#:~:text=title), [data_views.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/public/services/kibana/data_views.ts#:~:text=title) | - | +| | [data_views.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/public/services/kibana/data_views.ts#:~:text=title), [data_views.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/public/services/kibana/data_views.ts#:~:text=title) | - | | | [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/canvas_plugin_src/functions/server/demodata/index.ts#:~:text=context), [embeddable.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/canvas_plugin_src/functions/external/embeddable.ts#:~:text=context), [esdocs.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/esdocs.ts#:~:text=context), [escount.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/escount.ts#:~:text=context), [filters.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/common/functions/filters.ts#:~:text=context), [neq.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/canvas_plugin_src/functions/common/neq.ts#:~:text=context), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/canvas_plugin_src/functions/server/pointseries/index.ts#:~:text=context) | - | | | [setup_expressions.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/public/setup_expressions.ts#:~:text=getFunction) | - | | | [functions.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/server/routes/functions/functions.ts#:~:text=getFunctions), [functions.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/server/routes/functions/functions.ts#:~:text=getFunctions), [functions.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/server/routes/functions/functions.test.ts#:~:text=getFunctions) | - | @@ -532,12 +535,11 @@ so TS and code-reference navigation might not highlight them. | | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| | | [global_sync.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/routes/map_page/url_state/global_sync.ts#:~:text=syncQueryStateWithUrl), [global_sync.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/routes/map_page/url_state/global_sync.ts#:~:text=syncQueryStateWithUrl) | - | -| | [kibana_services.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/kibana_services.ts#:~:text=indexPatterns) | - | -| | [es_geo_grid_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx#:~:text=title), [scaling_form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/util/scaling_form.tsx#:~:text=title), [top_hits_form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/top_hits_form.tsx#:~:text=title), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=title), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=title), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=title), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=title), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=title), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=title), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=title)+ 14 more | - | +| | [es_tooltip_property.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/tooltips/es_tooltip_property.test.ts#:~:text=title), [es_tooltip_property.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/tooltips/es_tooltip_property.test.ts#:~:text=title) | - | | | [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=flattenHit), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=flattenHit), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=flattenHit), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=flattenHit) | - | -| | [es_geo_grid_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx#:~:text=title), [scaling_form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/util/scaling_form.tsx#:~:text=title), [top_hits_form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/top_hits_form.tsx#:~:text=title), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=title), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=title), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=title), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=title), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=title), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=title), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=title)+ 14 more | - | +| | [es_tooltip_property.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/tooltips/es_tooltip_property.test.ts#:~:text=title), [es_tooltip_property.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/tooltips/es_tooltip_property.test.ts#:~:text=title) | - | | | [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=flattenHit), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=flattenHit), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=flattenHit), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=flattenHit) | - | -| | [es_geo_grid_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx#:~:text=title), [scaling_form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/util/scaling_form.tsx#:~:text=title), [top_hits_form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/top_hits_form.tsx#:~:text=title), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=title), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=title), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=title), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=title), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=title), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=title), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=title)+ 2 more | - | +| | [es_tooltip_property.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/tooltips/es_tooltip_property.test.ts#:~:text=title) | - | | | [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=flattenHit), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=flattenHit) | - | | | [render_app.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/render_app.tsx#:~:text=onAppLeave), [map_app.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/routes/map_page/map_app/map_app.tsx#:~:text=onAppLeave), [map_page.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/routes/map_page/map_page.tsx#:~:text=onAppLeave) | 8.8.0 | | | [saved_object_migrations.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/server/saved_objects/saved_object_migrations.ts#:~:text=warning) | 8.8.0 | @@ -589,9 +591,9 @@ so TS and code-reference navigation might not highlight them. | | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| -| | [observability_data_views.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts#:~:text=title), [report_definition_field.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/report_definition_field.tsx#:~:text=title), [use_filter_values.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/use_filter_values.ts#:~:text=title), [filter_value_btn.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx#:~:text=title), [observability_data_views.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts#:~:text=title), [report_definition_field.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/report_definition_field.tsx#:~:text=title), [use_filter_values.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/use_filter_values.ts#:~:text=title), [filter_value_btn.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx#:~:text=title) | - | -| | [observability_data_views.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts#:~:text=title), [report_definition_field.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/report_definition_field.tsx#:~:text=title), [use_filter_values.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/use_filter_values.ts#:~:text=title), [filter_value_btn.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx#:~:text=title), [observability_data_views.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts#:~:text=title), [report_definition_field.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/report_definition_field.tsx#:~:text=title), [use_filter_values.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/use_filter_values.ts#:~:text=title), [filter_value_btn.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx#:~:text=title) | - | -| | [observability_data_views.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts#:~:text=title), [report_definition_field.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/report_definition_field.tsx#:~:text=title), [use_filter_values.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/use_filter_values.ts#:~:text=title), [filter_value_btn.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx#:~:text=title) | - | +| | [observability_data_views.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts#:~:text=title), [report_definition_field.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/report_definition_field.tsx#:~:text=title), [use_filter_values.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/use_filter_values.ts#:~:text=title), [filter_value_btn.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx#:~:text=title), [sample_attribute.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute.ts#:~:text=title), [sample_attribute_kpi.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_kpi.ts#:~:text=title), [sample_attribute_with_reference_lines.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_with_reference_lines.ts#:~:text=title), [test_formula_metric_attribute.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/test_formula_metric_attribute.ts#:~:text=title), [single_metric_attributes.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes/single_metric_attributes.test.ts#:~:text=title), [single_metric_attributes.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes/single_metric_attributes.test.ts#:~:text=title)+ 14 more | - | +| | [observability_data_views.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts#:~:text=title), [report_definition_field.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/report_definition_field.tsx#:~:text=title), [use_filter_values.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/use_filter_values.ts#:~:text=title), [filter_value_btn.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx#:~:text=title), [sample_attribute.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute.ts#:~:text=title), [sample_attribute_kpi.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_kpi.ts#:~:text=title), [sample_attribute_with_reference_lines.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_with_reference_lines.ts#:~:text=title), [test_formula_metric_attribute.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/test_formula_metric_attribute.ts#:~:text=title), [single_metric_attributes.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes/single_metric_attributes.test.ts#:~:text=title), [single_metric_attributes.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes/single_metric_attributes.test.ts#:~:text=title)+ 14 more | - | +| | [observability_data_views.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts#:~:text=title), [report_definition_field.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/report_definition_field.tsx#:~:text=title), [use_filter_values.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/use_filter_values.ts#:~:text=title), [filter_value_btn.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx#:~:text=title), [sample_attribute.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute.ts#:~:text=title), [sample_attribute_kpi.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_kpi.ts#:~:text=title), [sample_attribute_with_reference_lines.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_with_reference_lines.ts#:~:text=title), [test_formula_metric_attribute.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/test_formula_metric_attribute.ts#:~:text=title), [single_metric_attributes.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes/single_metric_attributes.test.ts#:~:text=title), [single_metric_attributes.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes/single_metric_attributes.test.ts#:~:text=title)+ 2 more | - | | | [use_discover_link.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_discover_link.tsx#:~:text=indexPatternId) | - | | | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/application/index.tsx#:~:text=RedirectAppLinks), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/application/index.tsx#:~:text=RedirectAppLinks), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/application/index.tsx#:~:text=RedirectAppLinks) | - | diff --git a/api_docs/deprecations_by_team.mdx b/api_docs/deprecations_by_team.mdx index 7b6cb0650852..98e33fa6135b 100644 --- a/api_docs/deprecations_by_team.mdx +++ b/api_docs/deprecations_by_team.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsDueByTeam slug: /kibana-dev-docs/api-meta/deprecations-due-by-team title: Deprecated APIs due to be removed, by team description: Lists the teams that are referencing deprecated APIs with a remove by date. -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/dev_tools.mdx b/api_docs/dev_tools.mdx index 0feec691d406..a467c356502f 100644 --- a/api_docs/dev_tools.mdx +++ b/api_docs/dev_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/devTools title: "devTools" image: https://source.unsplash.com/400x175/?github description: API docs for the devTools plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'devTools'] --- import devToolsObj from './dev_tools.devdocs.json'; diff --git a/api_docs/discover.devdocs.json b/api_docs/discover.devdocs.json index 8c4a6308dd17..334c593cbc3c 100644 --- a/api_docs/discover.devdocs.json +++ b/api_docs/discover.devdocs.json @@ -693,7 +693,7 @@ }, ", ", "SearchOutput", - ">" + ", any>" ], "path": "src/plugins/discover/public/embeddable/types.ts", "deprecated": false, diff --git a/api_docs/discover.mdx b/api_docs/discover.mdx index 60bf441d6606..1c4634dc487e 100644 --- a/api_docs/discover.mdx +++ b/api_docs/discover.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discover title: "discover" image: https://source.unsplash.com/400x175/?github description: API docs for the discover plugin -date: 2022-10-24 +date: 2022-10-26 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 4a5b349acb9d..badcdfc7eec2 100644 --- a/api_docs/discover_enhanced.mdx +++ b/api_docs/discover_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discoverEnhanced title: "discoverEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the discoverEnhanced plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discoverEnhanced'] --- import discoverEnhancedObj from './discover_enhanced.devdocs.json'; diff --git a/api_docs/embeddable.devdocs.json b/api_docs/embeddable.devdocs.json index d82dd8df9db2..735d70ccffc5 100644 --- a/api_docs/embeddable.devdocs.json +++ b/api_docs/embeddable.devdocs.json @@ -121,7 +121,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - " = ", + " = ", { "pluginId": "embeddable", "scope": "public", @@ -129,7 +129,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - ">(embeddableFactoryId: string) => ", + ">(embeddableFactoryId: string) => ", { "pluginId": "embeddable", "scope": "public", @@ -200,7 +200,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">, unknown>>" + ", any>, unknown>>" ], "path": "src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_action.ts", "deprecated": false, @@ -581,7 +581,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">, unknown>) | undefined" + ", any>, unknown>) | undefined" ], "path": "src/plugins/embeddable/public/lib/attribute_service/attribute_service.tsx", "deprecated": false, @@ -824,7 +824,7 @@ "section": "def-public.Embeddable", "text": "Embeddable" }, - " implements ", + " implements ", { "pluginId": "embeddable", "scope": "public", @@ -953,7 +953,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - " = ", + " = ", { "pluginId": "embeddable", "scope": "public", @@ -961,7 +961,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - ">(embeddableFactoryId: string) => ", + ">(embeddableFactoryId: string) => ", { "pluginId": "embeddable", "scope": "public", @@ -1071,7 +1071,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">) => void" + ", any>) => void" ], "path": "src/plugins/embeddable/public/lib/containers/container.ts", "deprecated": false, @@ -1108,7 +1108,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">" + ", any>" ], "path": "src/plugins/embeddable/public/lib/containers/container.ts", "deprecated": false, @@ -1245,7 +1245,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - " = ", + " = ", { "pluginId": "embeddable", "scope": "public", @@ -1253,7 +1253,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - ">(type: string, explicitInput: Partial) => Promise>(type: string, explicitInput: Partial) => Promise = ", + " = ", { "pluginId": "embeddable", "scope": "public", @@ -1356,7 +1356,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - ">(id: string, newExplicitInput: Partial, newType?: string | undefined) => Promise" + ">(id: string, newExplicitInput: Partial, newType?: string | undefined) => Promise" ], "path": "src/plugins/embeddable/public/lib/containers/container.ts", "deprecated": false, @@ -1540,7 +1540,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">>(id: string) => E" + ", any>>(id: string) => E" ], "path": "src/plugins/embeddable/public/lib/containers/container.ts", "deprecated": false, @@ -1678,7 +1678,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">>(id: string) => Promise<", + ", any>>(id: string) => Promise<", { "pluginId": "embeddable", "scope": "public", @@ -1766,7 +1766,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - ">(factory: ", + ">(factory: ", { "pluginId": "embeddable", "scope": "public", @@ -2065,7 +2065,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - " = ", + " = ", { "pluginId": "embeddable", "scope": "public", @@ -2073,7 +2073,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - ">(embeddableFactoryId: string) => ", + ">(embeddableFactoryId: string) => ", { "pluginId": "embeddable", "scope": "public", @@ -2337,7 +2337,7 @@ "section": "def-public.Embeddable", "text": "Embeddable" }, - " implements ", + " implements ", { "pluginId": "embeddable", "scope": "public", @@ -2345,7 +2345,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - "" + "" ], "path": "src/plugins/embeddable/public/lib/embeddables/embeddable.tsx", "deprecated": false, @@ -2904,7 +2904,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - "> | ", + ", any> | ", { "pluginId": "embeddable", "scope": "public", @@ -2976,7 +2976,7 @@ "label": "render", "description": [], "signature": [ - "(el: HTMLElement) => void" + "(el: HTMLElement) => void | TNode" ], "path": "src/plugins/embeddable/public/lib/embeddables/embeddable.tsx", "deprecated": false, @@ -3222,7 +3222,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - "> | ", + ", any> | ", { "pluginId": "embeddable", "scope": "public", @@ -3606,7 +3606,7 @@ "section": "def-public.EmbeddableRoot", "text": "EmbeddableRoot" }, - " extends React.Component" + " extends React.Component" ], "path": "src/plugins/embeddable/public/lib/embeddables/embeddable_root.tsx", "deprecated": false, @@ -3700,7 +3700,7 @@ "label": "shouldComponentUpdate", "description": [], "signature": [ - "(newProps: Props) => boolean" + "({ embeddable, error, input, loading }: Props, { node }: State) => boolean" ], "path": "src/plugins/embeddable/public/lib/embeddables/embeddable_root.tsx", "deprecated": false, @@ -3711,7 +3711,7 @@ "id": "def-public.EmbeddableRoot.shouldComponentUpdate.$1", "type": "Object", "tags": [], - "label": "newProps", + "label": "{ embeddable, error, input, loading }", "description": [], "signature": [ "Props" @@ -3720,6 +3720,21 @@ "deprecated": false, "trackAdoption": false, "isRequired": true + }, + { + "parentPluginId": "embeddable", + "id": "def-public.EmbeddableRoot.shouldComponentUpdate.$2", + "type": "Object", + "tags": [], + "label": "{ node }", + "description": [], + "signature": [ + "State" + ], + "path": "src/plugins/embeddable/public/lib/embeddables/embeddable_root.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true } ], "returnComment": [] @@ -4298,7 +4313,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">" + ", React.ReactNode>" ], "path": "src/plugins/embeddable/public/lib/embeddables/error_embeddable.tsx", "deprecated": false, @@ -4419,21 +4434,6 @@ "deprecated": false, "trackAdoption": false, "isRequired": false - }, - { - "parentPluginId": "embeddable", - "id": "def-public.ErrorEmbeddable.Unnamed.$4", - "type": "boolean", - "tags": [], - "label": "compact", - "description": [], - "signature": [ - "boolean" - ], - "path": "src/plugins/embeddable/public/lib/embeddables/error_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true } ], "returnComment": [] @@ -4462,39 +4462,7 @@ "label": "render", "description": [], "signature": [ - "(dom: HTMLElement) => void" - ], - "path": "src/plugins/embeddable/public/lib/embeddables/error_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "embeddable", - "id": "def-public.ErrorEmbeddable.render.$1", - "type": "Object", - "tags": [], - "label": "dom", - "description": [], - "signature": [ - "HTMLElement" - ], - "path": "src/plugins/embeddable/public/lib/embeddables/error_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "embeddable", - "id": "def-public.ErrorEmbeddable.destroy", - "type": "Function", - "tags": [], - "label": "destroy", - "description": [], - "signature": [ - "() => void" + "() => JSX.Element" ], "path": "src/plugins/embeddable/public/lib/embeddables/error_embeddable.tsx", "deprecated": false, @@ -4606,7 +4574,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - " = ", + " = ", { "pluginId": "embeddable", "scope": "public", @@ -4614,7 +4582,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - ", T = ", + ", T = ", "SavedObjectAttributes", ">(def: ", { @@ -4844,7 +4812,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">>" + ", any>>" ], "path": "src/plugins/embeddable/public/lib/triggers/triggers.ts", "deprecated": false, @@ -4901,7 +4869,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">" + ", any>" ], "path": "src/plugins/embeddable/public/lib/embeddables/is_embeddable.ts", "deprecated": false, @@ -5053,7 +5021,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">>) => context is ", + ", any>>) => context is ", { "pluginId": "embeddable", "scope": "public", @@ -5085,7 +5053,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">>" + ", any>>" ], "path": "src/plugins/embeddable/public/lib/triggers/triggers.ts", "deprecated": false, @@ -5130,7 +5098,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">>" + ", any>>" ], "path": "src/plugins/embeddable/public/lib/triggers/triggers.ts", "deprecated": false, @@ -5214,7 +5182,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">>) => context is ", + ", any>>) => context is ", { "pluginId": "uiActions", "scope": "public", @@ -5266,7 +5234,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">>" + ", any>>" ], "path": "src/plugins/embeddable/public/lib/triggers/triggers.ts", "deprecated": false, @@ -5380,7 +5348,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">>) => context is ", + ", any>>) => context is ", { "pluginId": "embeddable", "scope": "public", @@ -5412,7 +5380,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">>" + ", any>>" ], "path": "src/plugins/embeddable/public/lib/triggers/triggers.ts", "deprecated": false, @@ -5457,7 +5425,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">>" + ", any>>" ], "path": "src/plugins/embeddable/public/lib/triggers/triggers.ts", "deprecated": false, @@ -5597,7 +5565,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - " = ", + " = ", { "pluginId": "embeddable", "scope": "public", @@ -5605,7 +5573,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - ">(embeddableFactoryId: string) => ", + ">(embeddableFactoryId: string) => ", { "pluginId": "embeddable", "scope": "public", @@ -5661,7 +5629,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">, unknown>>; overlays: ", + ", any>, unknown>>; overlays: ", "OverlayStart", "; notifications: ", "NotificationsStart", @@ -5770,7 +5738,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - " = ", + " = ", { "pluginId": "embeddable", "scope": "public", @@ -5778,7 +5746,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - ">(embeddableFactoryId: string) => ", + ">(embeddableFactoryId: string) => ", { "pluginId": "embeddable", "scope": "public", @@ -5862,7 +5830,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">, unknown>>" + ", any>, unknown>>" ], "path": "src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/open_add_panel_flyout.tsx", "deprecated": false, @@ -5992,7 +5960,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - "> | undefined, boolean, string | undefined]" + ", any> | undefined, boolean, string | undefined]" ], "path": "src/plugins/embeddable/public/lib/embeddables/embeddable_renderer.tsx", "deprecated": false, @@ -6049,7 +6017,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - " = ", + " = ", { "pluginId": "embeddable", "scope": "public", @@ -6057,7 +6025,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - ", ExtraProps = {}>(WrappedComponent: React.ComponentType<{ input: I; output: O; embeddable: E; } & ExtraProps>) => React.ComponentType<{ embeddable: E; } & ExtraProps>" + ", ExtraProps = {}>(WrappedComponent: React.ComponentType<{ input: I; output: O; embeddable: E; } & ExtraProps>) => React.ComponentType<{ embeddable: E; } & ExtraProps>" ], "path": "src/plugins/embeddable/public/lib/embeddables/with_subscription.tsx", "deprecated": false, @@ -6374,7 +6342,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">; hideHeader?: boolean | undefined; containerContext?: ", + ", any>; hideHeader?: boolean | undefined; containerContext?: ", { "pluginId": "embeddable", "scope": "public", @@ -7828,7 +7796,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - "" + "" ], "path": "src/plugins/embeddable/public/lib/containers/i_container.ts", "deprecated": false, @@ -7868,7 +7836,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">>(id: string) => Promise<", + ", any>>(id: string) => Promise<", { "pluginId": "embeddable", "scope": "public", @@ -8033,7 +8001,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - "> = ", + ", any> = ", { "pluginId": "embeddable", "scope": "public", @@ -8057,7 +8025,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">>(id: string) => E" + ", any>>(id: string) => E" ], "path": "src/plugins/embeddable/public/lib/containers/i_container.ts", "deprecated": false, @@ -8115,7 +8083,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - "> = ", + ", any> = ", { "pluginId": "embeddable", "scope": "public", @@ -8139,7 +8107,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">>(embeddable: E) => void" + ", any>>(embeddable: E) => void" ], "path": "src/plugins/embeddable/public/lib/containers/i_container.ts", "deprecated": false, @@ -8249,7 +8217,7 @@ "section": "def-public.Embeddable", "text": "Embeddable" }, - " = ", + " = ", { "pluginId": "embeddable", "scope": "public", @@ -8257,7 +8225,7 @@ "section": "def-public.Embeddable", "text": "Embeddable" }, - ">(type: string, explicitInput: Partial) => Promise<", + ">(type: string, explicitInput: Partial) => Promise<", { "pluginId": "embeddable", "scope": "public", @@ -8352,7 +8320,7 @@ "section": "def-public.Embeddable", "text": "Embeddable" }, - " = ", + " = ", { "pluginId": "embeddable", "scope": "public", @@ -8360,7 +8328,7 @@ "section": "def-public.Embeddable", "text": "Embeddable" }, - ">(id: string, newExplicitInput: Partial, newType?: string | undefined) => void" + ">(id: string, newExplicitInput: Partial, newType?: string | undefined) => void" ], "path": "src/plugins/embeddable/public/lib/containers/i_container.ts", "deprecated": false, @@ -8432,7 +8400,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - "" + "" ], "path": "src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts", "deprecated": false, @@ -8869,7 +8837,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - "> | ", + ", any> | ", { "pluginId": "embeddable", "scope": "public", @@ -8911,7 +8879,7 @@ "\nRenders the embeddable at the given node." ], "signature": [ - "(domNode: Element | HTMLElement) => void" + "(domNode: Element | HTMLElement) => void | N" ], "path": "src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts", "deprecated": false, @@ -8933,19 +8901,21 @@ "isRequired": true } ], - "returnComment": [] + "returnComment": [ + "A React node to mount or void in the case when rendering is done without React." + ] }, { "parentPluginId": "embeddable", - "id": "def-public.IEmbeddable.renderError", + "id": "def-public.IEmbeddable.catchError", "type": "Function", "tags": [], - "label": "renderError", + "label": "catchError", "description": [ "\nRenders a custom embeddable error at the given node." ], "signature": [ - "((domNode: Element | HTMLElement, error: ", + "((error: ", { "pluginId": "expressions", "scope": "common", @@ -8953,7 +8923,7 @@ "section": "def-common.ErrorLike", "text": "ErrorLike" }, - ") => () => void) | undefined" + ", domNode: Element | HTMLElement) => N | (() => void)) | undefined" ], "path": "src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts", "deprecated": false, @@ -8961,13 +8931,19 @@ "children": [ { "parentPluginId": "embeddable", - "id": "def-public.IEmbeddable.renderError.$1", + "id": "def-public.IEmbeddable.catchError.$1", "type": "CompoundType", "tags": [], - "label": "domNode", + "label": "error", "description": [], "signature": [ - "Element | HTMLElement" + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ErrorLike", + "text": "ErrorLike" + } ], "path": "src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts", "deprecated": false, @@ -8976,19 +8952,13 @@ }, { "parentPluginId": "embeddable", - "id": "def-public.IEmbeddable.renderError.$2", + "id": "def-public.IEmbeddable.catchError.$2", "type": "CompoundType", "tags": [], - "label": "error", + "label": "domNode", "description": [], "signature": [ - { - "pluginId": "expressions", - "scope": "common", - "docId": "kibExpressionsPluginApi", - "section": "def-common.ErrorLike", - "text": "ErrorLike" - } + "Element | HTMLElement" ], "path": "src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts", "deprecated": false, @@ -8997,7 +8967,7 @@ } ], "returnComment": [ - "A callback that will be called on error destroy." + "A React node or callback that will be called on error destroy." ] }, { @@ -9791,7 +9761,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">; hideHeader?: boolean | undefined; containerContext?: ", + ", any>; hideHeader?: boolean | undefined; containerContext?: ", { "pluginId": "embeddable", "scope": "public", @@ -10116,7 +10086,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - " = ", + " = ", { "pluginId": "embeddable", "scope": "public", @@ -10124,7 +10094,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - ">(id: string, factory: ", + ">(id: string, factory: ", { "pluginId": "embeddable", "scope": "public", @@ -10360,7 +10330,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - " = ", + " = ", { "pluginId": "embeddable", "scope": "public", @@ -10368,7 +10338,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - ">(embeddableFactoryId: string) => ", + ">(embeddableFactoryId: string) => ", { "pluginId": "embeddable", "scope": "public", @@ -10456,7 +10426,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">, unknown>>" + ", any>, unknown>>" ], "path": "src/plugins/embeddable/public/plugin.tsx", "deprecated": false, @@ -10496,7 +10466,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">; hideHeader?: boolean | undefined; containerContext?: ", + ", any>; hideHeader?: boolean | undefined; containerContext?: ", { "pluginId": "embeddable", "scope": "public", diff --git a/api_docs/embeddable.mdx b/api_docs/embeddable.mdx index 0b6dd63154f5..8fc13479fb21 100644 --- a/api_docs/embeddable.mdx +++ b/api_docs/embeddable.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/embeddable title: "embeddable" image: https://source.unsplash.com/400x175/?github description: API docs for the embeddable plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddable'] --- import embeddableObj from './embeddable.devdocs.json'; @@ -21,7 +21,7 @@ Contact [App Services](https://github.com/orgs/elastic/teams/kibana-app-services | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 512 | 0 | 412 | 4 | +| 510 | 0 | 410 | 4 | ## Client diff --git a/api_docs/embeddable_enhanced.devdocs.json b/api_docs/embeddable_enhanced.devdocs.json index 0ec7cfbd8767..6b615629b946 100644 --- a/api_docs/embeddable_enhanced.devdocs.json +++ b/api_docs/embeddable_enhanced.devdocs.json @@ -43,7 +43,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - "> ? E : never>" + ", any> ? E : never>" ], "path": "x-pack/plugins/embeddable_enhanced/public/embeddables/is_enhanced_embeddable.ts", "deprecated": false, @@ -112,7 +112,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - "> & { enhancements: { dynamicActions: ", + ", any> & { enhancements: { dynamicActions: ", { "pluginId": "uiActionsEnhanced", "scope": "public", @@ -273,7 +273,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - "> | undefined; }>[]" + ", any> | undefined; }>[]" ], "path": "x-pack/plugins/embeddable_enhanced/public/actions/drilldown_grouping.ts", "deprecated": false, diff --git a/api_docs/embeddable_enhanced.mdx b/api_docs/embeddable_enhanced.mdx index d4f1f4a57a6e..b35aef2a4cfd 100644 --- a/api_docs/embeddable_enhanced.mdx +++ b/api_docs/embeddable_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/embeddableEnhanced title: "embeddableEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the embeddableEnhanced plugin -date: 2022-10-24 +date: 2022-10-26 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 247f65e1f0d4..8c56e55d378f 100644 --- a/api_docs/encrypted_saved_objects.mdx +++ b/api_docs/encrypted_saved_objects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/encryptedSavedObjects title: "encryptedSavedObjects" image: https://source.unsplash.com/400x175/?github description: API docs for the encryptedSavedObjects plugin -date: 2022-10-24 +date: 2022-10-26 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 c4da56246b68..85f9b45c1262 100644 --- a/api_docs/enterprise_search.mdx +++ b/api_docs/enterprise_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/enterpriseSearch title: "enterpriseSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the enterpriseSearch plugin -date: 2022-10-24 +date: 2022-10-26 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 c5f2230103aa..4852cc2712c4 100644 --- a/api_docs/es_ui_shared.mdx +++ b/api_docs/es_ui_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/esUiShared title: "esUiShared" image: https://source.unsplash.com/400x175/?github description: API docs for the esUiShared plugin -date: 2022-10-24 +date: 2022-10-26 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 498491b48015..4db8cdbf3b02 100644 --- a/api_docs/event_annotation.mdx +++ b/api_docs/event_annotation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventAnnotation title: "eventAnnotation" image: https://source.unsplash.com/400x175/?github description: API docs for the eventAnnotation plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventAnnotation'] --- import eventAnnotationObj from './event_annotation.devdocs.json'; diff --git a/api_docs/event_log.mdx b/api_docs/event_log.mdx index 1d2706ea1b2c..02796883eabb 100644 --- a/api_docs/event_log.mdx +++ b/api_docs/event_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventLog title: "eventLog" image: https://source.unsplash.com/400x175/?github description: API docs for the eventLog plugin -date: 2022-10-24 +date: 2022-10-26 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 f6253ab5570b..443b7781df9c 100644 --- a/api_docs/expression_error.mdx +++ b/api_docs/expression_error.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionError title: "expressionError" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionError plugin -date: 2022-10-24 +date: 2022-10-26 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 a3ab9a63ff6a..c6ff8de75c9f 100644 --- a/api_docs/expression_gauge.mdx +++ b/api_docs/expression_gauge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionGauge title: "expressionGauge" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionGauge plugin -date: 2022-10-24 +date: 2022-10-26 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 8335b1da7201..7d02b7541d03 100644 --- a/api_docs/expression_heatmap.mdx +++ b/api_docs/expression_heatmap.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionHeatmap title: "expressionHeatmap" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionHeatmap plugin -date: 2022-10-24 +date: 2022-10-26 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 0e91bb98190a..6aceca2650fc 100644 --- a/api_docs/expression_image.mdx +++ b/api_docs/expression_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionImage title: "expressionImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionImage plugin -date: 2022-10-24 +date: 2022-10-26 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 ca60b919dfa3..ba615b94f3b9 100644 --- a/api_docs/expression_legacy_metric_vis.mdx +++ b/api_docs/expression_legacy_metric_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionLegacyMetricVis title: "expressionLegacyMetricVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionLegacyMetricVis plugin -date: 2022-10-24 +date: 2022-10-26 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 4b63148d14da..23350f7938e5 100644 --- a/api_docs/expression_metric.mdx +++ b/api_docs/expression_metric.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionMetric title: "expressionMetric" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionMetric plugin -date: 2022-10-24 +date: 2022-10-26 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 9a8b4d9653ec..1790eff9eee2 100644 --- a/api_docs/expression_metric_vis.mdx +++ b/api_docs/expression_metric_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionMetricVis title: "expressionMetricVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionMetricVis plugin -date: 2022-10-24 +date: 2022-10-26 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 28f8759733eb..ec12dfd056ed 100644 --- a/api_docs/expression_partition_vis.mdx +++ b/api_docs/expression_partition_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionPartitionVis title: "expressionPartitionVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionPartitionVis plugin -date: 2022-10-24 +date: 2022-10-26 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 4b1814f785cb..ad1ea2cbcf64 100644 --- a/api_docs/expression_repeat_image.mdx +++ b/api_docs/expression_repeat_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionRepeatImage title: "expressionRepeatImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionRepeatImage plugin -date: 2022-10-24 +date: 2022-10-26 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 43bc92d50536..ad020aa14a96 100644 --- a/api_docs/expression_reveal_image.mdx +++ b/api_docs/expression_reveal_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionRevealImage title: "expressionRevealImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionRevealImage plugin -date: 2022-10-24 +date: 2022-10-26 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 51ee5e5e7412..8b6f431fc174 100644 --- a/api_docs/expression_shape.mdx +++ b/api_docs/expression_shape.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionShape title: "expressionShape" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionShape plugin -date: 2022-10-24 +date: 2022-10-26 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 82f4f9235eba..ec2a8eae13ad 100644 --- a/api_docs/expression_tagcloud.mdx +++ b/api_docs/expression_tagcloud.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionTagcloud title: "expressionTagcloud" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionTagcloud plugin -date: 2022-10-24 +date: 2022-10-26 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 999b6f4b44f7..6697a2d04ab9 100644 --- a/api_docs/expression_x_y.mdx +++ b/api_docs/expression_x_y.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionXY title: "expressionXY" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionXY plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionXY'] --- import expressionXYObj from './expression_x_y.devdocs.json'; diff --git a/api_docs/expressions.devdocs.json b/api_docs/expressions.devdocs.json index 651bca99eca7..fdfba4c5796c 100644 --- a/api_docs/expressions.devdocs.json +++ b/api_docs/expressions.devdocs.json @@ -634,7 +634,15 @@ }, "[]>) => ", "Observable", - ">" + " | ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExpressionValueError", + "text": "ExpressionValueError" + }, + ">" ], "path": "src/plugins/expressions/common/execution/execution.ts", "deprecated": false, @@ -13696,7 +13704,15 @@ }, "[]>) => ", "Observable", - ">" + " | ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExpressionValueError", + "text": "ExpressionValueError" + }, + ">" ], "path": "src/plugins/expressions/common/execution/execution.ts", "deprecated": false, @@ -22527,7 +22543,15 @@ }, "[]>) => ", "Observable", - ">" + " | ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExpressionValueError", + "text": "ExpressionValueError" + }, + ">" ], "path": "src/plugins/expressions/common/execution/execution.ts", "deprecated": false, diff --git a/api_docs/expressions.mdx b/api_docs/expressions.mdx index e6e78ade43e9..fe13eb199049 100644 --- a/api_docs/expressions.mdx +++ b/api_docs/expressions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressions title: "expressions" image: https://source.unsplash.com/400x175/?github description: API docs for the expressions plugin -date: 2022-10-24 +date: 2022-10-26 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 6a521bc986a2..3fa349d28e00 100644 --- a/api_docs/features.mdx +++ b/api_docs/features.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/features title: "features" image: https://source.unsplash.com/400x175/?github description: API docs for the features plugin -date: 2022-10-24 +date: 2022-10-26 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 d5714fc7d103..27978bf82e16 100644 --- a/api_docs/field_formats.mdx +++ b/api_docs/field_formats.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fieldFormats title: "fieldFormats" image: https://source.unsplash.com/400x175/?github description: API docs for the fieldFormats plugin -date: 2022-10-24 +date: 2022-10-26 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 74d0df107707..4a59c8090ddc 100644 --- a/api_docs/file_upload.mdx +++ b/api_docs/file_upload.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fileUpload title: "fileUpload" image: https://source.unsplash.com/400x175/?github description: API docs for the fileUpload plugin -date: 2022-10-24 +date: 2022-10-26 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 520b91977324..7388e47d5ca1 100644 --- a/api_docs/files.devdocs.json +++ b/api_docs/files.devdocs.json @@ -3,6 +3,54 @@ "client": { "classes": [], "functions": [ + { + "parentPluginId": "files", + "id": "def-public.FilePicker", + "type": "Function", + "tags": [], + "label": "FilePicker", + "description": [], + "signature": [ + "(props: ", + { + "pluginId": "files", + "scope": "public", + "docId": "kibFilesPluginApi", + "section": "def-public.Props", + "text": "Props" + }, + ") => JSX.Element" + ], + "path": "x-pack/plugins/files/public/components/file_picker/index.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "files", + "id": "def-public.FilePicker.$1", + "type": "Object", + "tags": [], + "label": "props", + "description": [], + "signature": [ + { + "pluginId": "files", + "scope": "public", + "docId": "kibFilesPluginApi", + "section": "def-public.Props", + "text": "Props" + }, + "" + ], + "path": "x-pack/plugins/files/public/components/file_picker/index.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "files", "id": "def-public.FilesContext", @@ -11,7 +59,7 @@ "label": "FilesContext", "description": [], "signature": [ - "({ children }: { children?: React.ReactNode; }) => JSX.Element" + "({ client, children }: React.PropsWithChildren) => JSX.Element" ], "path": "x-pack/plugins/files/public/components/context.tsx", "deprecated": false, @@ -20,12 +68,12 @@ { "parentPluginId": "files", "id": "def-public.FilesContext.$1", - "type": "Object", + "type": "CompoundType", "tags": [], - "label": "{ children }", + "label": "{ client, children }", "description": [], "signature": [ - "{ children?: React.ReactNode; }" + "React.PropsWithChildren" ], "path": "x-pack/plugins/files/public/components/context.tsx", "deprecated": false, @@ -163,7 +211,7 @@ "\nCreate a new file object with the provided metadata.\n" ], "signature": [ - "(args: Readonly<{ meta?: Readonly<{} & {}> | undefined; alt?: string | undefined; mimeType?: string | undefined; } & { name: string; }> & { kind: string; }) => Promise<{ file: ", + "(args: Readonly<{ meta?: Readonly<{} & {}> | undefined; alt?: string | undefined; mimeType?: string | undefined; } & { name: string; }> & { abortSignal?: AbortSignal | undefined; } & { kind: string; }) => Promise<{ file: ", { "pluginId": "files", "scope": "common", @@ -188,7 +236,7 @@ "- create file args" ], "signature": [ - "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; } & ExtraArgs" + "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { abortSignal?: AbortSignal | undefined; } & { kind: string; } & ExtraArgs" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -206,7 +254,7 @@ "\nDelete a file object and all associated share and content objects.\n" ], "signature": [ - "(args: Readonly<{} & { id: string; }> & { kind: string; }) => Promise<{ ok: true; }>" + "(args: Readonly<{} & { id: string; }> & { abortSignal?: AbortSignal | undefined; } & { kind: string; }) => Promise<{ ok: true; }>" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -223,7 +271,7 @@ "- delete file args" ], "signature": [ - "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; } & ExtraArgs" + "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { abortSignal?: AbortSignal | undefined; } & { kind: string; } & ExtraArgs" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -241,7 +289,7 @@ "\nGet a file object by ID.\n" ], "signature": [ - "(args: Readonly<{} & { id: string; }> & { kind: string; }) => Promise<{ file: ", + "(args: Readonly<{} & { id: string; }> & { abortSignal?: AbortSignal | undefined; } & { kind: string; }) => Promise<{ file: ", { "pluginId": "files", "scope": "common", @@ -266,7 +314,7 @@ "- get file by ID args" ], "signature": [ - "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; } & ExtraArgs" + "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { abortSignal?: AbortSignal | undefined; } & { kind: string; } & ExtraArgs" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -284,7 +332,7 @@ "\nList all file objects, of a given {@link FileKind}.\n" ], "signature": [ - "(args: Readonly<{ name?: string | string[] | undefined; status?: string | string[] | undefined; meta?: Readonly<{} & {}> | undefined; extension?: string | string[] | undefined; } & {}> & Readonly<{ page?: number | undefined; perPage?: number | undefined; } & {}> & { kind: string; }) => Promise<{ files: ", + "(args: Readonly<{ name?: string | string[] | undefined; status?: string | string[] | undefined; meta?: Readonly<{} & {}> | undefined; extension?: string | string[] | undefined; } & {}> & Readonly<{ page?: number | undefined; perPage?: number | undefined; } & {}> & { abortSignal?: AbortSignal | undefined; } & { kind: string; }) => Promise<{ files: ", { "pluginId": "files", "scope": "common", @@ -309,7 +357,7 @@ "- list files args" ], "signature": [ - "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; } & ExtraArgs" + "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { abortSignal?: AbortSignal | undefined; } & { kind: string; } & ExtraArgs" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -327,7 +375,7 @@ "\nUpdate a set of of metadata values of the file object.\n" ], "signature": [ - "(args: Readonly<{ name?: string | undefined; meta?: Readonly<{} & {}> | undefined; alt?: string | undefined; } & {}> & Readonly<{} & { id: string; }> & { kind: string; }) => Promise<{ file: ", + "(args: Readonly<{ name?: string | undefined; meta?: Readonly<{} & {}> | undefined; alt?: string | undefined; } & {}> & Readonly<{} & { id: string; }> & { abortSignal?: AbortSignal | undefined; } & { kind: string; }) => Promise<{ file: ", { "pluginId": "files", "scope": "common", @@ -352,7 +400,7 @@ "- update file args" ], "signature": [ - "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; } & ExtraArgs" + "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { abortSignal?: AbortSignal | undefined; } & { kind: string; } & ExtraArgs" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -406,7 +454,7 @@ "\nStream a download of the file object's content.\n" ], "signature": [ - "(args: Readonly<{ fileName?: string | undefined; } & { id: string; }> & { kind: string; }) => Promise" + "(args: Readonly<{ fileName?: string | undefined; } & { id: string; }> & { abortSignal?: AbortSignal | undefined; } & { kind: string; }) => Promise" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -423,7 +471,7 @@ "- download file args" ], "signature": [ - "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; } & ExtraArgs" + "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { abortSignal?: AbortSignal | undefined; } & { kind: string; } & ExtraArgs" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -495,7 +543,7 @@ "\nShare a file by creating a new file share instance.\n" ], "signature": [ - "(args: Readonly<{ name?: string | undefined; validUntil?: number | undefined; } & {}> & Readonly<{} & { fileId: string; }> & { kind: string; }) => Promise<", + "(args: Readonly<{ name?: string | undefined; validUntil?: number | undefined; } & {}> & Readonly<{} & { fileId: string; }> & { abortSignal?: AbortSignal | undefined; } & { kind: string; }) => Promise<", { "pluginId": "files", "scope": "common", @@ -520,7 +568,7 @@ "- File share arguments" ], "signature": [ - "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; } & ExtraArgs" + "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { abortSignal?: AbortSignal | undefined; } & { kind: string; } & ExtraArgs" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -538,7 +586,7 @@ "\nDelete a file share instance.\n" ], "signature": [ - "(args: Readonly<{} & { id: string; }> & { kind: string; }) => Promise<{ ok: true; }>" + "(args: Readonly<{} & { id: string; }> & { abortSignal?: AbortSignal | undefined; } & { kind: string; }) => Promise<{ ok: true; }>" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -555,7 +603,7 @@ "- File unshare arguments" ], "signature": [ - "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; } & ExtraArgs" + "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { abortSignal?: AbortSignal | undefined; } & { kind: string; } & ExtraArgs" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -573,7 +621,7 @@ "\nGet a file share instance.\n" ], "signature": [ - "(args: Readonly<{} & { id: string; }> & { kind: string; }) => Promise<{ share: ", + "(args: Readonly<{} & { id: string; }> & { abortSignal?: AbortSignal | undefined; } & { kind: string; }) => Promise<{ share: ", { "pluginId": "files", "scope": "common", @@ -598,7 +646,7 @@ "- Get file share arguments" ], "signature": [ - "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; } & ExtraArgs" + "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { abortSignal?: AbortSignal | undefined; } & { kind: string; } & ExtraArgs" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -616,7 +664,7 @@ "\nList all file shares. Optionally scoping to a specific\nfile.\n" ], "signature": [ - "(args: Readonly<{ page?: number | undefined; perPage?: number | undefined; forFileId?: string | undefined; } & {}> & { kind: string; }) => Promise<{ shares: ", + "(args: Readonly<{ page?: number | undefined; perPage?: number | undefined; forFileId?: string | undefined; } & {}> & { abortSignal?: AbortSignal | undefined; } & { kind: string; }) => Promise<{ shares: ", { "pluginId": "files", "scope": "common", @@ -641,7 +689,7 @@ "- Get file share arguments" ], "signature": [ - "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; } & ExtraArgs" + "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { abortSignal?: AbortSignal | undefined; } & { kind: string; } & ExtraArgs" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -896,29 +944,6 @@ "deprecated": false, "trackAdoption": false }, - { - "parentPluginId": "files", - "id": "def-public.Props.client", - "type": "Object", - "tags": [], - "label": "client", - "description": [ - "\nA files client that will be used process uploads." - ], - "signature": [ - { - "pluginId": "files", - "scope": "public", - "docId": "kibFilesPluginApi", - "section": "def-public.FilesClient", - "text": "FilesClient" - }, - "" - ], - "path": "x-pack/plugins/files/public/components/upload_file/upload_file.tsx", - "deprecated": false, - "trackAdoption": false - }, { "parentPluginId": "files", "id": "def-public.Props.allowClear", @@ -969,6 +994,22 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "files", + "id": "def-public.Props.fullWidth", + "type": "CompoundType", + "tags": [], + "label": "fullWidth", + "description": [ + "\nWhether to display the file picker with width 100%;" + ], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/files/public/components/upload_file/upload_file.tsx", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "files", "id": "def-public.Props.allowRepeatedUploads", @@ -987,6 +1028,22 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "files", + "id": "def-public.Props.initialPromptText", + "type": "string", + "tags": [], + "label": "initialPromptText", + "description": [ + "\nThe initial text prompt" + ], + "signature": [ + "string | undefined" + ], + "path": "x-pack/plugins/files/public/components/upload_file/upload_file.tsx", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "files", "id": "def-public.Props.onDone", @@ -1054,6 +1111,151 @@ } ], "returnComment": [] + }, + { + "parentPluginId": "files", + "id": "def-public.Props.compressed", + "type": "CompoundType", + "tags": [ + "default", + "note" + ], + "label": "compressed", + "description": [ + "\nWhether to display the component in it's compact form.\n" + ], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/files/public/components/upload_file/upload_file.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "files", + "id": "def-public.Props.multiple", + "type": "CompoundType", + "tags": [ + "default" + ], + "label": "multiple", + "description": [ + "\nAllow upload more than one file at a time\n" + ], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/files/public/components/upload_file/upload_file.tsx", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "files", + "id": "def-public.Props", + "type": "Interface", + "tags": [], + "label": "Props", + "description": [], + "signature": [ + { + "pluginId": "files", + "scope": "public", + "docId": "kibFilesPluginApi", + "section": "def-public.Props", + "text": "Props" + }, + "" + ], + "path": "x-pack/plugins/files/public/components/file_picker/file_picker.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "files", + "id": "def-public.Props.kind", + "type": "Uncategorized", + "tags": [], + "label": "kind", + "description": [ + "\nThe file kind that was passed to the registry." + ], + "signature": [ + "Kind" + ], + "path": "x-pack/plugins/files/public/components/file_picker/file_picker.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "files", + "id": "def-public.Props.onClose", + "type": "Function", + "tags": [], + "label": "onClose", + "description": [ + "\nWill be called when the modal is closed" + ], + "signature": [ + "() => void" + ], + "path": "x-pack/plugins/files/public/components/file_picker/file_picker.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "files", + "id": "def-public.Props.onDone", + "type": "Function", + "tags": [], + "label": "onDone", + "description": [ + "\nWill be called after a user has a selected a set of files" + ], + "signature": [ + "(fileIds: string[]) => void" + ], + "path": "x-pack/plugins/files/public/components/file_picker/file_picker.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "files", + "id": "def-public.Props.onDone.$1", + "type": "Array", + "tags": [], + "label": "fileIds", + "description": [], + "signature": [ + "string[]" + ], + "path": "x-pack/plugins/files/public/components/file_picker/file_picker.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "files", + "id": "def-public.Props.pageSize", + "type": "number", + "tags": [], + "label": "pageSize", + "description": [ + "\nThe number of results to show per page." + ], + "signature": [ + "number | undefined" + ], + "path": "x-pack/plugins/files/public/components/file_picker/file_picker.tsx", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -1158,7 +1360,7 @@ "\nA files client that is scoped to a specific {@link FileKind}.\n\nMore convenient if you want to re-use the same client for the same file kind\nand not specify the kind every time." ], "signature": [ - "{ create: (arg: Omit | undefined; alt?: string | undefined; mimeType?: string | undefined; } & { name: string; }> & { kind: string; }, \"kind\">) => Promise<{ file: ", + "{ create: (arg: Omit | undefined; alt?: string | undefined; mimeType?: string | undefined; } & { name: string; }> & { abortSignal?: AbortSignal | undefined; } & { kind: string; }, \"kind\">) => Promise<{ file: ", { "pluginId": "files", "scope": "common", @@ -1166,7 +1368,7 @@ "section": "def-common.FileJSON", "text": "FileJSON" }, - "; }>; delete: (arg: Omit & { kind: string; }, \"kind\">) => Promise<{ ok: true; }>; getById: (arg: Omit & { kind: string; }, \"kind\">) => Promise<{ file: ", + "; }>; delete: (arg: Omit & { abortSignal?: AbortSignal | undefined; } & { kind: string; }, \"kind\">) => Promise<{ ok: true; }>; getById: (arg: Omit & { abortSignal?: AbortSignal | undefined; } & { kind: string; }, \"kind\">) => Promise<{ file: ", { "pluginId": "files", "scope": "common", @@ -1174,7 +1376,7 @@ "section": "def-common.FileJSON", "text": "FileJSON" }, - "; }>; list: (arg?: Omit | undefined; extension?: string | string[] | undefined; } & {}> & Readonly<{ page?: number | undefined; perPage?: number | undefined; } & {}> & { kind: string; }, \"kind\"> | undefined) => Promise<{ files: ", + "; }>; list: (arg?: Omit | undefined; extension?: string | string[] | undefined; } & {}> & Readonly<{ page?: number | undefined; perPage?: number | undefined; } & {}> & { abortSignal?: AbortSignal | undefined; } & { kind: string; }, \"kind\"> | undefined) => Promise<{ files: ", { "pluginId": "files", "scope": "common", @@ -1182,7 +1384,7 @@ "section": "def-common.FileJSON", "text": "FileJSON" }, - "[]; total: number; }>; update: (arg: Omit | undefined; alt?: string | undefined; } & {}> & Readonly<{} & { id: string; }> & { kind: string; }, \"kind\">) => Promise<{ file: ", + "[]; total: number; }>; update: (arg: Omit | undefined; alt?: string | undefined; } & {}> & Readonly<{} & { id: string; }> & { abortSignal?: AbortSignal | undefined; } & { kind: string; }, \"kind\">) => Promise<{ file: ", { "pluginId": "files", "scope": "common", @@ -1190,7 +1392,7 @@ "section": "def-common.FileJSON", "text": "FileJSON" }, - "; }>; upload: (arg: Omit & Readonly<{ selfDestructOnAbort?: boolean | undefined; } & {}> & { body: unknown; kind: string; abortSignal?: AbortSignal | undefined; contentType?: string | undefined; }, \"kind\">) => Promise<{ ok: true; size: number; }>; download: (arg: Omit & { kind: string; }, \"kind\">) => Promise; getDownloadHref: (arg: Omit; }>; upload: (arg: Omit & Readonly<{ selfDestructOnAbort?: boolean | undefined; } & {}> & { body: unknown; kind: string; abortSignal?: AbortSignal | undefined; contentType?: string | undefined; }, \"kind\">) => Promise<{ ok: true; size: number; }>; download: (arg: Omit & { abortSignal?: AbortSignal | undefined; } & { kind: string; }, \"kind\">) => Promise; getDownloadHref: (arg: Omit, \"id\" | \"fileKind\">, \"kind\">) => string; share: (arg: Omit & Readonly<{} & { fileId: string; }> & { kind: string; }, \"kind\">) => Promise<", + ", \"id\" | \"fileKind\">, \"kind\">) => string; share: (arg: Omit & Readonly<{} & { fileId: string; }> & { abortSignal?: AbortSignal | undefined; } & { kind: string; }, \"kind\">) => Promise<", { "pluginId": "files", "scope": "common", @@ -1206,7 +1408,7 @@ "section": "def-common.FileShareJSONWithToken", "text": "FileShareJSONWithToken" }, - ">; unshare: (arg: Omit & { kind: string; }, \"kind\">) => Promise<{ ok: true; }>; getShare: (arg: Omit & { kind: string; }, \"kind\">) => Promise<{ share: ", + ">; unshare: (arg: Omit & { abortSignal?: AbortSignal | undefined; } & { kind: string; }, \"kind\">) => Promise<{ ok: true; }>; getShare: (arg: Omit & { abortSignal?: AbortSignal | undefined; } & { kind: string; }, \"kind\">) => Promise<{ share: ", { "pluginId": "files", "scope": "common", @@ -1214,7 +1416,7 @@ "section": "def-common.FileShareJSON", "text": "FileShareJSON" }, - "; }>; listShares: (arg: Omit & { kind: string; }, \"kind\">) => Promise<{ shares: ", + "; }>; listShares: (arg: Omit & { abortSignal?: AbortSignal | undefined; } & { kind: string; }, \"kind\">) => Promise<{ shares: ", { "pluginId": "files", "scope": "common", @@ -1230,7 +1432,7 @@ "section": "def-common.FilesMetrics", "text": "FilesMetrics" }, - ">; publicDownload: (arg: Omit & Readonly<{} & { token: string; }>, \"kind\">) => Promise; find: (arg: Omit | undefined; kind?: string | string[] | undefined; extension?: string | string[] | undefined; } & {}> & Readonly<{ page?: number | undefined; perPage?: number | undefined; } & {}>, \"kind\">) => Promise<{ files: ", + ">; publicDownload: (arg: Omit & Readonly<{} & { token: string; }> & { abortSignal?: AbortSignal | undefined; }, \"kind\">) => Promise; find: (arg: Omit | undefined; kind?: string | string[] | undefined; extension?: string | string[] | undefined; } & {}> & Readonly<{ page?: number | undefined; perPage?: number | undefined; } & {}> & { abortSignal?: AbortSignal | undefined; }, \"kind\">) => Promise<{ files: ", { "pluginId": "files", "scope": "common", diff --git a/api_docs/files.mdx b/api_docs/files.mdx index b3e811531cb4..614788b18c1b 100644 --- a/api_docs/files.mdx +++ b/api_docs/files.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/files title: "files" image: https://source.unsplash.com/400x175/?github description: API docs for the files plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'files'] --- import filesObj from './files.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-app-services](https://github.com/orgs/elastic/teams/tea | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 260 | 0 | 14 | 2 | +| 271 | 0 | 18 | 2 | ## Client diff --git a/api_docs/fleet.mdx b/api_docs/fleet.mdx index 9411e94690d6..ef7c82c8917e 100644 --- a/api_docs/fleet.mdx +++ b/api_docs/fleet.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fleet title: "fleet" image: https://source.unsplash.com/400x175/?github description: API docs for the fleet plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fleet'] --- import fleetObj from './fleet.devdocs.json'; diff --git a/api_docs/global_search.mdx b/api_docs/global_search.mdx index bd17be79cdc7..bce77595c703 100644 --- a/api_docs/global_search.mdx +++ b/api_docs/global_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/globalSearch title: "globalSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the globalSearch plugin -date: 2022-10-24 +date: 2022-10-26 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 881a886f2610..5797ac6f96e2 100644 --- a/api_docs/guided_onboarding.mdx +++ b/api_docs/guided_onboarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/guidedOnboarding title: "guidedOnboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the guidedOnboarding plugin -date: 2022-10-24 +date: 2022-10-26 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 9b0f2944cb20..a6b8f5ebee8b 100644 --- a/api_docs/home.mdx +++ b/api_docs/home.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/home title: "home" image: https://source.unsplash.com/400x175/?github description: API docs for the home plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'home'] --- import homeObj from './home.devdocs.json'; diff --git a/api_docs/index_lifecycle_management.mdx b/api_docs/index_lifecycle_management.mdx index 879adf658036..eefa3d660943 100644 --- a/api_docs/index_lifecycle_management.mdx +++ b/api_docs/index_lifecycle_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/indexLifecycleManagement title: "indexLifecycleManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the indexLifecycleManagement plugin -date: 2022-10-24 +date: 2022-10-26 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 252f8ed81b6b..57ec2ab38019 100644 --- a/api_docs/index_management.mdx +++ b/api_docs/index_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/indexManagement title: "indexManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the indexManagement plugin -date: 2022-10-24 +date: 2022-10-26 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 11f572a0541d..cd40c4f23c6a 100644 --- a/api_docs/infra.mdx +++ b/api_docs/infra.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/infra title: "infra" image: https://source.unsplash.com/400x175/?github description: API docs for the infra plugin -date: 2022-10-24 +date: 2022-10-26 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 aba491b93a8e..af9e719c8ad4 100644 --- a/api_docs/inspector.mdx +++ b/api_docs/inspector.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/inspector title: "inspector" image: https://source.unsplash.com/400x175/?github description: API docs for the inspector plugin -date: 2022-10-24 +date: 2022-10-26 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 5dfc6d364acc..5bc224188453 100644 --- a/api_docs/interactive_setup.mdx +++ b/api_docs/interactive_setup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/interactiveSetup title: "interactiveSetup" image: https://source.unsplash.com/400x175/?github description: API docs for the interactiveSetup plugin -date: 2022-10-24 +date: 2022-10-26 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 a9d4f451049e..dd04c329c72e 100644 --- a/api_docs/kbn_ace.mdx +++ b/api_docs/kbn_ace.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ace title: "@kbn/ace" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ace plugin -date: 2022-10-24 +date: 2022-10-26 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 e856824cd465..1c4d7fe6695b 100644 --- a/api_docs/kbn_aiops_components.mdx +++ b/api_docs/kbn_aiops_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-components title: "@kbn/aiops-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-components plugin -date: 2022-10-24 +date: 2022-10-26 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 25a5a279932c..d58b0e9c87fe 100644 --- a/api_docs/kbn_aiops_utils.mdx +++ b/api_docs/kbn_aiops_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-utils title: "@kbn/aiops-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-utils plugin -date: 2022-10-24 +date: 2022-10-26 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 394760e1e250..5b603c68899a 100644 --- a/api_docs/kbn_alerts.mdx +++ b/api_docs/kbn_alerts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerts title: "@kbn/alerts" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerts plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts'] --- import kbnAlertsObj from './kbn_alerts.devdocs.json'; diff --git a/api_docs/kbn_analytics.mdx b/api_docs/kbn_analytics.mdx index 64e297d8c02a..1b367a710cd0 100644 --- a/api_docs/kbn_analytics.mdx +++ b/api_docs/kbn_analytics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics title: "@kbn/analytics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics plugin -date: 2022-10-24 +date: 2022-10-26 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 ee740710cc89..0a0cb18115b8 100644 --- a/api_docs/kbn_analytics_client.mdx +++ b/api_docs/kbn_analytics_client.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-client title: "@kbn/analytics-client" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-client plugin -date: 2022-10-24 +date: 2022-10-26 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 8f0d89b32f87..8ab848ae6f34 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-browser title: "@kbn/analytics-shippers-elastic-v3-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-browser plugin -date: 2022-10-24 +date: 2022-10-26 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 a13ae9f793bd..0dee99f506dd 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-common title: "@kbn/analytics-shippers-elastic-v3-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-common plugin -date: 2022-10-24 +date: 2022-10-26 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 ab9e813825ec..90b3d84c8dfd 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-server title: "@kbn/analytics-shippers-elastic-v3-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-server plugin -date: 2022-10-24 +date: 2022-10-26 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 c8e99aaa70fa..49d00e6c0739 100644 --- a/api_docs/kbn_analytics_shippers_fullstory.mdx +++ b/api_docs/kbn_analytics_shippers_fullstory.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-fullstory title: "@kbn/analytics-shippers-fullstory" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-fullstory plugin -date: 2022-10-24 +date: 2022-10-26 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 15a9175703af..5f7e33381d27 100644 --- a/api_docs/kbn_analytics_shippers_gainsight.mdx +++ b/api_docs/kbn_analytics_shippers_gainsight.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-gainsight title: "@kbn/analytics-shippers-gainsight" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-gainsight plugin -date: 2022-10-24 +date: 2022-10-26 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 0e2b948836ae..4ac106f76275 100644 --- a/api_docs/kbn_apm_config_loader.mdx +++ b/api_docs/kbn_apm_config_loader.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-config-loader title: "@kbn/apm-config-loader" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-config-loader plugin -date: 2022-10-24 +date: 2022-10-26 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 b4cf8d19dc2e..1d0580cd6dda 100644 --- a/api_docs/kbn_apm_synthtrace.mdx +++ b/api_docs/kbn_apm_synthtrace.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-synthtrace title: "@kbn/apm-synthtrace" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-synthtrace plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-synthtrace'] --- import kbnApmSynthtraceObj from './kbn_apm_synthtrace.devdocs.json'; diff --git a/api_docs/kbn_apm_utils.mdx b/api_docs/kbn_apm_utils.mdx index 0a8b85a80ac7..47d0691abfc0 100644 --- a/api_docs/kbn_apm_utils.mdx +++ b/api_docs/kbn_apm_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-utils title: "@kbn/apm-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-utils plugin -date: 2022-10-24 +date: 2022-10-26 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 774a0ee0330c..2156ea720266 100644 --- a/api_docs/kbn_axe_config.mdx +++ b/api_docs/kbn_axe_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-axe-config title: "@kbn/axe-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/axe-config plugin -date: 2022-10-24 +date: 2022-10-26 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 56a10f629d6b..bf6517aebaa4 100644 --- a/api_docs/kbn_cases_components.mdx +++ b/api_docs/kbn_cases_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cases-components title: "@kbn/cases-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cases-components plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cases-components'] --- import kbnCasesComponentsObj from './kbn_cases_components.devdocs.json'; diff --git a/api_docs/kbn_chart_icons.mdx b/api_docs/kbn_chart_icons.mdx index b96d4163b65c..a9b101024b15 100644 --- a/api_docs/kbn_chart_icons.mdx +++ b/api_docs/kbn_chart_icons.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-chart-icons title: "@kbn/chart-icons" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/chart-icons plugin -date: 2022-10-24 +date: 2022-10-26 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 34a85abda98b..ab877ddcb4a4 100644 --- a/api_docs/kbn_ci_stats_core.mdx +++ b/api_docs/kbn_ci_stats_core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-core title: "@kbn/ci-stats-core" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-core plugin -date: 2022-10-24 +date: 2022-10-26 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 b6799e71abd0..cc43f43322d9 100644 --- a/api_docs/kbn_ci_stats_performance_metrics.mdx +++ b/api_docs/kbn_ci_stats_performance_metrics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-performance-metrics title: "@kbn/ci-stats-performance-metrics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-performance-metrics plugin -date: 2022-10-24 +date: 2022-10-26 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 f8887d91bcdc..fb56815f1b74 100644 --- a/api_docs/kbn_ci_stats_reporter.mdx +++ b/api_docs/kbn_ci_stats_reporter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-reporter title: "@kbn/ci-stats-reporter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-reporter plugin -date: 2022-10-24 +date: 2022-10-26 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 d8543e906772..87502b0cd6be 100644 --- a/api_docs/kbn_cli_dev_mode.mdx +++ b/api_docs/kbn_cli_dev_mode.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cli-dev-mode title: "@kbn/cli-dev-mode" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cli-dev-mode plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cli-dev-mode'] --- import kbnCliDevModeObj from './kbn_cli_dev_mode.devdocs.json'; diff --git a/api_docs/kbn_coloring.mdx b/api_docs/kbn_coloring.mdx index 4ec2f8f515d3..83901fbcbe94 100644 --- a/api_docs/kbn_coloring.mdx +++ b/api_docs/kbn_coloring.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-coloring title: "@kbn/coloring" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/coloring plugin -date: 2022-10-24 +date: 2022-10-26 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 c8013cb00173..3d527cfe3938 100644 --- a/api_docs/kbn_config.mdx +++ b/api_docs/kbn_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config title: "@kbn/config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config plugin -date: 2022-10-24 +date: 2022-10-26 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 3785f9ac2944..2feafb153879 100644 --- a/api_docs/kbn_config_mocks.mdx +++ b/api_docs/kbn_config_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config-mocks title: "@kbn/config-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 70f3d9c4d3b5..66a347ef9c88 100644 --- a/api_docs/kbn_config_schema.mdx +++ b/api_docs/kbn_config_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config-schema title: "@kbn/config-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config-schema plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-schema'] --- import kbnConfigSchemaObj from './kbn_config_schema.devdocs.json'; diff --git a/api_docs/kbn_content_management_table_list.mdx b/api_docs/kbn_content_management_table_list.mdx index 2f7d72fe038b..18253531008c 100644 --- a/api_docs/kbn_content_management_table_list.mdx +++ b/api_docs/kbn_content_management_table_list.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-table-list title: "@kbn/content-management-table-list" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-table-list plugin -date: 2022-10-24 +date: 2022-10-26 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 f63116031e19..7af427fed2aa 100644 --- a/api_docs/kbn_core_analytics_browser.mdx +++ b/api_docs/kbn_core_analytics_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser title: "@kbn/core-analytics-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser plugin -date: 2022-10-24 +date: 2022-10-26 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 68d907d76e67..ce8e1171a41d 100644 --- a/api_docs/kbn_core_analytics_browser_internal.mdx +++ b/api_docs/kbn_core_analytics_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser-internal title: "@kbn/core-analytics-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser-internal plugin -date: 2022-10-24 +date: 2022-10-26 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 4a3621f85a0f..84072dda2f42 100644 --- a/api_docs/kbn_core_analytics_browser_mocks.mdx +++ b/api_docs/kbn_core_analytics_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser-mocks title: "@kbn/core-analytics-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 ca8068039751..fd3f973fa07b 100644 --- a/api_docs/kbn_core_analytics_server.mdx +++ b/api_docs/kbn_core_analytics_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server title: "@kbn/core-analytics-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server plugin -date: 2022-10-24 +date: 2022-10-26 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 6785dd1c24cf..ad24bed9773b 100644 --- a/api_docs/kbn_core_analytics_server_internal.mdx +++ b/api_docs/kbn_core_analytics_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server-internal title: "@kbn/core-analytics-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server-internal plugin -date: 2022-10-24 +date: 2022-10-26 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 3af523b673b0..8b44333995f2 100644 --- a/api_docs/kbn_core_analytics_server_mocks.mdx +++ b/api_docs/kbn_core_analytics_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server-mocks title: "@kbn/core-analytics-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 f7be87db8d16..9668d2b1dfaa 100644 --- a/api_docs/kbn_core_application_browser.mdx +++ b/api_docs/kbn_core_application_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser title: "@kbn/core-application-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser plugin -date: 2022-10-24 +date: 2022-10-26 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 dec5910b5372..5b23e0052fab 100644 --- a/api_docs/kbn_core_application_browser_internal.mdx +++ b/api_docs/kbn_core_application_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser-internal title: "@kbn/core-application-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser-internal plugin -date: 2022-10-24 +date: 2022-10-26 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 45f826f3b3bc..926105e01532 100644 --- a/api_docs/kbn_core_application_browser_mocks.mdx +++ b/api_docs/kbn_core_application_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser-mocks title: "@kbn/core-application-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 ed6ce1bada64..956b55e48a61 100644 --- a/api_docs/kbn_core_application_common.mdx +++ b/api_docs/kbn_core_application_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-common title: "@kbn/core-application-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-common plugin -date: 2022-10-24 +date: 2022-10-26 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 7301f169abeb..faf3460eee6b 100644 --- a/api_docs/kbn_core_apps_browser_internal.mdx +++ b/api_docs/kbn_core_apps_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-browser-internal title: "@kbn/core-apps-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-browser-internal plugin -date: 2022-10-24 +date: 2022-10-26 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 da1a1129beb6..cb460044721e 100644 --- a/api_docs/kbn_core_apps_browser_mocks.mdx +++ b/api_docs/kbn_core_apps_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-browser-mocks title: "@kbn/core-apps-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-browser-mocks plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-browser-mocks'] --- import kbnCoreAppsBrowserMocksObj from './kbn_core_apps_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_base_browser_mocks.mdx b/api_docs/kbn_core_base_browser_mocks.mdx index 65ca57d22c84..efb620f25dec 100644 --- a/api_docs/kbn_core_base_browser_mocks.mdx +++ b/api_docs/kbn_core_base_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-browser-mocks title: "@kbn/core-base-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-browser-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 3cd12a846815..de533d752b03 100644 --- a/api_docs/kbn_core_base_common.mdx +++ b/api_docs/kbn_core_base_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-common title: "@kbn/core-base-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-common plugin -date: 2022-10-24 +date: 2022-10-26 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 fa31c5c961af..d9f5e2e18544 100644 --- a/api_docs/kbn_core_base_server_internal.mdx +++ b/api_docs/kbn_core_base_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-server-internal title: "@kbn/core-base-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-server-internal plugin -date: 2022-10-24 +date: 2022-10-26 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 8eaa7503a4f7..e3999d9e3631 100644 --- a/api_docs/kbn_core_base_server_mocks.mdx +++ b/api_docs/kbn_core_base_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-server-mocks title: "@kbn/core-base-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-server-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 90127fcda829..8bd883cfb95e 100644 --- a/api_docs/kbn_core_capabilities_browser_mocks.mdx +++ b/api_docs/kbn_core_capabilities_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-browser-mocks title: "@kbn/core-capabilities-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-browser-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 450ece235157..fce232ef986a 100644 --- a/api_docs/kbn_core_capabilities_common.mdx +++ b/api_docs/kbn_core_capabilities_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-common title: "@kbn/core-capabilities-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-common plugin -date: 2022-10-24 +date: 2022-10-26 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 48d443e3b00b..c18a3bd6f8c5 100644 --- a/api_docs/kbn_core_capabilities_server.mdx +++ b/api_docs/kbn_core_capabilities_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-server title: "@kbn/core-capabilities-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-server plugin -date: 2022-10-24 +date: 2022-10-26 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 c02230657603..06ab50caf59f 100644 --- a/api_docs/kbn_core_capabilities_server_mocks.mdx +++ b/api_docs/kbn_core_capabilities_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-server-mocks title: "@kbn/core-capabilities-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-server-mocks plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-server-mocks'] --- import kbnCoreCapabilitiesServerMocksObj from './kbn_core_capabilities_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_chrome_browser.devdocs.json b/api_docs/kbn_core_chrome_browser.devdocs.json index fb399e5080e6..ac10e8f1cb1d 100644 --- a/api_docs/kbn_core_chrome_browser.devdocs.json +++ b/api_docs/kbn_core_chrome_browser.devdocs.json @@ -187,6 +187,50 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/core-chrome-browser", + "id": "def-common.ChromeGlobalHelpExtensionMenuLink", + "type": "Interface", + "tags": [], + "label": "ChromeGlobalHelpExtensionMenuLink", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-chrome-browser", + "scope": "common", + "docId": "kibKbnCoreChromeBrowserPluginApi", + "section": "def-common.ChromeGlobalHelpExtensionMenuLink", + "text": "ChromeGlobalHelpExtensionMenuLink" + }, + " extends ", + { + "pluginId": "@kbn/core-chrome-browser", + "scope": "common", + "docId": "kibKbnCoreChromeBrowserPluginApi", + "section": "def-common.ChromeHelpExtensionMenuCustomLink", + "text": "ChromeHelpExtensionMenuCustomLink" + } + ], + "path": "packages/core/chrome/core-chrome-browser/src/help_extension.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-chrome-browser", + "id": "def-common.ChromeGlobalHelpExtensionMenuLink.priority", + "type": "number", + "tags": [], + "label": "priority", + "description": [ + "\nHighest priority items are listed at the top of the list of links." + ], + "path": "packages/core/chrome/core-chrome-browser/src/help_extension.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/core-chrome-browser", "id": "def-common.ChromeHelpExtension", @@ -1882,6 +1926,82 @@ ], "returnComment": [] }, + { + "parentPluginId": "@kbn/core-chrome-browser", + "id": "def-common.ChromeStart.getGlobalHelpExtensionMenuLinks$", + "type": "Function", + "tags": [], + "label": "getGlobalHelpExtensionMenuLinks$", + "description": [ + "\nGet the list of the registered global help extension menu links" + ], + "signature": [ + "() => ", + "Observable", + "<", + { + "pluginId": "@kbn/core-chrome-browser", + "scope": "common", + "docId": "kibKbnCoreChromeBrowserPluginApi", + "section": "def-common.ChromeGlobalHelpExtensionMenuLink", + "text": "ChromeGlobalHelpExtensionMenuLink" + }, + "[]>" + ], + "path": "packages/core/chrome/core-chrome-browser/src/contracts.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-chrome-browser", + "id": "def-common.ChromeStart.registerGlobalHelpExtensionMenuLink", + "type": "Function", + "tags": [], + "label": "registerGlobalHelpExtensionMenuLink", + "description": [ + "\nAppend a global help extension menu link" + ], + "signature": [ + "(globalHelpExtensionMenuLink: ", + { + "pluginId": "@kbn/core-chrome-browser", + "scope": "common", + "docId": "kibKbnCoreChromeBrowserPluginApi", + "section": "def-common.ChromeGlobalHelpExtensionMenuLink", + "text": "ChromeGlobalHelpExtensionMenuLink" + }, + ") => void" + ], + "path": "packages/core/chrome/core-chrome-browser/src/contracts.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-chrome-browser", + "id": "def-common.ChromeStart.registerGlobalHelpExtensionMenuLink.$1", + "type": "Object", + "tags": [], + "label": "globalHelpExtensionMenuLink", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-chrome-browser", + "scope": "common", + "docId": "kibKbnCoreChromeBrowserPluginApi", + "section": "def-common.ChromeGlobalHelpExtensionMenuLink", + "text": "ChromeGlobalHelpExtensionMenuLink" + } + ], + "path": "packages/core/chrome/core-chrome-browser/src/contracts.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, { "parentPluginId": "@kbn/core-chrome-browser", "id": "def-common.ChromeStart.getHelpExtension$", @@ -1889,7 +2009,7 @@ "tags": [], "label": "getHelpExtension$", "description": [ - "\nGet an observable of the current custom help conttent" + "\nGet an observable of the current custom help content" ], "signature": [ "() => ", diff --git a/api_docs/kbn_core_chrome_browser.mdx b/api_docs/kbn_core_chrome_browser.mdx index f4a49ab87577..99be614027fd 100644 --- a/api_docs/kbn_core_chrome_browser.mdx +++ b/api_docs/kbn_core_chrome_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-chrome-browser title: "@kbn/core-chrome-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-chrome-browser plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-chrome-browser'] --- import kbnCoreChromeBrowserObj from './kbn_core_chrome_browser.devdocs.json'; @@ -21,7 +21,7 @@ Contact Kibana Core for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 114 | 0 | 41 | 0 | +| 119 | 0 | 43 | 0 | ## Common diff --git a/api_docs/kbn_core_chrome_browser_mocks.mdx b/api_docs/kbn_core_chrome_browser_mocks.mdx index 92d82d26e605..9709ac0cdcc5 100644 --- a/api_docs/kbn_core_chrome_browser_mocks.mdx +++ b/api_docs/kbn_core_chrome_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-chrome-browser-mocks title: "@kbn/core-chrome-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-chrome-browser-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 d75d3570bae1..b4af59fdaf7e 100644 --- a/api_docs/kbn_core_config_server_internal.mdx +++ b/api_docs/kbn_core_config_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-config-server-internal title: "@kbn/core-config-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-config-server-internal plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-config-server-internal'] --- import kbnCoreConfigServerInternalObj from './kbn_core_config_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser.mdx b/api_docs/kbn_core_deprecations_browser.mdx index 1ef10f67a6ce..f199115ffbd3 100644 --- a/api_docs/kbn_core_deprecations_browser.mdx +++ b/api_docs/kbn_core_deprecations_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser title: "@kbn/core-deprecations-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser plugin -date: 2022-10-24 +date: 2022-10-26 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 7a76cfe89994..87b911dc4b82 100644 --- a/api_docs/kbn_core_deprecations_browser_internal.mdx +++ b/api_docs/kbn_core_deprecations_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser-internal title: "@kbn/core-deprecations-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser-internal plugin -date: 2022-10-24 +date: 2022-10-26 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 1b048e3c47da..2f0de83107c8 100644 --- a/api_docs/kbn_core_deprecations_browser_mocks.mdx +++ b/api_docs/kbn_core_deprecations_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser-mocks title: "@kbn/core-deprecations-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 0de0c7f12fe5..f23e78fc7d1c 100644 --- a/api_docs/kbn_core_deprecations_common.mdx +++ b/api_docs/kbn_core_deprecations_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-common title: "@kbn/core-deprecations-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-common plugin -date: 2022-10-24 +date: 2022-10-26 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 a3a5411e49c9..6c06b27fe4e6 100644 --- a/api_docs/kbn_core_deprecations_server.mdx +++ b/api_docs/kbn_core_deprecations_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server title: "@kbn/core-deprecations-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server plugin -date: 2022-10-24 +date: 2022-10-26 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 5bad8b2ae00d..933fddb6428d 100644 --- a/api_docs/kbn_core_deprecations_server_internal.mdx +++ b/api_docs/kbn_core_deprecations_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server-internal title: "@kbn/core-deprecations-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server-internal plugin -date: 2022-10-24 +date: 2022-10-26 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 e774ea2f7b74..0d758d1d32b3 100644 --- a/api_docs/kbn_core_deprecations_server_mocks.mdx +++ b/api_docs/kbn_core_deprecations_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server-mocks title: "@kbn/core-deprecations-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 1796e47ccb04..bce42aaafcd4 100644 --- a/api_docs/kbn_core_doc_links_browser.mdx +++ b/api_docs/kbn_core_doc_links_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-browser title: "@kbn/core-doc-links-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-browser plugin -date: 2022-10-24 +date: 2022-10-26 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 120f5cad04ab..1ae08a29f808 100644 --- a/api_docs/kbn_core_doc_links_browser_mocks.mdx +++ b/api_docs/kbn_core_doc_links_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-browser-mocks title: "@kbn/core-doc-links-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-browser-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 d556c7dc38d4..6b189876a6ca 100644 --- a/api_docs/kbn_core_doc_links_server.mdx +++ b/api_docs/kbn_core_doc_links_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-server title: "@kbn/core-doc-links-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-server plugin -date: 2022-10-24 +date: 2022-10-26 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 92f40659a599..6344bf943e8f 100644 --- a/api_docs/kbn_core_doc_links_server_mocks.mdx +++ b/api_docs/kbn_core_doc_links_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-server-mocks title: "@kbn/core-doc-links-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-server-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 89bdb5f8dd35..c6922c41e0d2 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_internal.mdx +++ b/api_docs/kbn_core_elasticsearch_client_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-client-server-internal title: "@kbn/core-elasticsearch-client-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-client-server-internal plugin -date: 2022-10-24 +date: 2022-10-26 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 be7c56c04191..2fd94cd952ae 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx +++ b/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-client-server-mocks title: "@kbn/core-elasticsearch-client-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-client-server-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 3619c8e0bca4..dab47c1a76ca 100644 --- a/api_docs/kbn_core_elasticsearch_server.mdx +++ b/api_docs/kbn_core_elasticsearch_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server title: "@kbn/core-elasticsearch-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server plugin -date: 2022-10-24 +date: 2022-10-26 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 22faa7a16af2..09d01052a8cb 100644 --- a/api_docs/kbn_core_elasticsearch_server_internal.mdx +++ b/api_docs/kbn_core_elasticsearch_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server-internal title: "@kbn/core-elasticsearch-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server-internal plugin -date: 2022-10-24 +date: 2022-10-26 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 790152efe235..f6a43d575fb1 100644 --- a/api_docs/kbn_core_elasticsearch_server_mocks.mdx +++ b/api_docs/kbn_core_elasticsearch_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server-mocks title: "@kbn/core-elasticsearch-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 4d38008e4a03..31d9a7ffbb2e 100644 --- a/api_docs/kbn_core_environment_server_internal.mdx +++ b/api_docs/kbn_core_environment_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-environment-server-internal title: "@kbn/core-environment-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-environment-server-internal plugin -date: 2022-10-24 +date: 2022-10-26 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 55895197efb8..15334d974150 100644 --- a/api_docs/kbn_core_environment_server_mocks.mdx +++ b/api_docs/kbn_core_environment_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-environment-server-mocks title: "@kbn/core-environment-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-environment-server-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 661c35b0e186..820aa2863d3e 100644 --- a/api_docs/kbn_core_execution_context_browser.mdx +++ b/api_docs/kbn_core_execution_context_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser title: "@kbn/core-execution-context-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser plugin -date: 2022-10-24 +date: 2022-10-26 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 b921a2fafac4..2580b3783096 100644 --- a/api_docs/kbn_core_execution_context_browser_internal.mdx +++ b/api_docs/kbn_core_execution_context_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser-internal title: "@kbn/core-execution-context-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser-internal plugin -date: 2022-10-24 +date: 2022-10-26 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 2ce6736b1765..f0bd2323c0bd 100644 --- a/api_docs/kbn_core_execution_context_browser_mocks.mdx +++ b/api_docs/kbn_core_execution_context_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser-mocks title: "@kbn/core-execution-context-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 c95b7c4ae721..6d3e5a442b3e 100644 --- a/api_docs/kbn_core_execution_context_common.mdx +++ b/api_docs/kbn_core_execution_context_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-common title: "@kbn/core-execution-context-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-common plugin -date: 2022-10-24 +date: 2022-10-26 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 35900d2f6656..5b636a3ca767 100644 --- a/api_docs/kbn_core_execution_context_server.mdx +++ b/api_docs/kbn_core_execution_context_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server title: "@kbn/core-execution-context-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server plugin -date: 2022-10-24 +date: 2022-10-26 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 cc4ff29d79a4..4aa8121d6ff7 100644 --- a/api_docs/kbn_core_execution_context_server_internal.mdx +++ b/api_docs/kbn_core_execution_context_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server-internal title: "@kbn/core-execution-context-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server-internal plugin -date: 2022-10-24 +date: 2022-10-26 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 eeb3cea35a30..7e487a0cfd9a 100644 --- a/api_docs/kbn_core_execution_context_server_mocks.mdx +++ b/api_docs/kbn_core_execution_context_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server-mocks title: "@kbn/core-execution-context-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 475b11e99f69..ffe790e0d6d1 100644 --- a/api_docs/kbn_core_fatal_errors_browser.mdx +++ b/api_docs/kbn_core_fatal_errors_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-fatal-errors-browser title: "@kbn/core-fatal-errors-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-fatal-errors-browser plugin -date: 2022-10-24 +date: 2022-10-26 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 9c1936ddd77b..a7e5a2f26472 100644 --- a/api_docs/kbn_core_fatal_errors_browser_mocks.mdx +++ b/api_docs/kbn_core_fatal_errors_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-fatal-errors-browser-mocks title: "@kbn/core-fatal-errors-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-fatal-errors-browser-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 81d63fc9b6f3..4ed54e0c1cee 100644 --- a/api_docs/kbn_core_http_browser.mdx +++ b/api_docs/kbn_core_http_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser title: "@kbn/core-http-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser plugin -date: 2022-10-24 +date: 2022-10-26 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 85473fb712d0..6fe0fd0bd166 100644 --- a/api_docs/kbn_core_http_browser_internal.mdx +++ b/api_docs/kbn_core_http_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser-internal title: "@kbn/core-http-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser-internal plugin -date: 2022-10-24 +date: 2022-10-26 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 0cac18712831..38f09ae65705 100644 --- a/api_docs/kbn_core_http_browser_mocks.mdx +++ b/api_docs/kbn_core_http_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser-mocks title: "@kbn/core-http-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 6f900446a59a..8e382dc64786 100644 --- a/api_docs/kbn_core_http_common.mdx +++ b/api_docs/kbn_core_http_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-common title: "@kbn/core-http-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-common plugin -date: 2022-10-24 +date: 2022-10-26 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 6bddc86756fe..bc203401cf67 100644 --- a/api_docs/kbn_core_http_context_server_mocks.mdx +++ b/api_docs/kbn_core_http_context_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-context-server-mocks title: "@kbn/core-http-context-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-context-server-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 e28e61ffb037..2ee3310f867d 100644 --- a/api_docs/kbn_core_http_request_handler_context_server.mdx +++ b/api_docs/kbn_core_http_request_handler_context_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-request-handler-context-server title: "@kbn/core-http-request-handler-context-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-request-handler-context-server plugin -date: 2022-10-24 +date: 2022-10-26 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 2aeb4b945495..566723c70615 100644 --- a/api_docs/kbn_core_http_resources_server.mdx +++ b/api_docs/kbn_core_http_resources_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-resources-server title: "@kbn/core-http-resources-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-resources-server plugin -date: 2022-10-24 +date: 2022-10-26 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 d5f8e50c2da7..f33e09e9f54b 100644 --- a/api_docs/kbn_core_http_resources_server_internal.mdx +++ b/api_docs/kbn_core_http_resources_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-resources-server-internal title: "@kbn/core-http-resources-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-resources-server-internal plugin -date: 2022-10-24 +date: 2022-10-26 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 02e122b5473b..56d8de4c06c8 100644 --- a/api_docs/kbn_core_http_resources_server_mocks.mdx +++ b/api_docs/kbn_core_http_resources_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-resources-server-mocks title: "@kbn/core-http-resources-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-resources-server-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 75997bef4e25..7c2ef6cdfd43 100644 --- a/api_docs/kbn_core_http_router_server_internal.mdx +++ b/api_docs/kbn_core_http_router_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-router-server-internal title: "@kbn/core-http-router-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-router-server-internal plugin -date: 2022-10-24 +date: 2022-10-26 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 f062b763f0b6..e391838aa36d 100644 --- a/api_docs/kbn_core_http_router_server_mocks.mdx +++ b/api_docs/kbn_core_http_router_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-router-server-mocks title: "@kbn/core-http-router-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-router-server-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 940ed5d08097..1ba21683fa4f 100644 --- a/api_docs/kbn_core_http_server.mdx +++ b/api_docs/kbn_core_http_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server title: "@kbn/core-http-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server'] --- import kbnCoreHttpServerObj from './kbn_core_http_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_server_internal.devdocs.json b/api_docs/kbn_core_http_server_internal.devdocs.json index 1a51d9c4ae67..d7eec9a8625a 100644 --- a/api_docs/kbn_core_http_server_internal.devdocs.json +++ b/api_docs/kbn_core_http_server_internal.devdocs.json @@ -382,7 +382,7 @@ "label": "compression", "description": [], "signature": [ - "{ enabled: boolean; referrerWhitelist?: string[] | undefined; }" + "{ enabled: boolean; referrerWhitelist?: string[] | undefined; brotli: { enabled: boolean; quality: number; }; }" ], "path": "packages/core/http/core-http-server-internal/src/http_config.ts", "deprecated": false, @@ -759,7 +759,7 @@ "label": "HttpConfigType", "description": [], "signature": [ - "{ readonly uuid?: string | undefined; readonly basePath?: string | undefined; readonly publicBaseUrl?: string | undefined; readonly name: string; readonly host: string; readonly compression: Readonly<{ referrerWhitelist?: string[] | undefined; } & { enabled: boolean; }>; readonly ssl: Readonly<{ key?: string | undefined; certificateAuthorities?: string | string[] | undefined; certificate?: string | undefined; keyPassphrase?: string | undefined; redirectHttpFromPort?: number | undefined; } & { enabled: boolean; keystore: Readonly<{ path?: string | undefined; password?: string | undefined; } & {}>; truststore: Readonly<{ path?: string | undefined; password?: string | undefined; } & {}>; cipherSuites: string[]; supportedProtocols: string[]; clientAuthentication: \"optional\" | \"none\" | \"required\"; }>; readonly port: number; readonly cors: Readonly<{} & { enabled: boolean; allowCredentials: boolean; allowOrigin: string[] | \"*\"[]; }>; readonly autoListen: boolean; readonly shutdownTimeout: moment.Duration; readonly securityResponseHeaders: Readonly<{} & { referrerPolicy: \"origin\" | \"no-referrer\" | \"no-referrer-when-downgrade\" | \"origin-when-cross-origin\" | \"same-origin\" | \"strict-origin\" | \"strict-origin-when-cross-origin\" | \"unsafe-url\" | null; disableEmbedding: boolean; strictTransportSecurity: string | null; xContentTypeOptions: \"nosniff\" | null; permissionsPolicy: string | null; }>; readonly customResponseHeaders: Record; readonly maxPayload: ", + "{ readonly uuid?: string | undefined; readonly basePath?: string | undefined; readonly publicBaseUrl?: string | undefined; readonly name: string; readonly host: string; readonly compression: Readonly<{ referrerWhitelist?: string[] | undefined; } & { enabled: boolean; brotli: Readonly<{} & { enabled: boolean; quality: number; }>; }>; readonly ssl: Readonly<{ key?: string | undefined; certificateAuthorities?: string | string[] | undefined; certificate?: string | undefined; keyPassphrase?: string | undefined; redirectHttpFromPort?: number | undefined; } & { enabled: boolean; keystore: Readonly<{ path?: string | undefined; password?: string | undefined; } & {}>; truststore: Readonly<{ path?: string | undefined; password?: string | undefined; } & {}>; cipherSuites: string[]; supportedProtocols: string[]; clientAuthentication: \"optional\" | \"none\" | \"required\"; }>; readonly port: number; readonly cors: Readonly<{} & { enabled: boolean; allowCredentials: boolean; allowOrigin: string[] | \"*\"[]; }>; readonly autoListen: boolean; readonly shutdownTimeout: moment.Duration; readonly securityResponseHeaders: Readonly<{} & { referrerPolicy: \"origin\" | \"no-referrer\" | \"no-referrer-when-downgrade\" | \"origin-when-cross-origin\" | \"same-origin\" | \"strict-origin\" | \"strict-origin-when-cross-origin\" | \"unsafe-url\" | null; disableEmbedding: boolean; strictTransportSecurity: string | null; xContentTypeOptions: \"nosniff\" | null; permissionsPolicy: string | null; }>; readonly customResponseHeaders: Record; readonly maxPayload: ", "ByteSizeValue", "; readonly rewriteBasePath: boolean; readonly keepaliveTimeout: number; readonly socketTimeout: number; readonly xsrf: Readonly<{} & { disableProtection: boolean; allowlist: string[]; }>; readonly requestId: Readonly<{} & { allowFromAnyIp: boolean; ipAllowlist: string[]; }>; }" ], diff --git a/api_docs/kbn_core_http_server_internal.mdx b/api_docs/kbn_core_http_server_internal.mdx index 75178ea85da0..ba93f3c5bf79 100644 --- a/api_docs/kbn_core_http_server_internal.mdx +++ b/api_docs/kbn_core_http_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server-internal title: "@kbn/core-http-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server-internal plugin -date: 2022-10-24 +date: 2022-10-26 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 ef3250a681d4..e62b492a9c9b 100644 --- a/api_docs/kbn_core_http_server_mocks.mdx +++ b/api_docs/kbn_core_http_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server-mocks title: "@kbn/core-http-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 3eb1a9e3a5d6..577eee1e7a06 100644 --- a/api_docs/kbn_core_i18n_browser.mdx +++ b/api_docs/kbn_core_i18n_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-browser title: "@kbn/core-i18n-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-browser plugin -date: 2022-10-24 +date: 2022-10-26 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 c4053d98527a..d7e384fcbd15 100644 --- a/api_docs/kbn_core_i18n_browser_mocks.mdx +++ b/api_docs/kbn_core_i18n_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-browser-mocks title: "@kbn/core-i18n-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-browser-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 2698e1485a13..77384e4658f4 100644 --- a/api_docs/kbn_core_i18n_server.mdx +++ b/api_docs/kbn_core_i18n_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server title: "@kbn/core-i18n-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server plugin -date: 2022-10-24 +date: 2022-10-26 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 6530943459ef..640999801eb4 100644 --- a/api_docs/kbn_core_i18n_server_internal.mdx +++ b/api_docs/kbn_core_i18n_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server-internal title: "@kbn/core-i18n-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server-internal plugin -date: 2022-10-24 +date: 2022-10-26 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 7e03c6d47515..ccf484f69699 100644 --- a/api_docs/kbn_core_i18n_server_mocks.mdx +++ b/api_docs/kbn_core_i18n_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server-mocks title: "@kbn/core-i18n-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server-mocks plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server-mocks'] --- import kbnCoreI18nServerMocksObj from './kbn_core_i18n_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_injected_metadata_browser.mdx b/api_docs/kbn_core_injected_metadata_browser.mdx index 746ba812a203..69d394955376 100644 --- a/api_docs/kbn_core_injected_metadata_browser.mdx +++ b/api_docs/kbn_core_injected_metadata_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-injected-metadata-browser title: "@kbn/core-injected-metadata-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-injected-metadata-browser plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-injected-metadata-browser'] --- import kbnCoreInjectedMetadataBrowserObj from './kbn_core_injected_metadata_browser.devdocs.json'; diff --git a/api_docs/kbn_core_injected_metadata_browser_mocks.mdx b/api_docs/kbn_core_injected_metadata_browser_mocks.mdx index 532b5fe35e48..2f58371d42ad 100644 --- a/api_docs/kbn_core_injected_metadata_browser_mocks.mdx +++ b/api_docs/kbn_core_injected_metadata_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-injected-metadata-browser-mocks title: "@kbn/core-injected-metadata-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-injected-metadata-browser-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 232be2470ac4..73f6136b45b5 100644 --- a/api_docs/kbn_core_integrations_browser_internal.mdx +++ b/api_docs/kbn_core_integrations_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-integrations-browser-internal title: "@kbn/core-integrations-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-integrations-browser-internal plugin -date: 2022-10-24 +date: 2022-10-26 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 dac8713e351e..69a0ca28c8b8 100644 --- a/api_docs/kbn_core_integrations_browser_mocks.mdx +++ b/api_docs/kbn_core_integrations_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-integrations-browser-mocks title: "@kbn/core-integrations-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-integrations-browser-mocks plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-integrations-browser-mocks'] --- import kbnCoreIntegrationsBrowserMocksObj from './kbn_core_integrations_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_browser.mdx b/api_docs/kbn_core_lifecycle_browser.mdx index 037d552fe945..a07aa774cde7 100644 --- a/api_docs/kbn_core_lifecycle_browser.mdx +++ b/api_docs/kbn_core_lifecycle_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-browser title: "@kbn/core-lifecycle-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-browser plugin -date: 2022-10-24 +date: 2022-10-26 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 1a824bbba619..3417b22806fc 100644 --- a/api_docs/kbn_core_lifecycle_browser_mocks.mdx +++ b/api_docs/kbn_core_lifecycle_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-browser-mocks title: "@kbn/core-lifecycle-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-browser-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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_logging_server.mdx b/api_docs/kbn_core_logging_server.mdx index b96bcc4f4927..c820958772df 100644 --- a/api_docs/kbn_core_logging_server.mdx +++ b/api_docs/kbn_core_logging_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server title: "@kbn/core-logging-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server plugin -date: 2022-10-24 +date: 2022-10-26 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 e54e0effb3fd..398d8f4f4a7f 100644 --- a/api_docs/kbn_core_logging_server_internal.mdx +++ b/api_docs/kbn_core_logging_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server-internal title: "@kbn/core-logging-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server-internal plugin -date: 2022-10-24 +date: 2022-10-26 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 a87b0e0bb3cd..b15ae3a8608b 100644 --- a/api_docs/kbn_core_logging_server_mocks.mdx +++ b/api_docs/kbn_core_logging_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server-mocks title: "@kbn/core-logging-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 251470bc5dd6..5537ff6ea6de 100644 --- a/api_docs/kbn_core_metrics_collectors_server_internal.mdx +++ b/api_docs/kbn_core_metrics_collectors_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-collectors-server-internal title: "@kbn/core-metrics-collectors-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-collectors-server-internal plugin -date: 2022-10-24 +date: 2022-10-26 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 9836655a7a7c..c6e62d7fa333 100644 --- a/api_docs/kbn_core_metrics_collectors_server_mocks.mdx +++ b/api_docs/kbn_core_metrics_collectors_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-collectors-server-mocks title: "@kbn/core-metrics-collectors-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-collectors-server-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 cfbabf60a498..222afef08704 100644 --- a/api_docs/kbn_core_metrics_server.mdx +++ b/api_docs/kbn_core_metrics_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server title: "@kbn/core-metrics-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server plugin -date: 2022-10-24 +date: 2022-10-26 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 91e6d190572c..03c5b22b6df2 100644 --- a/api_docs/kbn_core_metrics_server_internal.mdx +++ b/api_docs/kbn_core_metrics_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server-internal title: "@kbn/core-metrics-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server-internal plugin -date: 2022-10-24 +date: 2022-10-26 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 13e4430c8cce..e27527ed957f 100644 --- a/api_docs/kbn_core_metrics_server_mocks.mdx +++ b/api_docs/kbn_core_metrics_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server-mocks title: "@kbn/core-metrics-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 2bb083295fa1..10e0635e9173 100644 --- a/api_docs/kbn_core_mount_utils_browser.mdx +++ b/api_docs/kbn_core_mount_utils_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-mount-utils-browser title: "@kbn/core-mount-utils-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-mount-utils-browser plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-mount-utils-browser'] --- import kbnCoreMountUtilsBrowserObj from './kbn_core_mount_utils_browser.devdocs.json'; diff --git a/api_docs/kbn_core_node_server.mdx b/api_docs/kbn_core_node_server.mdx index 9384c8bf3cdf..d64d1f407c09 100644 --- a/api_docs/kbn_core_node_server.mdx +++ b/api_docs/kbn_core_node_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server title: "@kbn/core-node-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server'] --- import kbnCoreNodeServerObj from './kbn_core_node_server.devdocs.json'; diff --git a/api_docs/kbn_core_node_server_internal.mdx b/api_docs/kbn_core_node_server_internal.mdx index 9da489b8443e..a133e9ac2586 100644 --- a/api_docs/kbn_core_node_server_internal.mdx +++ b/api_docs/kbn_core_node_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server-internal title: "@kbn/core-node-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server-internal plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server-internal'] --- import kbnCoreNodeServerInternalObj from './kbn_core_node_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_node_server_mocks.mdx b/api_docs/kbn_core_node_server_mocks.mdx index faae84d73a4d..06a7b16c6dde 100644 --- a/api_docs/kbn_core_node_server_mocks.mdx +++ b/api_docs/kbn_core_node_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server-mocks title: "@kbn/core-node-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 d33a2160531f..f2769cc0586b 100644 --- a/api_docs/kbn_core_notifications_browser.mdx +++ b/api_docs/kbn_core_notifications_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser title: "@kbn/core-notifications-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser plugin -date: 2022-10-24 +date: 2022-10-26 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 51a5b6b1e5c0..f64b12bbf650 100644 --- a/api_docs/kbn_core_notifications_browser_internal.mdx +++ b/api_docs/kbn_core_notifications_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser-internal title: "@kbn/core-notifications-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser-internal plugin -date: 2022-10-24 +date: 2022-10-26 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 094ecbc105bc..d7b4fe5f036f 100644 --- a/api_docs/kbn_core_notifications_browser_mocks.mdx +++ b/api_docs/kbn_core_notifications_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser-mocks title: "@kbn/core-notifications-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 dab96b323695..6891e2af0743 100644 --- a/api_docs/kbn_core_overlays_browser.mdx +++ b/api_docs/kbn_core_overlays_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser title: "@kbn/core-overlays-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser plugin -date: 2022-10-24 +date: 2022-10-26 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 1844875b57af..9a9e7eed7a3b 100644 --- a/api_docs/kbn_core_overlays_browser_internal.mdx +++ b/api_docs/kbn_core_overlays_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser-internal title: "@kbn/core-overlays-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser-internal plugin -date: 2022-10-24 +date: 2022-10-26 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 0bdbac43e793..a34873e61abb 100644 --- a/api_docs/kbn_core_overlays_browser_mocks.mdx +++ b/api_docs/kbn_core_overlays_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser-mocks title: "@kbn/core-overlays-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 78cd7f446eb6..135db8842113 100644 --- a/api_docs/kbn_core_plugins_browser.mdx +++ b/api_docs/kbn_core_plugins_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-browser title: "@kbn/core-plugins-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-browser plugin -date: 2022-10-24 +date: 2022-10-26 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 885b7856c540..0239e0747f63 100644 --- a/api_docs/kbn_core_plugins_browser_mocks.mdx +++ b/api_docs/kbn_core_plugins_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-browser-mocks title: "@kbn/core-plugins-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-browser-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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_preboot_server.mdx b/api_docs/kbn_core_preboot_server.mdx index 5279be0e19fd..f5c90f191c0d 100644 --- a/api_docs/kbn_core_preboot_server.mdx +++ b/api_docs/kbn_core_preboot_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-preboot-server title: "@kbn/core-preboot-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-preboot-server plugin -date: 2022-10-24 +date: 2022-10-26 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 9b4c4e881a10..9ec313e32a77 100644 --- a/api_docs/kbn_core_preboot_server_mocks.mdx +++ b/api_docs/kbn_core_preboot_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-preboot-server-mocks title: "@kbn/core-preboot-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-preboot-server-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 3ff205815883..a31f6c0e5bcc 100644 --- a/api_docs/kbn_core_rendering_browser_mocks.mdx +++ b/api_docs/kbn_core_rendering_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-browser-mocks title: "@kbn/core-rendering-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-browser-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 a4b770408653..addfb5c8ac97 100644 --- a/api_docs/kbn_core_rendering_server_internal.mdx +++ b/api_docs/kbn_core_rendering_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-server-internal title: "@kbn/core-rendering-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-server-internal plugin -date: 2022-10-24 +date: 2022-10-26 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 465f2c961203..ef92133ba3b0 100644 --- a/api_docs/kbn_core_rendering_server_mocks.mdx +++ b/api_docs/kbn_core_rendering_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-server-mocks title: "@kbn/core-rendering-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-server-mocks plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-server-mocks'] --- import kbnCoreRenderingServerMocksObj from './kbn_core_rendering_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_browser.mdx b/api_docs/kbn_core_saved_objects_api_browser.mdx index 7aabde923ad0..3d3d85bf837a 100644 --- a/api_docs/kbn_core_saved_objects_api_browser.mdx +++ b/api_docs/kbn_core_saved_objects_api_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-browser title: "@kbn/core-saved-objects-api-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-browser plugin -date: 2022-10-24 +date: 2022-10-26 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 ec7ef7497ef9..5bb3a7f1951f 100644 --- a/api_docs/kbn_core_saved_objects_api_server.mdx +++ b/api_docs/kbn_core_saved_objects_api_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server title: "@kbn/core-saved-objects-api-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server plugin -date: 2022-10-24 +date: 2022-10-26 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 1b389e8e63b1..23e96b271e5b 100644 --- a/api_docs/kbn_core_saved_objects_api_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_api_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server-internal title: "@kbn/core-saved-objects-api-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server-internal plugin -date: 2022-10-24 +date: 2022-10-26 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 9f1ea7478cbb..c1eaea93b064 100644 --- a/api_docs/kbn_core_saved_objects_api_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_api_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server-mocks title: "@kbn/core-saved-objects-api-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server-mocks plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server-mocks'] --- import kbnCoreSavedObjectsApiServerMocksObj from './kbn_core_saved_objects_api_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_base_server_internal.mdx b/api_docs/kbn_core_saved_objects_base_server_internal.mdx index 151e7283d103..2cb912c2c6b9 100644 --- a/api_docs/kbn_core_saved_objects_base_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_base_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-base-server-internal title: "@kbn/core-saved-objects-base-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-base-server-internal plugin -date: 2022-10-24 +date: 2022-10-26 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 5d82251619d8..f28cc95c9cf8 100644 --- a/api_docs/kbn_core_saved_objects_base_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_base_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-base-server-mocks title: "@kbn/core-saved-objects-base-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-base-server-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 7fca357b09c2..203027576f75 100644 --- a/api_docs/kbn_core_saved_objects_browser.mdx +++ b/api_docs/kbn_core_saved_objects_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser title: "@kbn/core-saved-objects-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser plugin -date: 2022-10-24 +date: 2022-10-26 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 83ece93b25df..a5c5fb7f6047 100644 --- a/api_docs/kbn_core_saved_objects_browser_internal.mdx +++ b/api_docs/kbn_core_saved_objects_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser-internal title: "@kbn/core-saved-objects-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser-internal plugin -date: 2022-10-24 +date: 2022-10-26 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 f7926112a4ff..51fcca520506 100644 --- a/api_docs/kbn_core_saved_objects_browser_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser-mocks title: "@kbn/core-saved-objects-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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.mdx b/api_docs/kbn_core_saved_objects_common.mdx index 39308f1c9ad2..4e3a6e736830 100644 --- a/api_docs/kbn_core_saved_objects_common.mdx +++ b/api_docs/kbn_core_saved_objects_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-common title: "@kbn/core-saved-objects-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-common plugin -date: 2022-10-24 +date: 2022-10-26 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 1d42426f0239..204fc7f3a662 100644 --- a/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-import-export-server-internal title: "@kbn/core-saved-objects-import-export-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-import-export-server-internal plugin -date: 2022-10-24 +date: 2022-10-26 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 3d77891f2eb0..e948fb3df27f 100644 --- a/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-import-export-server-mocks title: "@kbn/core-saved-objects-import-export-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-import-export-server-mocks plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-import-export-server-mocks'] --- import kbnCoreSavedObjectsImportExportServerMocksObj from './kbn_core_saved_objects_import_export_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_migration_server_internal.mdx b/api_docs/kbn_core_saved_objects_migration_server_internal.mdx index 855f6ebe0973..10d7c1b07a6f 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_migration_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-migration-server-internal title: "@kbn/core-saved-objects-migration-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-migration-server-internal plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-migration-server-internal'] --- import kbnCoreSavedObjectsMigrationServerInternalObj from './kbn_core_saved_objects_migration_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx b/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx index cfd08eb218eb..596a703d7599 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-migration-server-mocks title: "@kbn/core-saved-objects-migration-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-migration-server-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 b95526b573da..49e0586dbce4 100644 --- a/api_docs/kbn_core_saved_objects_server.mdx +++ b/api_docs/kbn_core_saved_objects_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server title: "@kbn/core-saved-objects-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server plugin -date: 2022-10-24 +date: 2022-10-26 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 2d823860b537..95caa9638f55 100644 --- a/api_docs/kbn_core_saved_objects_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server-internal title: "@kbn/core-saved-objects-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server-internal plugin -date: 2022-10-24 +date: 2022-10-26 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 b0ac4a8706ef..dbda52a4a572 100644 --- a/api_docs/kbn_core_saved_objects_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server-mocks title: "@kbn/core-saved-objects-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 079d4e771d0b..7be421b3994d 100644 --- a/api_docs/kbn_core_saved_objects_utils_server.mdx +++ b/api_docs/kbn_core_saved_objects_utils_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-utils-server title: "@kbn/core-saved-objects-utils-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-utils-server plugin -date: 2022-10-24 +date: 2022-10-26 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 2e3f37a017eb..1bcb5f359be5 100644 --- a/api_docs/kbn_core_status_common.mdx +++ b/api_docs/kbn_core_status_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-common title: "@kbn/core-status-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-common plugin -date: 2022-10-24 +date: 2022-10-26 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 1b5d38f2d81d..081d8d45ab78 100644 --- a/api_docs/kbn_core_status_common_internal.mdx +++ b/api_docs/kbn_core_status_common_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-common-internal title: "@kbn/core-status-common-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-common-internal plugin -date: 2022-10-24 +date: 2022-10-26 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 189c26df3ad4..b04e0f8c3bbe 100644 --- a/api_docs/kbn_core_status_server.mdx +++ b/api_docs/kbn_core_status_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server title: "@kbn/core-status-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server plugin -date: 2022-10-24 +date: 2022-10-26 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 9c46d9bd3ef4..b7f24780d8ce 100644 --- a/api_docs/kbn_core_status_server_internal.mdx +++ b/api_docs/kbn_core_status_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server-internal title: "@kbn/core-status-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server-internal plugin -date: 2022-10-24 +date: 2022-10-26 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 f565c9057297..633a83ceeb51 100644 --- a/api_docs/kbn_core_status_server_mocks.mdx +++ b/api_docs/kbn_core_status_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server-mocks title: "@kbn/core-status-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 9a1831ce59c4..a36d93945979 100644 --- a/api_docs/kbn_core_test_helpers_deprecations_getters.mdx +++ b/api_docs/kbn_core_test_helpers_deprecations_getters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-deprecations-getters title: "@kbn/core-test-helpers-deprecations-getters" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-deprecations-getters plugin -date: 2022-10-24 +date: 2022-10-26 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 9d39a35943ac..49e18e07b45c 100644 --- a/api_docs/kbn_core_test_helpers_http_setup_browser.mdx +++ b/api_docs/kbn_core_test_helpers_http_setup_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-http-setup-browser title: "@kbn/core-test-helpers-http-setup-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-http-setup-browser plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-http-setup-browser'] --- import kbnCoreTestHelpersHttpSetupBrowserObj from './kbn_core_test_helpers_http_setup_browser.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_so_type_serializer.mdx b/api_docs/kbn_core_test_helpers_so_type_serializer.mdx index 824203afa7ef..82a2ab23d3c3 100644 --- a/api_docs/kbn_core_test_helpers_so_type_serializer.mdx +++ b/api_docs/kbn_core_test_helpers_so_type_serializer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-so-type-serializer title: "@kbn/core-test-helpers-so-type-serializer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-so-type-serializer plugin -date: 2022-10-24 +date: 2022-10-26 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 384301230d65..b60a98ac73bb 100644 --- a/api_docs/kbn_core_test_helpers_test_utils.mdx +++ b/api_docs/kbn_core_test_helpers_test_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-test-utils title: "@kbn/core-test-helpers-test-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-test-utils plugin -date: 2022-10-24 +date: 2022-10-26 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 79c08444a084..b8ac1433e56e 100644 --- a/api_docs/kbn_core_theme_browser.mdx +++ b/api_docs/kbn_core_theme_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser title: "@kbn/core-theme-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser plugin -date: 2022-10-24 +date: 2022-10-26 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 625e5cd02b2c..d60e7a54f5fe 100644 --- a/api_docs/kbn_core_theme_browser_internal.mdx +++ b/api_docs/kbn_core_theme_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser-internal title: "@kbn/core-theme-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser-internal plugin -date: 2022-10-24 +date: 2022-10-26 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 c7a22a15e2ba..26e22ebe05b0 100644 --- a/api_docs/kbn_core_theme_browser_mocks.mdx +++ b/api_docs/kbn_core_theme_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser-mocks title: "@kbn/core-theme-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 9b429cfd565f..8bf0abf9c204 100644 --- a/api_docs/kbn_core_ui_settings_browser.mdx +++ b/api_docs/kbn_core_ui_settings_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser title: "@kbn/core-ui-settings-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser plugin -date: 2022-10-24 +date: 2022-10-26 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 1036b49b1b40..6e48023d9069 100644 --- a/api_docs/kbn_core_ui_settings_browser_internal.mdx +++ b/api_docs/kbn_core_ui_settings_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser-internal title: "@kbn/core-ui-settings-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser-internal plugin -date: 2022-10-24 +date: 2022-10-26 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 ed14e3fab33a..e1fa5c85e227 100644 --- a/api_docs/kbn_core_ui_settings_browser_mocks.mdx +++ b/api_docs/kbn_core_ui_settings_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser-mocks title: "@kbn/core-ui-settings-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 e8ff59047562..9b9deb3279f5 100644 --- a/api_docs/kbn_core_ui_settings_common.mdx +++ b/api_docs/kbn_core_ui_settings_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-common title: "@kbn/core-ui-settings-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-common plugin -date: 2022-10-24 +date: 2022-10-26 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 12c596c904e5..507bc470de06 100644 --- a/api_docs/kbn_core_ui_settings_server.mdx +++ b/api_docs/kbn_core_ui_settings_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server title: "@kbn/core-ui-settings-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server plugin -date: 2022-10-24 +date: 2022-10-26 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 0f37f30eff62..c5d07472bbf0 100644 --- a/api_docs/kbn_core_ui_settings_server_internal.mdx +++ b/api_docs/kbn_core_ui_settings_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server-internal title: "@kbn/core-ui-settings-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server-internal plugin -date: 2022-10-24 +date: 2022-10-26 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 c1f5d84abe52..2db0ae9add69 100644 --- a/api_docs/kbn_core_ui_settings_server_mocks.mdx +++ b/api_docs/kbn_core_ui_settings_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server-mocks title: "@kbn/core-ui-settings-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 1a8c00b643ac..749563514bbc 100644 --- a/api_docs/kbn_core_usage_data_server.mdx +++ b/api_docs/kbn_core_usage_data_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server title: "@kbn/core-usage-data-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server plugin -date: 2022-10-24 +date: 2022-10-26 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 cf99719d58e0..ebb1e61bac6d 100644 --- a/api_docs/kbn_core_usage_data_server_internal.mdx +++ b/api_docs/kbn_core_usage_data_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server-internal title: "@kbn/core-usage-data-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server-internal plugin -date: 2022-10-24 +date: 2022-10-26 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 350c317c2573..0f801420ee62 100644 --- a/api_docs/kbn_core_usage_data_server_mocks.mdx +++ b/api_docs/kbn_core_usage_data_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server-mocks title: "@kbn/core-usage-data-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 6a179a27e52e..649e7b16c694 100644 --- a/api_docs/kbn_crypto.mdx +++ b/api_docs/kbn_crypto.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-crypto title: "@kbn/crypto" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/crypto plugin -date: 2022-10-24 +date: 2022-10-26 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 320ce22f0f30..e1073e2b364c 100644 --- a/api_docs/kbn_crypto_browser.mdx +++ b/api_docs/kbn_crypto_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-crypto-browser title: "@kbn/crypto-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/crypto-browser plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto-browser'] --- import kbnCryptoBrowserObj from './kbn_crypto_browser.devdocs.json'; diff --git a/api_docs/kbn_datemath.mdx b/api_docs/kbn_datemath.mdx index 535d2b531b67..6ddb88ea4077 100644 --- a/api_docs/kbn_datemath.mdx +++ b/api_docs/kbn_datemath.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-datemath title: "@kbn/datemath" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/datemath plugin -date: 2022-10-24 +date: 2022-10-26 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 396d5dda411f..0e5e900c8743 100644 --- a/api_docs/kbn_dev_cli_errors.mdx +++ b/api_docs/kbn_dev_cli_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-cli-errors title: "@kbn/dev-cli-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-cli-errors plugin -date: 2022-10-24 +date: 2022-10-26 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 571b864d4a46..9b965697dd1d 100644 --- a/api_docs/kbn_dev_cli_runner.mdx +++ b/api_docs/kbn_dev_cli_runner.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-cli-runner title: "@kbn/dev-cli-runner" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-cli-runner plugin -date: 2022-10-24 +date: 2022-10-26 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 b03ef3309396..8a8293a74d99 100644 --- a/api_docs/kbn_dev_proc_runner.mdx +++ b/api_docs/kbn_dev_proc_runner.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-proc-runner title: "@kbn/dev-proc-runner" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-proc-runner plugin -date: 2022-10-24 +date: 2022-10-26 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 20182c161c8b..a88337ce267d 100644 --- a/api_docs/kbn_dev_utils.mdx +++ b/api_docs/kbn_dev_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-utils title: "@kbn/dev-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-utils plugin -date: 2022-10-24 +date: 2022-10-26 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 f85e958b203d..7afb3f3c933f 100644 --- a/api_docs/kbn_doc_links.mdx +++ b/api_docs/kbn_doc_links.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-doc-links title: "@kbn/doc-links" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/doc-links plugin -date: 2022-10-24 +date: 2022-10-26 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 b01179afc26a..e48c8b549944 100644 --- a/api_docs/kbn_docs_utils.mdx +++ b/api_docs/kbn_docs_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-docs-utils title: "@kbn/docs-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/docs-utils plugin -date: 2022-10-24 +date: 2022-10-26 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 d7c245297163..d8552f0a2255 100644 --- a/api_docs/kbn_ebt_tools.mdx +++ b/api_docs/kbn_ebt_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ebt-tools title: "@kbn/ebt-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ebt-tools plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ebt-tools'] --- import kbnEbtToolsObj from './kbn_ebt_tools.devdocs.json'; diff --git a/api_docs/kbn_es_archiver.mdx b/api_docs/kbn_es_archiver.mdx index 2630a960da87..551692eb048f 100644 --- a/api_docs/kbn_es_archiver.mdx +++ b/api_docs/kbn_es_archiver.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-archiver title: "@kbn/es-archiver" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-archiver plugin -date: 2022-10-24 +date: 2022-10-26 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 fcd4f38bb91e..057609a4f756 100644 --- a/api_docs/kbn_es_errors.mdx +++ b/api_docs/kbn_es_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-errors title: "@kbn/es-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-errors plugin -date: 2022-10-24 +date: 2022-10-26 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 e36366844132..3fe19d0e0355 100644 --- a/api_docs/kbn_es_query.mdx +++ b/api_docs/kbn_es_query.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-query title: "@kbn/es-query" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-query plugin -date: 2022-10-24 +date: 2022-10-26 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 142f3d50fb1d..4678e80e9ec3 100644 --- a/api_docs/kbn_es_types.mdx +++ b/api_docs/kbn_es_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-types title: "@kbn/es-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-types plugin -date: 2022-10-24 +date: 2022-10-26 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 5b5bddf7a8c3..8336f8be482a 100644 --- a/api_docs/kbn_eslint_plugin_imports.mdx +++ b/api_docs/kbn_eslint_plugin_imports.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-eslint-plugin-imports title: "@kbn/eslint-plugin-imports" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/eslint-plugin-imports plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/eslint-plugin-imports'] --- import kbnEslintPluginImportsObj from './kbn_eslint_plugin_imports.devdocs.json'; diff --git a/api_docs/kbn_field_types.mdx b/api_docs/kbn_field_types.mdx index 204a52445c07..4fbc9d63fcc7 100644 --- a/api_docs/kbn_field_types.mdx +++ b/api_docs/kbn_field_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-field-types title: "@kbn/field-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/field-types plugin -date: 2022-10-24 +date: 2022-10-26 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 e9f40628e323..b9127cf8124c 100644 --- a/api_docs/kbn_find_used_node_modules.mdx +++ b/api_docs/kbn_find_used_node_modules.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-find-used-node-modules title: "@kbn/find-used-node-modules" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/find-used-node-modules plugin -date: 2022-10-24 +date: 2022-10-26 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 e5fec11c32dc..5b6eb2614ac5 100644 --- a/api_docs/kbn_ftr_common_functional_services.mdx +++ b/api_docs/kbn_ftr_common_functional_services.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ftr-common-functional-services title: "@kbn/ftr-common-functional-services" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ftr-common-functional-services plugin -date: 2022-10-24 +date: 2022-10-26 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 54b58587b993..14e9a6d8abbc 100644 --- a/api_docs/kbn_generate.mdx +++ b/api_docs/kbn_generate.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate title: "@kbn/generate" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate'] --- import kbnGenerateObj from './kbn_generate.devdocs.json'; diff --git a/api_docs/kbn_get_repo_files.mdx b/api_docs/kbn_get_repo_files.mdx index 149848d28f56..3a5e56dacdd5 100644 --- a/api_docs/kbn_get_repo_files.mdx +++ b/api_docs/kbn_get_repo_files.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-get-repo-files title: "@kbn/get-repo-files" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/get-repo-files plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/get-repo-files'] --- import kbnGetRepoFilesObj from './kbn_get_repo_files.devdocs.json'; diff --git a/api_docs/kbn_guided_onboarding.mdx b/api_docs/kbn_guided_onboarding.mdx index 529074de483d..d7568964d15c 100644 --- a/api_docs/kbn_guided_onboarding.mdx +++ b/api_docs/kbn_guided_onboarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-guided-onboarding title: "@kbn/guided-onboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/guided-onboarding plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/guided-onboarding'] --- import kbnGuidedOnboardingObj from './kbn_guided_onboarding.devdocs.json'; diff --git a/api_docs/kbn_handlebars.mdx b/api_docs/kbn_handlebars.mdx index b6c9d40630f2..b3d6fb40138f 100644 --- a/api_docs/kbn_handlebars.mdx +++ b/api_docs/kbn_handlebars.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-handlebars title: "@kbn/handlebars" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/handlebars plugin -date: 2022-10-24 +date: 2022-10-26 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 062826d73164..4398e72463c5 100644 --- a/api_docs/kbn_hapi_mocks.mdx +++ b/api_docs/kbn_hapi_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-hapi-mocks title: "@kbn/hapi-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/hapi-mocks plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/hapi-mocks'] --- import kbnHapiMocksObj from './kbn_hapi_mocks.devdocs.json'; diff --git a/api_docs/kbn_home_sample_data_card.mdx b/api_docs/kbn_home_sample_data_card.mdx index 736fc7a3bb76..e531ba3ae7cd 100644 --- a/api_docs/kbn_home_sample_data_card.mdx +++ b/api_docs/kbn_home_sample_data_card.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-home-sample-data-card title: "@kbn/home-sample-data-card" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/home-sample-data-card plugin -date: 2022-10-24 +date: 2022-10-26 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 25dfc11c439f..1be936dddb95 100644 --- a/api_docs/kbn_home_sample_data_tab.mdx +++ b/api_docs/kbn_home_sample_data_tab.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-home-sample-data-tab title: "@kbn/home-sample-data-tab" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/home-sample-data-tab plugin -date: 2022-10-24 +date: 2022-10-26 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 180a8a8d4a04..9ce89b0d8cbc 100644 --- a/api_docs/kbn_i18n.mdx +++ b/api_docs/kbn_i18n.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-i18n title: "@kbn/i18n" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/i18n plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/i18n'] --- import kbnI18nObj from './kbn_i18n.devdocs.json'; diff --git a/api_docs/kbn_import_resolver.mdx b/api_docs/kbn_import_resolver.mdx index 9c12bd37c24f..93fe5ecccf7d 100644 --- a/api_docs/kbn_import_resolver.mdx +++ b/api_docs/kbn_import_resolver.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-import-resolver title: "@kbn/import-resolver" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/import-resolver plugin -date: 2022-10-24 +date: 2022-10-26 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 bf377fe03d22..f28aca90dc14 100644 --- a/api_docs/kbn_interpreter.mdx +++ b/api_docs/kbn_interpreter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-interpreter title: "@kbn/interpreter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/interpreter plugin -date: 2022-10-24 +date: 2022-10-26 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 056005999135..7d202a5eb307 100644 --- a/api_docs/kbn_io_ts_utils.mdx +++ b/api_docs/kbn_io_ts_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-io-ts-utils title: "@kbn/io-ts-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/io-ts-utils plugin -date: 2022-10-24 +date: 2022-10-26 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 e36ad3bbccf3..37525c3639f4 100644 --- a/api_docs/kbn_jest_serializers.mdx +++ b/api_docs/kbn_jest_serializers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-jest-serializers title: "@kbn/jest-serializers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/jest-serializers plugin -date: 2022-10-24 +date: 2022-10-26 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 d582dadda4a7..92b81aafaf49 100644 --- a/api_docs/kbn_journeys.mdx +++ b/api_docs/kbn_journeys.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-journeys title: "@kbn/journeys" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/journeys plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/journeys'] --- import kbnJourneysObj from './kbn_journeys.devdocs.json'; diff --git a/api_docs/kbn_kibana_manifest_schema.mdx b/api_docs/kbn_kibana_manifest_schema.mdx index 4d3beb4de75c..ce61e702ca85 100644 --- a/api_docs/kbn_kibana_manifest_schema.mdx +++ b/api_docs/kbn_kibana_manifest_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-kibana-manifest-schema title: "@kbn/kibana-manifest-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/kibana-manifest-schema plugin -date: 2022-10-24 +date: 2022-10-26 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 dd1a7f8d2c9f..528620eed3ef 100644 --- a/api_docs/kbn_language_documentation_popover.mdx +++ b/api_docs/kbn_language_documentation_popover.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-language-documentation-popover title: "@kbn/language-documentation-popover" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/language-documentation-popover plugin -date: 2022-10-24 +date: 2022-10-26 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 6eb885da91b2..390b5d5a5e8f 100644 --- a/api_docs/kbn_logging.mdx +++ b/api_docs/kbn_logging.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-logging title: "@kbn/logging" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/logging plugin -date: 2022-10-24 +date: 2022-10-26 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 7be99c0dcf7a..aa5141d06559 100644 --- a/api_docs/kbn_logging_mocks.mdx +++ b/api_docs/kbn_logging_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-logging-mocks title: "@kbn/logging-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/logging-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 f06980ca9960..88790264b5f2 100644 --- a/api_docs/kbn_managed_vscode_config.mdx +++ b/api_docs/kbn_managed_vscode_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-managed-vscode-config title: "@kbn/managed-vscode-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/managed-vscode-config plugin -date: 2022-10-24 +date: 2022-10-26 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 a2b0c579d9fa..2dc8e5f2a4e6 100644 --- a/api_docs/kbn_mapbox_gl.mdx +++ b/api_docs/kbn_mapbox_gl.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-mapbox-gl title: "@kbn/mapbox-gl" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/mapbox-gl plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/mapbox-gl'] --- import kbnMapboxGlObj from './kbn_mapbox_gl.devdocs.json'; diff --git a/api_docs/kbn_ml_agg_utils.devdocs.json b/api_docs/kbn_ml_agg_utils.devdocs.json index 6c021a7a5bc7..efb59fbc5c16 100644 --- a/api_docs/kbn_ml_agg_utils.devdocs.json +++ b/api_docs/kbn_ml_agg_utils.devdocs.json @@ -87,7 +87,7 @@ }, "[], samplerShardSize: number, runtimeMappings?: ", "MappingRuntimeFields", - " | undefined) => Promise<", + " | undefined, abortSignal?: AbortSignal | undefined) => Promise<", { "pluginId": "@kbn/ml-agg-utils", "scope": "server", @@ -198,6 +198,21 @@ "deprecated": false, "trackAdoption": false, "isRequired": false + }, + { + "parentPluginId": "@kbn/ml-agg-utils", + "id": "def-server.fetchAggIntervals.$7", + "type": "Object", + "tags": [], + "label": "abortSignal", + "description": [], + "signature": [ + "AbortSignal | undefined" + ], + "path": "x-pack/packages/ml/agg_utils/src/fetch_agg_intervals.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false } ], "returnComment": [], @@ -225,7 +240,7 @@ }, ", samplerShardSize: number, runtimeMappings?: ", "MappingRuntimeFields", - " | undefined) => Promise<(", + " | undefined, abortSignal?: AbortSignal | undefined) => Promise<(", "NumericChartData", " | OrdinalChartData | UnsupportedChartData)[]>" ], @@ -341,6 +356,21 @@ "deprecated": false, "trackAdoption": false, "isRequired": false + }, + { + "parentPluginId": "@kbn/ml-agg-utils", + "id": "def-server.fetchHistogramsForFields.$7", + "type": "Object", + "tags": [], + "label": "abortSignal", + "description": [], + "signature": [ + "AbortSignal | undefined" + ], + "path": "x-pack/packages/ml/agg_utils/src/fetch_histograms_for_fields.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false } ], "returnComment": [ diff --git a/api_docs/kbn_ml_agg_utils.mdx b/api_docs/kbn_ml_agg_utils.mdx index a9e3088d3b4c..021b3c9d9478 100644 --- a/api_docs/kbn_ml_agg_utils.mdx +++ b/api_docs/kbn_ml_agg_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-agg-utils title: "@kbn/ml-agg-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-agg-utils plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-agg-utils'] --- import kbnMlAggUtilsObj from './kbn_ml_agg_utils.devdocs.json'; @@ -21,7 +21,7 @@ Contact Machine Learning UI for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 64 | 2 | 44 | 3 | +| 66 | 2 | 46 | 3 | ## Server diff --git a/api_docs/kbn_ml_is_populated_object.mdx b/api_docs/kbn_ml_is_populated_object.mdx index 269ebc510149..dc642229955f 100644 --- a/api_docs/kbn_ml_is_populated_object.mdx +++ b/api_docs/kbn_ml_is_populated_object.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-is-populated-object title: "@kbn/ml-is-populated-object" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-is-populated-object plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-is-populated-object'] --- import kbnMlIsPopulatedObjectObj from './kbn_ml_is_populated_object.devdocs.json'; diff --git a/api_docs/kbn_ml_string_hash.mdx b/api_docs/kbn_ml_string_hash.mdx index 0e64277b4e11..5ba83404cd00 100644 --- a/api_docs/kbn_ml_string_hash.mdx +++ b/api_docs/kbn_ml_string_hash.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-string-hash title: "@kbn/ml-string-hash" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-string-hash plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-string-hash'] --- import kbnMlStringHashObj from './kbn_ml_string_hash.devdocs.json'; diff --git a/api_docs/kbn_monaco.mdx b/api_docs/kbn_monaco.mdx index aa6506c9fc1c..6a179a946c13 100644 --- a/api_docs/kbn_monaco.mdx +++ b/api_docs/kbn_monaco.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-monaco title: "@kbn/monaco" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/monaco plugin -date: 2022-10-24 +date: 2022-10-26 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 76c6259d827a..6f8423bac647 100644 --- a/api_docs/kbn_optimizer.mdx +++ b/api_docs/kbn_optimizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-optimizer title: "@kbn/optimizer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/optimizer plugin -date: 2022-10-24 +date: 2022-10-26 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 69615c614643..9b903b0d0a57 100644 --- a/api_docs/kbn_optimizer_webpack_helpers.mdx +++ b/api_docs/kbn_optimizer_webpack_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-optimizer-webpack-helpers title: "@kbn/optimizer-webpack-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/optimizer-webpack-helpers plugin -date: 2022-10-24 +date: 2022-10-26 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 1eef3bdde91f..e5aa8fe47c8f 100644 --- a/api_docs/kbn_osquery_io_ts_types.mdx +++ b/api_docs/kbn_osquery_io_ts_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-osquery-io-ts-types title: "@kbn/osquery-io-ts-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/osquery-io-ts-types plugin -date: 2022-10-24 +date: 2022-10-26 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 fea368c7794c..e781a705a2af 100644 --- a/api_docs/kbn_performance_testing_dataset_extractor.mdx +++ b/api_docs/kbn_performance_testing_dataset_extractor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-performance-testing-dataset-extractor title: "@kbn/performance-testing-dataset-extractor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/performance-testing-dataset-extractor plugin -date: 2022-10-24 +date: 2022-10-26 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 b824d0ddcb8f..a9bb35775a5d 100644 --- a/api_docs/kbn_plugin_generator.mdx +++ b/api_docs/kbn_plugin_generator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-generator title: "@kbn/plugin-generator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-generator plugin -date: 2022-10-24 +date: 2022-10-26 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 0f54b2a8e442..2d5513b795d2 100644 --- a/api_docs/kbn_plugin_helpers.mdx +++ b/api_docs/kbn_plugin_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-helpers title: "@kbn/plugin-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-helpers plugin -date: 2022-10-24 +date: 2022-10-26 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 7474c397ec7a..c699ca618be5 100644 --- a/api_docs/kbn_react_field.mdx +++ b/api_docs/kbn_react_field.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-field title: "@kbn/react-field" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-field plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-field'] --- import kbnReactFieldObj from './kbn_react_field.devdocs.json'; diff --git a/api_docs/kbn_repo_source_classifier.mdx b/api_docs/kbn_repo_source_classifier.mdx index c6eb8444a649..e6e8f3a6181f 100644 --- a/api_docs/kbn_repo_source_classifier.mdx +++ b/api_docs/kbn_repo_source_classifier.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-source-classifier title: "@kbn/repo-source-classifier" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-source-classifier plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-source-classifier'] --- import kbnRepoSourceClassifierObj from './kbn_repo_source_classifier.devdocs.json'; diff --git a/api_docs/kbn_rule_data_utils.mdx b/api_docs/kbn_rule_data_utils.mdx index ad075b918828..d0f64e0e5db9 100644 --- a/api_docs/kbn_rule_data_utils.mdx +++ b/api_docs/kbn_rule_data_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rule-data-utils title: "@kbn/rule-data-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rule-data-utils plugin -date: 2022-10-24 +date: 2022-10-26 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 1e164bccfc64..a2c7aa79846e 100644 --- a/api_docs/kbn_securitysolution_autocomplete.mdx +++ b/api_docs/kbn_securitysolution_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-autocomplete title: "@kbn/securitysolution-autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-autocomplete plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-autocomplete'] --- import kbnSecuritysolutionAutocompleteObj from './kbn_securitysolution_autocomplete.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_es_utils.mdx b/api_docs/kbn_securitysolution_es_utils.mdx index e81efd72ba92..2948eb1f9a1c 100644 --- a/api_docs/kbn_securitysolution_es_utils.mdx +++ b/api_docs/kbn_securitysolution_es_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-es-utils title: "@kbn/securitysolution-es-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-es-utils plugin -date: 2022-10-24 +date: 2022-10-26 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 9dbbabc241d2..c54c0f40025e 100644 --- a/api_docs/kbn_securitysolution_exception_list_components.mdx +++ b/api_docs/kbn_securitysolution_exception_list_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-exception-list-components title: "@kbn/securitysolution-exception-list-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-exception-list-components plugin -date: 2022-10-24 +date: 2022-10-26 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 528044567030..1993dfa0cacb 100644 --- a/api_docs/kbn_securitysolution_hook_utils.mdx +++ b/api_docs/kbn_securitysolution_hook_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-hook-utils title: "@kbn/securitysolution-hook-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-hook-utils plugin -date: 2022-10-24 +date: 2022-10-26 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 9510bef560cc..b66da17e36ac 100644 --- a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-alerting-types title: "@kbn/securitysolution-io-ts-alerting-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-alerting-types plugin -date: 2022-10-24 +date: 2022-10-26 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 18e34b6b3cb7..fbccfa543a41 100644 --- a/api_docs/kbn_securitysolution_io_ts_list_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_list_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-list-types title: "@kbn/securitysolution-io-ts-list-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-list-types plugin -date: 2022-10-24 +date: 2022-10-26 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 e235250c54f1..174a4219c633 100644 --- a/api_docs/kbn_securitysolution_io_ts_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-types title: "@kbn/securitysolution-io-ts-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-types plugin -date: 2022-10-24 +date: 2022-10-26 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 ea1ad269f395..e22da49f8a88 100644 --- a/api_docs/kbn_securitysolution_io_ts_utils.mdx +++ b/api_docs/kbn_securitysolution_io_ts_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-utils title: "@kbn/securitysolution-io-ts-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-utils plugin -date: 2022-10-24 +date: 2022-10-26 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 4656817f2267..dc652ea33ee2 100644 --- a/api_docs/kbn_securitysolution_list_api.mdx +++ b/api_docs/kbn_securitysolution_list_api.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-api title: "@kbn/securitysolution-list-api" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-api plugin -date: 2022-10-24 +date: 2022-10-26 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 c7a0a3f20849..91beb50b2c81 100644 --- a/api_docs/kbn_securitysolution_list_constants.mdx +++ b/api_docs/kbn_securitysolution_list_constants.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-constants title: "@kbn/securitysolution-list-constants" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-constants plugin -date: 2022-10-24 +date: 2022-10-26 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 7e55b4a50b8b..b0737a0ea494 100644 --- a/api_docs/kbn_securitysolution_list_hooks.mdx +++ b/api_docs/kbn_securitysolution_list_hooks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-hooks title: "@kbn/securitysolution-list-hooks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-hooks plugin -date: 2022-10-24 +date: 2022-10-26 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 7b035e960d03..879f3231a311 100644 --- a/api_docs/kbn_securitysolution_list_utils.mdx +++ b/api_docs/kbn_securitysolution_list_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-utils title: "@kbn/securitysolution-list-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-utils plugin -date: 2022-10-24 +date: 2022-10-26 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 d98405a80a1f..d4a8ba0f4fe6 100644 --- a/api_docs/kbn_securitysolution_rules.mdx +++ b/api_docs/kbn_securitysolution_rules.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-rules title: "@kbn/securitysolution-rules" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-rules plugin -date: 2022-10-24 +date: 2022-10-26 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 abfae9f0b144..eb224e157c27 100644 --- a/api_docs/kbn_securitysolution_t_grid.mdx +++ b/api_docs/kbn_securitysolution_t_grid.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-t-grid title: "@kbn/securitysolution-t-grid" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-t-grid plugin -date: 2022-10-24 +date: 2022-10-26 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 af5361b840a8..56cc686cf311 100644 --- a/api_docs/kbn_securitysolution_utils.mdx +++ b/api_docs/kbn_securitysolution_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-utils title: "@kbn/securitysolution-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-utils plugin -date: 2022-10-24 +date: 2022-10-26 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 494f78c7ba1e..f7752f1dc742 100644 --- a/api_docs/kbn_server_http_tools.mdx +++ b/api_docs/kbn_server_http_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-http-tools title: "@kbn/server-http-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-http-tools plugin -date: 2022-10-24 +date: 2022-10-26 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 a412845fc364..521b368cee0d 100644 --- a/api_docs/kbn_server_route_repository.mdx +++ b/api_docs/kbn_server_route_repository.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-route-repository title: "@kbn/server-route-repository" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-route-repository plugin -date: 2022-10-24 +date: 2022-10-26 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 498efc5f0bad..24d59f56c565 100644 --- a/api_docs/kbn_shared_svg.mdx +++ b/api_docs/kbn_shared_svg.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-svg title: "@kbn/shared-svg" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-svg plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-svg'] --- import kbnSharedSvgObj from './kbn_shared_svg.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx b/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx index 8b2165ba52d5..348469d99a1a 100644 --- a/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx +++ b/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-avatar-user-profile-components title: "@kbn/shared-ux-avatar-user-profile-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-avatar-user-profile-components plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-avatar-user-profile-components'] --- import kbnSharedUxAvatarUserProfileComponentsObj from './kbn_shared_ux_avatar_user_profile_components.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx b/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx index 97177c2991ae..eb4a52372a26 100644 --- a/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx +++ b/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-exit-full-screen-mocks title: "@kbn/shared-ux-button-exit-full-screen-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-exit-full-screen-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 6cd919181f52..f2495da52bf0 100644 --- a/api_docs/kbn_shared_ux_button_toolbar.mdx +++ b/api_docs/kbn_shared_ux_button_toolbar.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-toolbar title: "@kbn/shared-ux-button-toolbar" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-toolbar plugin -date: 2022-10-24 +date: 2022-10-26 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 7f46959ddd8b..80b1b340acd2 100644 --- a/api_docs/kbn_shared_ux_card_no_data.mdx +++ b/api_docs/kbn_shared_ux_card_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-card-no-data title: "@kbn/shared-ux-card-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-card-no-data plugin -date: 2022-10-24 +date: 2022-10-26 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 85d34487dfcf..ec351658c961 100644 --- a/api_docs/kbn_shared_ux_card_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_card_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-card-no-data-mocks title: "@kbn/shared-ux-card-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-card-no-data-mocks plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-card-no-data-mocks'] --- import kbnSharedUxCardNoDataMocksObj from './kbn_shared_ux_card_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx b/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx index 609878f69f13..36460df33b54 100644 --- a/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx +++ b/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-link-redirect-app-mocks title: "@kbn/shared-ux-link-redirect-app-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-link-redirect-app-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 b69b8e2f46ad..7cb911968f1a 100644 --- a/api_docs/kbn_shared_ux_markdown.mdx +++ b/api_docs/kbn_shared_ux_markdown.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-markdown title: "@kbn/shared-ux-markdown" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-markdown plugin -date: 2022-10-24 +date: 2022-10-26 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 2d5c809a7606..54cdb1f8e0fa 100644 --- a/api_docs/kbn_shared_ux_markdown_mocks.mdx +++ b/api_docs/kbn_shared_ux_markdown_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-markdown-mocks title: "@kbn/shared-ux-markdown-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-markdown-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 22e90a866b8d..aa11134cd636 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_analytics_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-analytics-no-data title: "@kbn/shared-ux-page-analytics-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-analytics-no-data plugin -date: 2022-10-24 +date: 2022-10-26 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 7b33923cba1f..6f261edc2162 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-analytics-no-data-mocks title: "@kbn/shared-ux-page-analytics-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-analytics-no-data-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 a4bb29372dcc..084189407fd6 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-no-data title: "@kbn/shared-ux-page-kibana-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-no-data plugin -date: 2022-10-24 +date: 2022-10-26 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 67efdf573d73..aca855a389e5 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-no-data-mocks title: "@kbn/shared-ux-page-kibana-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-no-data-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 7d3c32e43ea3..c13adb7242fa 100644 --- a/api_docs/kbn_shared_ux_page_kibana_template.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_template.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-template title: "@kbn/shared-ux-page-kibana-template" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-template plugin -date: 2022-10-24 +date: 2022-10-26 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 00ea90576060..7e4cef12c64d 100644 --- a/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-template-mocks title: "@kbn/shared-ux-page-kibana-template-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-template-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 43cee35fc731..a18670aa0cc0 100644 --- a/api_docs/kbn_shared_ux_page_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data title: "@kbn/shared-ux-page-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data plugin -date: 2022-10-24 +date: 2022-10-26 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 324601274926..88e4da9fd19d 100644 --- a/api_docs/kbn_shared_ux_page_no_data_config.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-config title: "@kbn/shared-ux-page-no-data-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-config plugin -date: 2022-10-24 +date: 2022-10-26 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 6991100e3b12..26db793d5934 100644 --- a/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-config-mocks title: "@kbn/shared-ux-page-no-data-config-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-config-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 70a6c8cb661a..0860b4962cf2 100644 --- a/api_docs/kbn_shared_ux_page_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-mocks title: "@kbn/shared-ux-page-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 6ce6953b106c..1ea08c8a93c3 100644 --- a/api_docs/kbn_shared_ux_page_solution_nav.mdx +++ b/api_docs/kbn_shared_ux_page_solution_nav.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-solution-nav title: "@kbn/shared-ux-page-solution-nav" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-solution-nav plugin -date: 2022-10-24 +date: 2022-10-26 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 9f40e4d8642c..3ee83bb92cea 100644 --- a/api_docs/kbn_shared_ux_prompt_no_data_views.mdx +++ b/api_docs/kbn_shared_ux_prompt_no_data_views.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-no-data-views title: "@kbn/shared-ux-prompt-no-data-views" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-no-data-views plugin -date: 2022-10-24 +date: 2022-10-26 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 6ee56c4ff243..2714f4dc9db0 100644 --- a/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx +++ b/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-no-data-views-mocks title: "@kbn/shared-ux-prompt-no-data-views-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-no-data-views-mocks plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-no-data-views-mocks'] --- import kbnSharedUxPromptNoDataViewsMocksObj from './kbn_shared_ux_prompt_no_data_views_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_router.mdx b/api_docs/kbn_shared_ux_router.mdx index 75b8a266c803..a120ab449c42 100644 --- a/api_docs/kbn_shared_ux_router.mdx +++ b/api_docs/kbn_shared_ux_router.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-router title: "@kbn/shared-ux-router" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-router plugin -date: 2022-10-24 +date: 2022-10-26 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 3083ac4bc322..3959e22b6782 100644 --- a/api_docs/kbn_shared_ux_router_mocks.mdx +++ b/api_docs/kbn_shared_ux_router_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-router-mocks title: "@kbn/shared-ux-router-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-router-mocks plugin -date: 2022-10-24 +date: 2022-10-26 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 13c66eb509d6..5e483e31df87 100644 --- a/api_docs/kbn_shared_ux_storybook_config.mdx +++ b/api_docs/kbn_shared_ux_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-storybook-config title: "@kbn/shared-ux-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-storybook-config plugin -date: 2022-10-24 +date: 2022-10-26 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 28b2571ba23c..833d7d5efe88 100644 --- a/api_docs/kbn_shared_ux_storybook_mock.mdx +++ b/api_docs/kbn_shared_ux_storybook_mock.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-storybook-mock title: "@kbn/shared-ux-storybook-mock" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-storybook-mock plugin -date: 2022-10-24 +date: 2022-10-26 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 5cbf90e820b1..3595cdb1e7aa 100644 --- a/api_docs/kbn_shared_ux_utility.mdx +++ b/api_docs/kbn_shared_ux_utility.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-utility title: "@kbn/shared-ux-utility" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-utility plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-utility'] --- import kbnSharedUxUtilityObj from './kbn_shared_ux_utility.devdocs.json'; diff --git a/api_docs/kbn_some_dev_log.mdx b/api_docs/kbn_some_dev_log.mdx index a8fade48b841..608dd258248d 100644 --- a/api_docs/kbn_some_dev_log.mdx +++ b/api_docs/kbn_some_dev_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-some-dev-log title: "@kbn/some-dev-log" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/some-dev-log plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/some-dev-log'] --- import kbnSomeDevLogObj from './kbn_some_dev_log.devdocs.json'; diff --git a/api_docs/kbn_sort_package_json.mdx b/api_docs/kbn_sort_package_json.mdx index 0f52c22ab829..0b084d8eb8dd 100644 --- a/api_docs/kbn_sort_package_json.mdx +++ b/api_docs/kbn_sort_package_json.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-sort-package-json title: "@kbn/sort-package-json" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/sort-package-json plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/sort-package-json'] --- import kbnSortPackageJsonObj from './kbn_sort_package_json.devdocs.json'; diff --git a/api_docs/kbn_std.mdx b/api_docs/kbn_std.mdx index 16bad577d451..31535f43eeee 100644 --- a/api_docs/kbn_std.mdx +++ b/api_docs/kbn_std.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-std title: "@kbn/std" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/std plugin -date: 2022-10-24 +date: 2022-10-26 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 357df78b62e9..8c86572bb8b4 100644 --- a/api_docs/kbn_stdio_dev_helpers.mdx +++ b/api_docs/kbn_stdio_dev_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-stdio-dev-helpers title: "@kbn/stdio-dev-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/stdio-dev-helpers plugin -date: 2022-10-24 +date: 2022-10-26 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 e7d63df0603b..54f9ca290a22 100644 --- a/api_docs/kbn_storybook.mdx +++ b/api_docs/kbn_storybook.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-storybook title: "@kbn/storybook" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/storybook plugin -date: 2022-10-24 +date: 2022-10-26 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 78d10c2515e8..47e675c85739 100644 --- a/api_docs/kbn_telemetry_tools.mdx +++ b/api_docs/kbn_telemetry_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-telemetry-tools title: "@kbn/telemetry-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/telemetry-tools plugin -date: 2022-10-24 +date: 2022-10-26 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 accfaf018d1c..9c74d8ee9e7a 100644 --- a/api_docs/kbn_test.mdx +++ b/api_docs/kbn_test.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test title: "@kbn/test" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test plugin -date: 2022-10-24 +date: 2022-10-26 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 69313aba986c..195e1ab37b4c 100644 --- a/api_docs/kbn_test_jest_helpers.mdx +++ b/api_docs/kbn_test_jest_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-jest-helpers title: "@kbn/test-jest-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-jest-helpers plugin -date: 2022-10-24 +date: 2022-10-26 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 e71b065f059f..7874d0e009ef 100644 --- a/api_docs/kbn_test_subj_selector.mdx +++ b/api_docs/kbn_test_subj_selector.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-subj-selector title: "@kbn/test-subj-selector" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-subj-selector plugin -date: 2022-10-24 +date: 2022-10-26 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 ef0a436c7bf4..f4ec62650f65 100644 --- a/api_docs/kbn_tooling_log.mdx +++ b/api_docs/kbn_tooling_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-tooling-log title: "@kbn/tooling-log" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/tooling-log plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/tooling-log'] --- import kbnToolingLogObj from './kbn_tooling_log.devdocs.json'; diff --git a/api_docs/kbn_type_summarizer.mdx b/api_docs/kbn_type_summarizer.mdx index ad50cb53ca5b..8e4136e11ce0 100644 --- a/api_docs/kbn_type_summarizer.mdx +++ b/api_docs/kbn_type_summarizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-type-summarizer title: "@kbn/type-summarizer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/type-summarizer plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/type-summarizer'] --- import kbnTypeSummarizerObj from './kbn_type_summarizer.devdocs.json'; diff --git a/api_docs/kbn_type_summarizer_core.mdx b/api_docs/kbn_type_summarizer_core.mdx index ff546645373a..07c644b51041 100644 --- a/api_docs/kbn_type_summarizer_core.mdx +++ b/api_docs/kbn_type_summarizer_core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-type-summarizer-core title: "@kbn/type-summarizer-core" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/type-summarizer-core plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/type-summarizer-core'] --- import kbnTypeSummarizerCoreObj from './kbn_type_summarizer_core.devdocs.json'; diff --git a/api_docs/kbn_typed_react_router_config.mdx b/api_docs/kbn_typed_react_router_config.mdx index 9d53a2f0f678..076636862fe7 100644 --- a/api_docs/kbn_typed_react_router_config.mdx +++ b/api_docs/kbn_typed_react_router_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-typed-react-router-config title: "@kbn/typed-react-router-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/typed-react-router-config plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/typed-react-router-config'] --- import kbnTypedReactRouterConfigObj from './kbn_typed_react_router_config.devdocs.json'; diff --git a/api_docs/kbn_ui_theme.mdx b/api_docs/kbn_ui_theme.mdx index dfc13d67cd0d..59f948eb187c 100644 --- a/api_docs/kbn_ui_theme.mdx +++ b/api_docs/kbn_ui_theme.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-theme title: "@kbn/ui-theme" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-theme plugin -date: 2022-10-24 +date: 2022-10-26 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 b7efb422b19e..84620e9ed7b2 100644 --- a/api_docs/kbn_user_profile_components.mdx +++ b/api_docs/kbn_user_profile_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-user-profile-components title: "@kbn/user-profile-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/user-profile-components plugin -date: 2022-10-24 +date: 2022-10-26 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 1fab2bde1e82..6393b8b27fd4 100644 --- a/api_docs/kbn_utility_types.mdx +++ b/api_docs/kbn_utility_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utility-types title: "@kbn/utility-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utility-types plugin -date: 2022-10-24 +date: 2022-10-26 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 80d57bde057b..16288c6b0b12 100644 --- a/api_docs/kbn_utility_types_jest.mdx +++ b/api_docs/kbn_utility_types_jest.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utility-types-jest title: "@kbn/utility-types-jest" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utility-types-jest plugin -date: 2022-10-24 +date: 2022-10-26 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 a6d1ba795bd4..5d6f1953ab95 100644 --- a/api_docs/kbn_utils.mdx +++ b/api_docs/kbn_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utils title: "@kbn/utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utils plugin -date: 2022-10-24 +date: 2022-10-26 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 dc8f0ba90668..4420868e4520 100644 --- a/api_docs/kbn_yarn_lock_validator.mdx +++ b/api_docs/kbn_yarn_lock_validator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-yarn-lock-validator title: "@kbn/yarn-lock-validator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/yarn-lock-validator plugin -date: 2022-10-24 +date: 2022-10-26 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 6eb1a4a9dc5d..0b8e8e5ff3ca 100644 --- a/api_docs/kibana_overview.mdx +++ b/api_docs/kibana_overview.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaOverview title: "kibanaOverview" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaOverview plugin -date: 2022-10-24 +date: 2022-10-26 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 c8ab8b7f6be4..93ff7e5caeaa 100644 --- a/api_docs/kibana_react.mdx +++ b/api_docs/kibana_react.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaReact title: "kibanaReact" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaReact plugin -date: 2022-10-24 +date: 2022-10-26 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 47e25541320c..48801e428d8a 100644 --- a/api_docs/kibana_utils.mdx +++ b/api_docs/kibana_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaUtils title: "kibanaUtils" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaUtils plugin -date: 2022-10-24 +date: 2022-10-26 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 a8ecbdd425e5..57f95e3f53f0 100644 --- a/api_docs/kubernetes_security.mdx +++ b/api_docs/kubernetes_security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kubernetesSecurity title: "kubernetesSecurity" image: https://source.unsplash.com/400x175/?github description: API docs for the kubernetesSecurity plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kubernetesSecurity'] --- import kubernetesSecurityObj from './kubernetes_security.devdocs.json'; diff --git a/api_docs/lens.devdocs.json b/api_docs/lens.devdocs.json index 01f255cfbf6c..6269a6ceded4 100644 --- a/api_docs/lens.devdocs.json +++ b/api_docs/lens.devdocs.json @@ -35,7 +35,7 @@ }, ", ", "LensEmbeddableOutput", - "> implements ", + ", any> implements ", { "pluginId": "embeddable", "scope": "public", @@ -2053,6 +2053,20 @@ "path": "x-pack/plugins/lens/public/datasources/form_based/types.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "lens", + "id": "def-public.FormBasedLayer.sampling", + "type": "number", + "tags": [], + "label": "sampling", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "x-pack/plugins/lens/public/datasources/form_based/types.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -5102,7 +5116,7 @@ "signature": [ "((layerId: string, state: T, setState: ", "StateSetter", - ") => ", + ", openLayerSettings?: (() => void) | undefined) => ", "LayerAction", "[]) | undefined" ], @@ -5155,6 +5169,21 @@ "deprecated": false, "trackAdoption": false, "isRequired": true + }, + { + "parentPluginId": "lens", + "id": "def-public.Visualization.getSupportedActionsForLayer.$4", + "type": "Function", + "tags": [], + "label": "openLayerSettings", + "description": [], + "signature": [ + "(() => void) | undefined" + ], + "path": "x-pack/plugins/lens/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false } ], "returnComment": [] @@ -5781,6 +5810,56 @@ ], "returnComment": [] }, + { + "parentPluginId": "lens", + "id": "def-public.Visualization.renderLayerSettings", + "type": "Function", + "tags": [], + "label": "renderLayerSettings", + "description": [], + "signature": [ + "((domElement: Element, props: ", + "VisualizationLayerSettingsProps", + ") => void | ((cleanupElement: Element) => void)) | undefined" + ], + "path": "x-pack/plugins/lens/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "lens", + "id": "def-public.Visualization.renderLayerSettings.$1", + "type": "Object", + "tags": [], + "label": "domElement", + "description": [], + "signature": [ + "Element" + ], + "path": "x-pack/plugins/lens/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "lens", + "id": "def-public.Visualization.renderLayerSettings.$2", + "type": "CompoundType", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "VisualizationLayerSettingsProps", + "" + ], + "path": "x-pack/plugins/lens/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, { "parentPluginId": "lens", "id": "def-public.Visualization.renderDimensionEditor", @@ -8980,7 +9059,7 @@ "section": "def-public.IncompleteColumn", "text": "IncompleteColumn" }, - "> | undefined; }" + "> | undefined; sampling?: number | undefined; }" ], "path": "x-pack/plugins/lens/public/datasources/form_based/types.ts", "deprecated": false, diff --git a/api_docs/lens.mdx b/api_docs/lens.mdx index 2677b9e80acd..506f33e6421f 100644 --- a/api_docs/lens.mdx +++ b/api_docs/lens.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/lens title: "lens" image: https://source.unsplash.com/400x175/?github description: API docs for the lens plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lens'] --- import lensObj from './lens.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 669 | 0 | 576 | 46 | +| 674 | 0 | 581 | 47 | ## Client diff --git a/api_docs/license_api_guard.mdx b/api_docs/license_api_guard.mdx index 11f663a022cb..73fe101c7d11 100644 --- a/api_docs/license_api_guard.mdx +++ b/api_docs/license_api_guard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licenseApiGuard title: "licenseApiGuard" image: https://source.unsplash.com/400x175/?github description: API docs for the licenseApiGuard plugin -date: 2022-10-24 +date: 2022-10-26 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 e44638edb512..e6439d0bd216 100644 --- a/api_docs/license_management.mdx +++ b/api_docs/license_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licenseManagement title: "licenseManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the licenseManagement plugin -date: 2022-10-24 +date: 2022-10-26 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 6462cd09322a..c9971bfb4c83 100644 --- a/api_docs/licensing.mdx +++ b/api_docs/licensing.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licensing title: "licensing" image: https://source.unsplash.com/400x175/?github description: API docs for the licensing plugin -date: 2022-10-24 +date: 2022-10-26 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 1eb5b5b3bf28..3ae93eab8218 100644 --- a/api_docs/lists.mdx +++ b/api_docs/lists.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/lists title: "lists" image: https://source.unsplash.com/400x175/?github description: API docs for the lists plugin -date: 2022-10-24 +date: 2022-10-26 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 3adfdd91e151..bfc17b7a53bb 100644 --- a/api_docs/management.mdx +++ b/api_docs/management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/management title: "management" image: https://source.unsplash.com/400x175/?github description: API docs for the management plugin -date: 2022-10-24 +date: 2022-10-26 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 752becaa2ceb..2350f3b292bc 100644 --- a/api_docs/maps.devdocs.json +++ b/api_docs/maps.devdocs.json @@ -200,7 +200,7 @@ "section": "def-public.MapEmbeddableOutput", "text": "MapEmbeddableOutput" }, - "> implements ", + ", any> implements ", { "pluginId": "embeddable", "scope": "public", diff --git a/api_docs/maps.mdx b/api_docs/maps.mdx index 2d39c4b7516e..467026ac79d7 100644 --- a/api_docs/maps.mdx +++ b/api_docs/maps.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/maps title: "maps" image: https://source.unsplash.com/400x175/?github description: API docs for the maps plugin -date: 2022-10-24 +date: 2022-10-26 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 20c83d460c62..f1c6e4c38c5e 100644 --- a/api_docs/maps_ems.mdx +++ b/api_docs/maps_ems.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/mapsEms title: "mapsEms" image: https://source.unsplash.com/400x175/?github description: API docs for the mapsEms plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'mapsEms'] --- import mapsEmsObj from './maps_ems.devdocs.json'; diff --git a/api_docs/ml.devdocs.json b/api_docs/ml.devdocs.json index 792bc38f914f..e407bdfa5368 100644 --- a/api_docs/ml.devdocs.json +++ b/api_docs/ml.devdocs.json @@ -639,6 +639,19 @@ "path": "x-pack/plugins/ml/common/types/anomalies.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "ml", + "id": "def-public.AnomaliesTableRecord.modelPlotEnabled", + "type": "boolean", + "tags": [], + "label": "modelPlotEnabled", + "description": [ + "\nReturns true if the job has the model plot enabled" + ], + "path": "x-pack/plugins/ml/common/types/anomalies.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -2066,6 +2079,19 @@ "path": "x-pack/plugins/ml/common/types/anomalies.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "ml", + "id": "def-server.AnomaliesTableRecord.modelPlotEnabled", + "type": "boolean", + "tags": [], + "label": "modelPlotEnabled", + "description": [ + "\nReturns true if the job has the model plot enabled" + ], + "path": "x-pack/plugins/ml/common/types/anomalies.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -2434,6 +2460,22 @@ "path": "x-pack/plugins/ml/common/types/anomalies.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "ml", + "id": "def-server.AnomalyRecordDoc.anomaly_score_explanation", + "type": "Object", + "tags": [], + "label": "anomaly_score_explanation", + "description": [ + "\nAn explanation for the anomaly score" + ], + "signature": [ + "{ anomaly_type?: \"dip\" | \"spike\" | undefined; anomaly_length?: number | undefined; single_bucket_impact?: number | undefined; multi_bucket_impact?: number | undefined; anomaly_characteristics_impact?: number | undefined; lower_confidence_bound?: number | undefined; typical_value?: number | undefined; upper_confidence_bound?: number | undefined; high_variance_penalty?: boolean | undefined; incomplete_bucket_penalty?: boolean | undefined; } | undefined" + ], + "path": "x-pack/plugins/ml/common/types/anomalies.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false diff --git a/api_docs/ml.mdx b/api_docs/ml.mdx index ef16305a02e1..53400c77ffcc 100644 --- a/api_docs/ml.mdx +++ b/api_docs/ml.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ml title: "ml" image: https://source.unsplash.com/400x175/?github description: API docs for the ml plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ml'] --- import mlObj from './ml.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Machine Learning UI](https://github.com/orgs/elastic/teams/ml-ui) for q | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 251 | 9 | 78 | 39 | +| 254 | 9 | 78 | 39 | ## Client diff --git a/api_docs/monitoring.mdx b/api_docs/monitoring.mdx index a6fd852fdd87..60a4d8717506 100644 --- a/api_docs/monitoring.mdx +++ b/api_docs/monitoring.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/monitoring title: "monitoring" image: https://source.unsplash.com/400x175/?github description: API docs for the monitoring plugin -date: 2022-10-24 +date: 2022-10-26 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 c1da6184b64e..62735c97e59e 100644 --- a/api_docs/monitoring_collection.mdx +++ b/api_docs/monitoring_collection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/monitoringCollection title: "monitoringCollection" image: https://source.unsplash.com/400x175/?github description: API docs for the monitoringCollection plugin -date: 2022-10-24 +date: 2022-10-26 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 4a46bfd5f475..f27311bbf516 100644 --- a/api_docs/navigation.mdx +++ b/api_docs/navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/navigation title: "navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the navigation plugin -date: 2022-10-24 +date: 2022-10-26 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 b389d448179d..1f27ada9f604 100644 --- a/api_docs/newsfeed.mdx +++ b/api_docs/newsfeed.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/newsfeed title: "newsfeed" image: https://source.unsplash.com/400x175/?github description: API docs for the newsfeed plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'newsfeed'] --- import newsfeedObj from './newsfeed.devdocs.json'; diff --git a/api_docs/observability.devdocs.json b/api_docs/observability.devdocs.json index 2c64c1c5b973..17f675ec0596 100644 --- a/api_docs/observability.devdocs.json +++ b/api_docs/observability.devdocs.json @@ -368,7 +368,7 @@ "label": "ExploratoryViewContextProvider", "description": [], "signature": [ - "({\n children,\n reportTypes,\n dataTypes,\n dataViews,\n reportConfigMap,\n setHeaderActionMenu,\n asPanel = true,\n theme$,\n}: { children: JSX.Element; } & ExploratoryViewContextValue) => JSX.Element" + "({\n children,\n reportTypes,\n dataTypes,\n reportConfigMap,\n setHeaderActionMenu,\n asPanel = true,\n theme$,\n}: { children: JSX.Element; } & ExploratoryViewContextValue) => JSX.Element" ], "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/contexts/exploratory_view_config.tsx", "deprecated": false, @@ -379,7 +379,7 @@ "id": "def-public.ExploratoryViewContextProvider.$1", "type": "CompoundType", "tags": [], - "label": "{\n children,\n reportTypes,\n dataTypes,\n dataViews,\n reportConfigMap,\n setHeaderActionMenu,\n asPanel = true,\n theme$,\n}", + "label": "{\n children,\n reportTypes,\n dataTypes,\n reportConfigMap,\n setHeaderActionMenu,\n asPanel = true,\n theme$,\n}", "description": [], "signature": [ "{ children: JSX.Element; } & ExploratoryViewContextValue" @@ -7780,7 +7780,7 @@ "section": "def-server.ObservabilityRouteHandlerResources", "text": "ObservabilityRouteHandlerResources" }, - ", { id: string; name: string; description: string; indicator: { type: string; params: { environment: string; service: string; transaction_type: string; transaction_name: string; 'threshold.us': number; }; } | { type: string; params: { environment: string; service: string; transaction_type: string; transaction_name: string; } & { good_status_codes?: (\"2xx\" | \"3xx\" | \"4xx\" | \"5xx\")[] | undefined; }; }; time_window: { duration: string; is_rolling: boolean; } | { duration: string; calendar: { start_time: string; }; }; budgeting_method: string; objective: { target: number; } & { timeslice_target?: number | undefined; timeslice_window?: string | undefined; }; summary: { sli_value: number; error_budget: { initial: number; consumed: number; remaining: number; }; }; revision: number; created_at: string; updated_at: string; }, ", + ", { id: string; name: string; description: string; indicator: { type: \"slo.apm.transaction_duration\"; params: { environment: string; service: string; transaction_type: string; transaction_name: string; 'threshold.us': number; }; } | { type: \"slo.apm.transaction_error_rate\"; params: { environment: string; service: string; transaction_type: string; transaction_name: string; } & { good_status_codes?: (\"2xx\" | \"3xx\" | \"4xx\" | \"5xx\")[] | undefined; }; } | { type: \"slo.kql.custom\"; params: { index: string; query_filter: string; numerator: string; denominator: string; }; }; time_window: { duration: string; is_rolling: boolean; } | { duration: string; calendar: { start_time: string; }; }; budgeting_method: string; objective: { target: number; } & { timeslice_target?: number | undefined; timeslice_window?: string | undefined; }; summary: { sli_value: number; error_budget: { initial: number; consumed: number; remaining: number; }; }; revision: number; created_at: string; updated_at: string; }, ", { "pluginId": "observability", "scope": "server", @@ -7808,7 +7808,7 @@ "TypeC", "<{ type: ", "LiteralC", - "; params: ", + "<\"slo.apm.transaction_duration\">; params: ", "TypeC", "<{ environment: ", "UnionC", @@ -7840,7 +7840,7 @@ "TypeC", "<{ type: ", "LiteralC", - "; params: ", + "<\"slo.apm.transaction_error_rate\">; params: ", "IntersectionC", "<[", "TypeC", @@ -7882,7 +7882,21 @@ "LiteralC", "<\"4xx\">, ", "LiteralC", - "<\"5xx\">]>>; }>]>; }>]>; time_window: ", + "<\"5xx\">]>>; }>]>; }>, ", + "TypeC", + "<{ type: ", + "LiteralC", + "<\"slo.kql.custom\">; params: ", + "TypeC", + "<{ index: ", + "StringC", + "; query_filter: ", + "StringC", + "; numerator: ", + "StringC", + "; denominator: ", + "StringC", + "; }>; }>]>; time_window: ", "UnionC", "<[", "TypeC", @@ -7930,7 +7944,7 @@ "section": "def-server.ObservabilityRouteHandlerResources", "text": "ObservabilityRouteHandlerResources" }, - ", { id: string; name: string; description: string; indicator: { type: string; params: { environment: string; service: string; transaction_type: string; transaction_name: string; 'threshold.us': number; }; } | { type: string; params: { environment: string; service: string; transaction_type: string; transaction_name: string; } & { good_status_codes?: (\"2xx\" | \"3xx\" | \"4xx\" | \"5xx\")[] | undefined; }; }; time_window: { duration: string; is_rolling: boolean; } | { duration: string; calendar: { start_time: string; }; }; budgeting_method: string; objective: { target: number; } & { timeslice_target?: number | undefined; timeslice_window?: string | undefined; }; created_at: string; updated_at: string; }, ", + ", { id: string; name: string; description: string; indicator: { type: \"slo.apm.transaction_duration\"; params: { environment: string; service: string; transaction_type: string; transaction_name: string; 'threshold.us': number; }; } | { type: \"slo.apm.transaction_error_rate\"; params: { environment: string; service: string; transaction_type: string; transaction_name: string; } & { good_status_codes?: (\"2xx\" | \"3xx\" | \"4xx\" | \"5xx\")[] | undefined; }; } | { type: \"slo.kql.custom\"; params: { index: string; query_filter: string; numerator: string; denominator: string; }; }; time_window: { duration: string; is_rolling: boolean; } | { duration: string; calendar: { start_time: string; }; }; budgeting_method: string; objective: { target: number; } & { timeslice_target?: number | undefined; timeslice_window?: string | undefined; }; created_at: string; updated_at: string; }, ", { "pluginId": "observability", "scope": "server", @@ -7954,7 +7968,7 @@ "TypeC", "<{ type: ", "LiteralC", - "; params: ", + "<\"slo.apm.transaction_duration\">; params: ", "TypeC", "<{ environment: ", "UnionC", @@ -7986,7 +8000,7 @@ "TypeC", "<{ type: ", "LiteralC", - "; params: ", + "<\"slo.apm.transaction_error_rate\">; params: ", "IntersectionC", "<[", "TypeC", @@ -8028,7 +8042,21 @@ "LiteralC", "<\"4xx\">, ", "LiteralC", - "<\"5xx\">]>>; }>]>; }>]>; time_window: ", + "<\"5xx\">]>>; }>]>; }>, ", + "TypeC", + "<{ type: ", + "LiteralC", + "<\"slo.kql.custom\">; params: ", + "TypeC", + "<{ index: ", + "StringC", + "; query_filter: ", + "StringC", + "; numerator: ", + "StringC", + "; denominator: ", + "StringC", + "; }>; }>]>; time_window: ", "UnionC", "<[", "TypeC", @@ -8186,7 +8214,7 @@ "section": "def-server.ObservabilityRouteHandlerResources", "text": "ObservabilityRouteHandlerResources" }, - ", { id: string; name: string; description: string; indicator: { type: string; params: { environment: string; service: string; transaction_type: string; transaction_name: string; 'threshold.us': number; }; } | { type: string; params: { environment: string; service: string; transaction_type: string; transaction_name: string; } & { good_status_codes?: (\"2xx\" | \"3xx\" | \"4xx\" | \"5xx\")[] | undefined; }; }; time_window: { duration: string; is_rolling: boolean; } | { duration: string; calendar: { start_time: string; }; }; budgeting_method: string; objective: { target: number; } & { timeslice_target?: number | undefined; timeslice_window?: string | undefined; }; summary: { sli_value: number; error_budget: { initial: number; consumed: number; remaining: number; }; }; revision: number; created_at: string; updated_at: string; }, ", + ", { id: string; name: string; description: string; indicator: { type: \"slo.apm.transaction_duration\"; params: { environment: string; service: string; transaction_type: string; transaction_name: string; 'threshold.us': number; }; } | { type: \"slo.apm.transaction_error_rate\"; params: { environment: string; service: string; transaction_type: string; transaction_name: string; } & { good_status_codes?: (\"2xx\" | \"3xx\" | \"4xx\" | \"5xx\")[] | undefined; }; } | { type: \"slo.kql.custom\"; params: { index: string; query_filter: string; numerator: string; denominator: string; }; }; time_window: { duration: string; is_rolling: boolean; } | { duration: string; calendar: { start_time: string; }; }; budgeting_method: string; objective: { target: number; } & { timeslice_target?: number | undefined; timeslice_window?: string | undefined; }; summary: { sli_value: number; error_budget: { initial: number; consumed: number; remaining: number; }; }; revision: number; created_at: string; updated_at: string; }, ", { "pluginId": "observability", "scope": "server", @@ -8214,7 +8242,7 @@ "TypeC", "<{ type: ", "LiteralC", - "; params: ", + "<\"slo.apm.transaction_duration\">; params: ", "TypeC", "<{ environment: ", "UnionC", @@ -8246,7 +8274,7 @@ "TypeC", "<{ type: ", "LiteralC", - "; params: ", + "<\"slo.apm.transaction_error_rate\">; params: ", "IntersectionC", "<[", "TypeC", @@ -8288,7 +8316,21 @@ "LiteralC", "<\"4xx\">, ", "LiteralC", - "<\"5xx\">]>>; }>]>; }>]>; time_window: ", + "<\"5xx\">]>>; }>]>; }>, ", + "TypeC", + "<{ type: ", + "LiteralC", + "<\"slo.kql.custom\">; params: ", + "TypeC", + "<{ index: ", + "StringC", + "; query_filter: ", + "StringC", + "; numerator: ", + "StringC", + "; denominator: ", + "StringC", + "; }>; }>]>; time_window: ", "UnionC", "<[", "TypeC", @@ -8336,7 +8378,7 @@ "section": "def-server.ObservabilityRouteHandlerResources", "text": "ObservabilityRouteHandlerResources" }, - ", { id: string; name: string; description: string; indicator: { type: string; params: { environment: string; service: string; transaction_type: string; transaction_name: string; 'threshold.us': number; }; } | { type: string; params: { environment: string; service: string; transaction_type: string; transaction_name: string; } & { good_status_codes?: (\"2xx\" | \"3xx\" | \"4xx\" | \"5xx\")[] | undefined; }; }; time_window: { duration: string; is_rolling: boolean; } | { duration: string; calendar: { start_time: string; }; }; budgeting_method: string; objective: { target: number; } & { timeslice_target?: number | undefined; timeslice_window?: string | undefined; }; created_at: string; updated_at: string; }, ", + ", { id: string; name: string; description: string; indicator: { type: \"slo.apm.transaction_duration\"; params: { environment: string; service: string; transaction_type: string; transaction_name: string; 'threshold.us': number; }; } | { type: \"slo.apm.transaction_error_rate\"; params: { environment: string; service: string; transaction_type: string; transaction_name: string; } & { good_status_codes?: (\"2xx\" | \"3xx\" | \"4xx\" | \"5xx\")[] | undefined; }; } | { type: \"slo.kql.custom\"; params: { index: string; query_filter: string; numerator: string; denominator: string; }; }; time_window: { duration: string; is_rolling: boolean; } | { duration: string; calendar: { start_time: string; }; }; budgeting_method: string; objective: { target: number; } & { timeslice_target?: number | undefined; timeslice_window?: string | undefined; }; created_at: string; updated_at: string; }, ", { "pluginId": "observability", "scope": "server", @@ -8360,7 +8402,7 @@ "TypeC", "<{ type: ", "LiteralC", - "; params: ", + "<\"slo.apm.transaction_duration\">; params: ", "TypeC", "<{ environment: ", "UnionC", @@ -8392,7 +8434,7 @@ "TypeC", "<{ type: ", "LiteralC", - "; params: ", + "<\"slo.apm.transaction_error_rate\">; params: ", "IntersectionC", "<[", "TypeC", @@ -8434,7 +8476,21 @@ "LiteralC", "<\"4xx\">, ", "LiteralC", - "<\"5xx\">]>>; }>]>; }>]>; time_window: ", + "<\"5xx\">]>>; }>]>; }>, ", + "TypeC", + "<{ type: ", + "LiteralC", + "<\"slo.kql.custom\">; params: ", + "TypeC", + "<{ index: ", + "StringC", + "; query_filter: ", + "StringC", + "; numerator: ", + "StringC", + "; denominator: ", + "StringC", + "; }>; }>]>; time_window: ", "UnionC", "<[", "TypeC", diff --git a/api_docs/observability.mdx b/api_docs/observability.mdx index a8d5cf35b064..0f2eb1813795 100644 --- a/api_docs/observability.mdx +++ b/api_docs/observability.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observability title: "observability" image: https://source.unsplash.com/400x175/?github description: API docs for the observability plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observability'] --- import observabilityObj from './observability.devdocs.json'; diff --git a/api_docs/osquery.mdx b/api_docs/osquery.mdx index 69bd76b53f83..ec9ef78a21bd 100644 --- a/api_docs/osquery.mdx +++ b/api_docs/osquery.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/osquery title: "osquery" image: https://source.unsplash.com/400x175/?github description: API docs for the osquery plugin -date: 2022-10-24 +date: 2022-10-26 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 b014b1dc15c7..2f3b9058ec84 100644 --- a/api_docs/plugin_directory.mdx +++ b/api_docs/plugin_directory.mdx @@ -7,7 +7,7 @@ id: kibDevDocsPluginDirectory slug: /kibana-dev-docs/api-meta/plugin-api-directory title: Directory description: Directory of public APIs available through plugins or packages. -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -21,7 +21,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | API Count | Any Count | Missing comments | Missing exports | |--------------|----------|-----------------|--------| -| 32648 | 179 | 21947 | 1032 | +| 32684 | 179 | 21967 | 1038 | ## Plugin Directory @@ -30,8 +30,8 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 214 | 0 | 209 | 23 | | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 36 | 1 | 32 | 2 | | | [Machine Learning UI](https://github.com/orgs/elastic/teams/ml-ui) | AIOps plugin maintained by ML team. | 9 | 0 | 0 | 2 | -| | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 382 | 0 | 373 | 24 | -| | [APM UI](https://github.com/orgs/elastic/teams/apm-ui) | The user interface for Elastic APM | 38 | 0 | 38 | 53 | +| | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 382 | 0 | 373 | 26 | +| | [APM UI](https://github.com/orgs/elastic/teams/apm-ui) | The user interface for Elastic APM | 38 | 0 | 38 | 56 | | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 9 | 0 | 9 | 0 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Considering using bfetch capabilities when fetching large amounts of data. This services supports batching HTTP requests and streaming responses back. | 81 | 1 | 72 | 2 | | | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds Canvas application to Kibana | 9 | 0 | 8 | 3 | @@ -45,13 +45,13 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | cloudLinks | [Kibana Core](https://github.com/orgs/elastic/teams/@kibana-core) | Adds the links to the Elastic Cloud console | 0 | 0 | 0 | 0 | | | [Cloud Security Posture](https://github.com/orgs/elastic/teams/cloud-posture-security) | The cloud security posture plugin | 18 | 0 | 2 | 3 | | | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 13 | 0 | 13 | 1 | -| | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | The Controls Plugin contains embeddable components intended to create a simple query interface for end users, and a powerful editing suite that allows dashboard authors to build controls | 229 | 0 | 220 | 7 | -| | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 2697 | 0 | 23 | 0 | +| | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | The Controls Plugin contains embeddable components intended to create a simple query interface for end users, and a powerful editing suite that allows dashboard authors to build controls | 233 | 0 | 224 | 7 | +| | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 2700 | 0 | 23 | 0 | | crossClusterReplication | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 0 | 0 | 0 | 0 | | | [Fleet](https://github.com/orgs/elastic/teams/fleet) | Add custom data integrations so they can be displayed in the Fleet integrations app | 107 | 0 | 88 | 1 | | | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds the Dashboard app to Kibana | 121 | 0 | 114 | 3 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | - | 52 | 0 | 51 | 0 | -| | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Data services are useful for searching and querying data from Elasticsearch. Helpful utilities include: a re-usable react query bar, KQL autocomplete, async search, Data Views (Index Patterns) and field formatters. | 3245 | 33 | 2517 | 24 | +| | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Data services are useful for searching and querying data from Elasticsearch. Helpful utilities include: a re-usable react query bar, KQL autocomplete, async search, Data Views (Index Patterns) and field formatters. | 3251 | 33 | 2523 | 24 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | This plugin provides the ability to create data views via a modal flyout inside Kibana apps | 16 | 0 | 7 | 0 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Reusable data view field editor across Kibana | 60 | 0 | 30 | 0 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Data view management app | 2 | 0 | 2 | 0 | @@ -60,7 +60,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 10 | 0 | 8 | 2 | | | [Data Discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | This plugin contains the Discover application and the saved search embeddable. | 97 | 0 | 80 | 4 | | | [Data Discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 37 | 0 | 35 | 2 | -| | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Adds embeddables service to Kibana | 512 | 0 | 412 | 4 | +| | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Adds embeddables service to Kibana | 510 | 0 | 410 | 4 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Extends embeddable plugin with more functionality | 14 | 0 | 14 | 0 | | | [Platform Security](https://github.com/orgs/elastic/teams/kibana-security) | This plugin provides encryption and decryption utilities for saved objects containing sensitive information. | 51 | 0 | 42 | 0 | | | [Enterprise Search](https://github.com/orgs/elastic/teams/enterprise-search-frontend) | Adds dashboards for discovering and managing Enterprise Search products. | 9 | 0 | 9 | 0 | @@ -84,7 +84,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 222 | 0 | 95 | 2 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Index pattern fields and ambiguous values formatters | 288 | 5 | 249 | 3 | | | [Machine Learning UI](https://github.com/orgs/elastic/teams/ml-ui) | The file upload plugin contains components and services for uploading a file, analyzing its data, and then importing the data into an Elasticsearch index. Supported file types include CSV, TSV, newline-delimited JSON and GeoJSON. | 62 | 0 | 62 | 2 | -| | [@elastic/kibana-app-services](https://github.com/orgs/elastic/teams/team:AppServicesUx) | File upload, download, sharing, and serving over HTTP implementation in Kibana. | 260 | 0 | 14 | 2 | +| | [@elastic/kibana-app-services](https://github.com/orgs/elastic/teams/team:AppServicesUx) | File upload, download, sharing, and serving over HTTP implementation in Kibana. | 271 | 0 | 18 | 2 | | | [Fleet](https://github.com/orgs/elastic/teams/fleet) | - | 996 | 3 | 893 | 17 | | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 68 | 0 | 14 | 5 | | globalSearchBar | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 0 | 0 | 0 | 0 | @@ -105,7 +105,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | kibanaUsageCollection | [Kibana Telemetry](https://github.com/orgs/elastic/teams/kibana-telemetry) | - | 0 | 0 | 0 | 0 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | - | 615 | 3 | 418 | 9 | | | [Security Team](https://github.com/orgs/elastic/teams/security-team) | - | 3 | 0 | 3 | 1 | -| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Visualization editor allowing to quickly and easily configure compelling visualizations to use on dashboards and canvas workpads. Exposes components to embed visualizations and link into the Lens editor from within other apps in Kibana. | 669 | 0 | 576 | 46 | +| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Visualization editor allowing to quickly and easily configure compelling visualizations to use on dashboards and canvas workpads. Exposes components to embed visualizations and link into the Lens editor from within other apps in Kibana. | 674 | 0 | 581 | 47 | | | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 8 | 0 | 8 | 0 | | | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 3 | 0 | 3 | 0 | | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 117 | 0 | 42 | 10 | @@ -114,7 +114,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | - | 41 | 0 | 41 | 6 | | | [GIS](https://github.com/orgs/elastic/teams/kibana-gis) | - | 263 | 0 | 262 | 26 | | | [GIS](https://github.com/orgs/elastic/teams/kibana-gis) | - | 67 | 0 | 67 | 0 | -| | [Machine Learning UI](https://github.com/orgs/elastic/teams/ml-ui) | This plugin provides access to the machine learning features provided by Elastic. | 251 | 9 | 78 | 39 | +| | [Machine Learning UI](https://github.com/orgs/elastic/teams/ml-ui) | This plugin provides access to the machine learning features provided by Elastic. | 254 | 9 | 78 | 39 | | | [Stack Monitoring](https://github.com/orgs/elastic/teams/stack-monitoring-ui) | - | 11 | 0 | 9 | 1 | | | [Stack Monitoring](https://github.com/orgs/elastic/teams/stack-monitoring-ui) | - | 9 | 0 | 9 | 0 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | - | 34 | 0 | 34 | 2 | @@ -180,7 +180,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Registers the vega visualization. Is the elastic version of vega and vega-lite libraries. | 2 | 0 | 2 | 0 | | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Contains the vislib visualizations. These are the classical area/line/bar, pie, gauge/goal and heatmap charts. We want to replace them with elastic-charts. | 26 | 0 | 25 | 1 | | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Contains the new xy-axis chart using the elastic-charts library, which will eventually replace the vislib xy-axis charts including bar, area, and line. | 53 | 0 | 50 | 5 | -| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Contains the shared architecture among all the legacy visualizations, e.g. the visualization type registry or the visualization embeddable. | 760 | 12 | 730 | 18 | +| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Contains the shared architecture among all the legacy visualizations, e.g. the visualization type registry or the visualization embeddable. | 759 | 12 | 729 | 18 | | watcher | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 0 | 0 | 0 | 0 | ## Package Directory @@ -233,7 +233,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | Kibana Core | - | 5 | 0 | 0 | 0 | | | Kibana Core | - | 16 | 0 | 7 | 0 | | | Kibana Core | - | 6 | 0 | 6 | 0 | -| | Kibana Core | - | 114 | 0 | 41 | 0 | +| | Kibana Core | - | 119 | 0 | 43 | 0 | | | Kibana Core | - | 3 | 0 | 3 | 0 | | | Kibana Core | - | 4 | 0 | 4 | 0 | | | Kibana Core | - | 9 | 0 | 3 | 0 | @@ -390,7 +390,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | Kibana Core | - | 8 | 0 | 8 | 0 | | | [Owner missing] | - | 6 | 0 | 1 | 1 | | | [Owner missing] | - | 534 | 1 | 1 | 0 | -| | Machine Learning UI | This package includes utility functions related to creating elasticsearch aggregation queries, data manipulation and verification. | 64 | 2 | 44 | 3 | +| | Machine Learning UI | This package includes utility functions related to creating elasticsearch aggregation queries, data manipulation and verification. | 66 | 2 | 46 | 3 | | | Machine Learning UI | A type guard to check record like object structures. | 3 | 0 | 2 | 0 | | | Machine Learning UI | Creates a deterministic number based hash out of a string. | 2 | 0 | 1 | 0 | | | [Owner missing] | - | 55 | 0 | 55 | 2 | diff --git a/api_docs/presentation_util.devdocs.json b/api_docs/presentation_util.devdocs.json index 9b4fae824a02..7a250188fd99 100644 --- a/api_docs/presentation_util.devdocs.json +++ b/api_docs/presentation_util.devdocs.json @@ -2613,7 +2613,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - "; initialComponentState?: ReduxEmbeddableStateType[\"componentState\"] | undefined; syncSettings?: ", + "; initialComponentState?: ReduxEmbeddableStateType[\"componentState\"] | undefined; syncSettings?: ", "ReduxEmbeddableSyncSettings", "<", { @@ -2670,7 +2670,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - "; initialComponentState?: ReduxEmbeddableStateType[\"componentState\"] | undefined; syncSettings?: ", + "; initialComponentState?: ReduxEmbeddableStateType[\"componentState\"] | undefined; syncSettings?: ", "ReduxEmbeddableSyncSettings", "<", { diff --git a/api_docs/presentation_util.mdx b/api_docs/presentation_util.mdx index ec403b1daecf..27cb60aff3b0 100644 --- a/api_docs/presentation_util.mdx +++ b/api_docs/presentation_util.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/presentationUtil title: "presentationUtil" image: https://source.unsplash.com/400x175/?github description: API docs for the presentationUtil plugin -date: 2022-10-24 +date: 2022-10-26 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 17af4446a2e4..7167eccb9cb5 100644 --- a/api_docs/profiling.mdx +++ b/api_docs/profiling.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/profiling title: "profiling" image: https://source.unsplash.com/400x175/?github description: API docs for the profiling plugin -date: 2022-10-24 +date: 2022-10-26 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 6e2b08c70c7f..d86dec5b192e 100644 --- a/api_docs/remote_clusters.mdx +++ b/api_docs/remote_clusters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/remoteClusters title: "remoteClusters" image: https://source.unsplash.com/400x175/?github description: API docs for the remoteClusters plugin -date: 2022-10-24 +date: 2022-10-26 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 8b7d2c692ade..c1cda46c8f20 100644 --- a/api_docs/reporting.mdx +++ b/api_docs/reporting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/reporting title: "reporting" image: https://source.unsplash.com/400x175/?github description: API docs for the reporting plugin -date: 2022-10-24 +date: 2022-10-26 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 8099d161fb9f..819505d2605f 100644 --- a/api_docs/rollup.mdx +++ b/api_docs/rollup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/rollup title: "rollup" image: https://source.unsplash.com/400x175/?github description: API docs for the rollup plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'rollup'] --- import rollupObj from './rollup.devdocs.json'; diff --git a/api_docs/rule_registry.mdx b/api_docs/rule_registry.mdx index 7285f5e3f37f..4d8e58dddc2d 100644 --- a/api_docs/rule_registry.mdx +++ b/api_docs/rule_registry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ruleRegistry title: "ruleRegistry" image: https://source.unsplash.com/400x175/?github description: API docs for the ruleRegistry plugin -date: 2022-10-24 +date: 2022-10-26 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 e2b01e3ff98d..63a0649cce88 100644 --- a/api_docs/runtime_fields.mdx +++ b/api_docs/runtime_fields.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/runtimeFields title: "runtimeFields" image: https://source.unsplash.com/400x175/?github description: API docs for the runtimeFields plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'runtimeFields'] --- import runtimeFieldsObj from './runtime_fields.devdocs.json'; diff --git a/api_docs/saved_objects.mdx b/api_docs/saved_objects.mdx index ffbcd2429fee..c8690592c32a 100644 --- a/api_docs/saved_objects.mdx +++ b/api_docs/saved_objects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjects title: "savedObjects" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjects plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjects'] --- import savedObjectsObj from './saved_objects.devdocs.json'; diff --git a/api_docs/saved_objects_finder.mdx b/api_docs/saved_objects_finder.mdx index cb95769f0c88..bdc4bac5c779 100644 --- a/api_docs/saved_objects_finder.mdx +++ b/api_docs/saved_objects_finder.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsFinder title: "savedObjectsFinder" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsFinder plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsFinder'] --- import savedObjectsFinderObj from './saved_objects_finder.devdocs.json'; diff --git a/api_docs/saved_objects_management.mdx b/api_docs/saved_objects_management.mdx index 1523f5943e69..0566073902f6 100644 --- a/api_docs/saved_objects_management.mdx +++ b/api_docs/saved_objects_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsManagement title: "savedObjectsManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsManagement plugin -date: 2022-10-24 +date: 2022-10-26 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 ca5ea5e6fe0f..6e8b710fec3a 100644 --- a/api_docs/saved_objects_tagging.mdx +++ b/api_docs/saved_objects_tagging.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsTagging title: "savedObjectsTagging" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsTagging plugin -date: 2022-10-24 +date: 2022-10-26 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 e588e9d2a45f..63780c679f42 100644 --- a/api_docs/saved_objects_tagging_oss.mdx +++ b/api_docs/saved_objects_tagging_oss.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsTaggingOss title: "savedObjectsTaggingOss" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsTaggingOss plugin -date: 2022-10-24 +date: 2022-10-26 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 25191348ad1f..3d7509946a0f 100644 --- a/api_docs/saved_search.mdx +++ b/api_docs/saved_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedSearch title: "savedSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the savedSearch plugin -date: 2022-10-24 +date: 2022-10-26 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 d45a93a68f93..1c0e950d15c8 100644 --- a/api_docs/screenshot_mode.mdx +++ b/api_docs/screenshot_mode.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/screenshotMode title: "screenshotMode" image: https://source.unsplash.com/400x175/?github description: API docs for the screenshotMode plugin -date: 2022-10-24 +date: 2022-10-26 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 c939b836664f..2b24ea2f453b 100644 --- a/api_docs/screenshotting.mdx +++ b/api_docs/screenshotting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/screenshotting title: "screenshotting" image: https://source.unsplash.com/400x175/?github description: API docs for the screenshotting plugin -date: 2022-10-24 +date: 2022-10-26 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 a827163faefe..b1020a9eeed1 100644 --- a/api_docs/security.mdx +++ b/api_docs/security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/security title: "security" image: https://source.unsplash.com/400x175/?github description: API docs for the security plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'security'] --- import securityObj from './security.devdocs.json'; diff --git a/api_docs/security_solution.mdx b/api_docs/security_solution.mdx index f5a333450aad..46f73c2ded75 100644 --- a/api_docs/security_solution.mdx +++ b/api_docs/security_solution.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/securitySolution title: "securitySolution" image: https://source.unsplash.com/400x175/?github description: API docs for the securitySolution plugin -date: 2022-10-24 +date: 2022-10-26 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 ef0b19ab974b..93f1c8d4485d 100644 --- a/api_docs/session_view.mdx +++ b/api_docs/session_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/sessionView title: "sessionView" image: https://source.unsplash.com/400x175/?github description: API docs for the sessionView plugin -date: 2022-10-24 +date: 2022-10-26 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 5d1f53461f8e..aeeb504974f4 100644 --- a/api_docs/share.mdx +++ b/api_docs/share.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/share title: "share" image: https://source.unsplash.com/400x175/?github description: API docs for the share plugin -date: 2022-10-24 +date: 2022-10-26 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 14a1a8765afe..e3fed30fcb8d 100644 --- a/api_docs/snapshot_restore.mdx +++ b/api_docs/snapshot_restore.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/snapshotRestore title: "snapshotRestore" image: https://source.unsplash.com/400x175/?github description: API docs for the snapshotRestore plugin -date: 2022-10-24 +date: 2022-10-26 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 71e5ef0c3a70..09b44e89c287 100644 --- a/api_docs/spaces.mdx +++ b/api_docs/spaces.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/spaces title: "spaces" image: https://source.unsplash.com/400x175/?github description: API docs for the spaces plugin -date: 2022-10-24 +date: 2022-10-26 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 91794b105352..837a0df659c0 100644 --- a/api_docs/stack_alerts.mdx +++ b/api_docs/stack_alerts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/stackAlerts title: "stackAlerts" image: https://source.unsplash.com/400x175/?github description: API docs for the stackAlerts plugin -date: 2022-10-24 +date: 2022-10-26 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 8c77a979c555..e8b45e46ad0a 100644 --- a/api_docs/stack_connectors.mdx +++ b/api_docs/stack_connectors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/stackConnectors title: "stackConnectors" image: https://source.unsplash.com/400x175/?github description: API docs for the stackConnectors plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackConnectors'] --- import stackConnectorsObj from './stack_connectors.devdocs.json'; diff --git a/api_docs/task_manager.devdocs.json b/api_docs/task_manager.devdocs.json index 41d8c4273d8d..4797c6b98c4f 100644 --- a/api_docs/task_manager.devdocs.json +++ b/api_docs/task_manager.devdocs.json @@ -1534,7 +1534,9 @@ "TaskScheduling", ", \"schedule\" | \"runSoon\" | \"ephemeralRunNow\" | \"ensureScheduled\" | \"bulkUpdateSchedules\" | \"bulkEnable\" | \"bulkDisable\" | \"bulkSchedule\"> & Pick<", "TaskStore", - ", \"get\" | \"aggregate\" | \"fetch\" | \"remove\"> & { removeIfExists: (id: string) => Promise; } & { supportsEphemeralTasks: () => boolean; }" + ", \"get\" | \"aggregate\" | \"fetch\" | \"remove\"> & { removeIfExists: (id: string) => Promise; } & { bulkRemoveIfExist: (ids: string[]) => Promise<", + "SavedObjectsBulkDeleteResponse", + " | undefined>; } & { supportsEphemeralTasks: () => boolean; }" ], "path": "x-pack/plugins/task_manager/server/plugin.ts", "deprecated": false, diff --git a/api_docs/task_manager.mdx b/api_docs/task_manager.mdx index f7caa6c81573..2a6c84c5c21e 100644 --- a/api_docs/task_manager.mdx +++ b/api_docs/task_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/taskManager title: "taskManager" image: https://source.unsplash.com/400x175/?github description: API docs for the taskManager plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'taskManager'] --- import taskManagerObj from './task_manager.devdocs.json'; diff --git a/api_docs/telemetry.mdx b/api_docs/telemetry.mdx index 888496756b7e..2fa5a1a056d1 100644 --- a/api_docs/telemetry.mdx +++ b/api_docs/telemetry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetry title: "telemetry" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetry plugin -date: 2022-10-24 +date: 2022-10-26 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 2919da0fd203..675975ab4c29 100644 --- a/api_docs/telemetry_collection_manager.mdx +++ b/api_docs/telemetry_collection_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryCollectionManager title: "telemetryCollectionManager" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryCollectionManager plugin -date: 2022-10-24 +date: 2022-10-26 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 91d9a7f891af..e5c58683052f 100644 --- a/api_docs/telemetry_collection_xpack.mdx +++ b/api_docs/telemetry_collection_xpack.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryCollectionXpack title: "telemetryCollectionXpack" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryCollectionXpack plugin -date: 2022-10-24 +date: 2022-10-26 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 426f52ed290f..ed76f3dfe8f9 100644 --- a/api_docs/telemetry_management_section.mdx +++ b/api_docs/telemetry_management_section.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryManagementSection title: "telemetryManagementSection" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryManagementSection plugin -date: 2022-10-24 +date: 2022-10-26 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 882f15401cf9..ec482a462b64 100644 --- a/api_docs/threat_intelligence.mdx +++ b/api_docs/threat_intelligence.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/threatIntelligence title: "threatIntelligence" image: https://source.unsplash.com/400x175/?github description: API docs for the threatIntelligence plugin -date: 2022-10-24 +date: 2022-10-26 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 32e922a0c81c..9d3d357891bc 100644 --- a/api_docs/timelines.mdx +++ b/api_docs/timelines.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/timelines title: "timelines" image: https://source.unsplash.com/400x175/?github description: API docs for the timelines plugin -date: 2022-10-24 +date: 2022-10-26 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 0bf13c135fd9..330382a29084 100644 --- a/api_docs/transform.mdx +++ b/api_docs/transform.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/transform title: "transform" image: https://source.unsplash.com/400x175/?github description: API docs for the transform plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'transform'] --- import transformObj from './transform.devdocs.json'; diff --git a/api_docs/triggers_actions_ui.mdx b/api_docs/triggers_actions_ui.mdx index 08ead4109c08..28d4033c25e9 100644 --- a/api_docs/triggers_actions_ui.mdx +++ b/api_docs/triggers_actions_ui.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/triggersActionsUi title: "triggersActionsUi" image: https://source.unsplash.com/400x175/?github description: API docs for the triggersActionsUi plugin -date: 2022-10-24 +date: 2022-10-26 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 8f82f4e464e1..a8421e4ffd1d 100644 --- a/api_docs/ui_actions.mdx +++ b/api_docs/ui_actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uiActions title: "uiActions" image: https://source.unsplash.com/400x175/?github description: API docs for the uiActions plugin -date: 2022-10-24 +date: 2022-10-26 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 aed9c6a8e4a0..9b5632b1a2af 100644 --- a/api_docs/ui_actions_enhanced.mdx +++ b/api_docs/ui_actions_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uiActionsEnhanced title: "uiActionsEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the uiActionsEnhanced plugin -date: 2022-10-24 +date: 2022-10-26 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 e5a947d81959..f835b15f1afd 100644 --- a/api_docs/unified_field_list.mdx +++ b/api_docs/unified_field_list.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedFieldList title: "unifiedFieldList" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedFieldList plugin -date: 2022-10-24 +date: 2022-10-26 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 101fb511048e..ea165799864a 100644 --- a/api_docs/unified_histogram.mdx +++ b/api_docs/unified_histogram.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedHistogram title: "unifiedHistogram" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedHistogram plugin -date: 2022-10-24 +date: 2022-10-26 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 8f42fc8d8828..ea8e01217ba9 100644 --- a/api_docs/unified_search.devdocs.json +++ b/api_docs/unified_search.devdocs.json @@ -452,7 +452,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - "> | undefined" + ", any> | undefined" ], "path": "src/plugins/unified_search/public/actions/apply_filter_action.ts", "deprecated": false, diff --git a/api_docs/unified_search.mdx b/api_docs/unified_search.mdx index c6e706fff43f..2d7937c8fda2 100644 --- a/api_docs/unified_search.mdx +++ b/api_docs/unified_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedSearch title: "unifiedSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedSearch plugin -date: 2022-10-24 +date: 2022-10-26 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 37d3fcb4df7b..607dccc87e94 100644 --- a/api_docs/unified_search_autocomplete.mdx +++ b/api_docs/unified_search_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedSearch-autocomplete title: "unifiedSearch.autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedSearch.autocomplete plugin -date: 2022-10-24 +date: 2022-10-26 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 a7a3bdcce7cb..cb4ce1a4e39d 100644 --- a/api_docs/url_forwarding.mdx +++ b/api_docs/url_forwarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/urlForwarding title: "urlForwarding" image: https://source.unsplash.com/400x175/?github description: API docs for the urlForwarding plugin -date: 2022-10-24 +date: 2022-10-26 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 3bd37737fe77..82f1927c2940 100644 --- a/api_docs/usage_collection.mdx +++ b/api_docs/usage_collection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/usageCollection title: "usageCollection" image: https://source.unsplash.com/400x175/?github description: API docs for the usageCollection plugin -date: 2022-10-24 +date: 2022-10-26 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 0717eea02ad3..e56841d9d871 100644 --- a/api_docs/ux.mdx +++ b/api_docs/ux.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ux title: "ux" image: https://source.unsplash.com/400x175/?github description: API docs for the ux plugin -date: 2022-10-24 +date: 2022-10-26 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 55278b5401d8..e67940c983a6 100644 --- a/api_docs/vis_default_editor.mdx +++ b/api_docs/vis_default_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visDefaultEditor title: "visDefaultEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the visDefaultEditor plugin -date: 2022-10-24 +date: 2022-10-26 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 c2c6d6d601fc..7d5f99dc5806 100644 --- a/api_docs/vis_type_gauge.mdx +++ b/api_docs/vis_type_gauge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeGauge title: "visTypeGauge" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeGauge plugin -date: 2022-10-24 +date: 2022-10-26 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 afba3a7c2c0a..76f3b43df49c 100644 --- a/api_docs/vis_type_heatmap.mdx +++ b/api_docs/vis_type_heatmap.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeHeatmap title: "visTypeHeatmap" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeHeatmap plugin -date: 2022-10-24 +date: 2022-10-26 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 ad0cff7deeac..5ee263d97682 100644 --- a/api_docs/vis_type_pie.mdx +++ b/api_docs/vis_type_pie.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypePie title: "visTypePie" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypePie plugin -date: 2022-10-24 +date: 2022-10-26 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 0e56fe19cc73..e52194670b85 100644 --- a/api_docs/vis_type_table.mdx +++ b/api_docs/vis_type_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTable title: "visTypeTable" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTable plugin -date: 2022-10-24 +date: 2022-10-26 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 4677afb2acd2..a8668626c596 100644 --- a/api_docs/vis_type_timelion.mdx +++ b/api_docs/vis_type_timelion.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTimelion title: "visTypeTimelion" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTimelion plugin -date: 2022-10-24 +date: 2022-10-26 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 596c74e5f39d..5bea563728e5 100644 --- a/api_docs/vis_type_timeseries.mdx +++ b/api_docs/vis_type_timeseries.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTimeseries title: "visTypeTimeseries" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTimeseries plugin -date: 2022-10-24 +date: 2022-10-26 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 dcc99abbcba4..062a440515fc 100644 --- a/api_docs/vis_type_vega.mdx +++ b/api_docs/vis_type_vega.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeVega title: "visTypeVega" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeVega plugin -date: 2022-10-24 +date: 2022-10-26 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 a4a18274d6b0..ff9afaad7632 100644 --- a/api_docs/vis_type_vislib.mdx +++ b/api_docs/vis_type_vislib.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeVislib title: "visTypeVislib" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeVislib plugin -date: 2022-10-24 +date: 2022-10-26 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 c15d6fe4437d..c17841e01d9b 100644 --- a/api_docs/vis_type_xy.mdx +++ b/api_docs/vis_type_xy.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeXy title: "visTypeXy" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeXy plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeXy'] --- import visTypeXyObj from './vis_type_xy.devdocs.json'; diff --git a/api_docs/visualizations.devdocs.json b/api_docs/visualizations.devdocs.json index b4885bafc9d7..b4c5d22ea35e 100644 --- a/api_docs/visualizations.devdocs.json +++ b/api_docs/visualizations.devdocs.json @@ -1320,7 +1320,7 @@ }, ", ", "VisualizeOutput", - "> implements ", + ", any> implements ", { "pluginId": "embeddable", "scope": "public", @@ -1815,13 +1815,13 @@ }, { "parentPluginId": "visualizations", - "id": "def-public.VisualizeEmbeddable.renderError", + "id": "def-public.VisualizeEmbeddable.catchError", "type": "Function", "tags": [], - "label": "renderError", + "label": "catchError", "description": [], "signature": [ - "(domNode: HTMLElement, error: string | ", + "(error: string | ", { "pluginId": "expressions", "scope": "common", @@ -1829,7 +1829,7 @@ "section": "def-common.ErrorLike", "text": "ErrorLike" }, - ") => () => boolean" + ") => JSX.Element" ], "path": "src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx", "deprecated": false, @@ -1837,22 +1837,7 @@ "children": [ { "parentPluginId": "visualizations", - "id": "def-public.VisualizeEmbeddable.renderError.$1", - "type": "Object", - "tags": [], - "label": "domNode", - "description": [], - "signature": [ - "HTMLElement" - ], - "path": "src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "visualizations", - "id": "def-public.VisualizeEmbeddable.renderError.$2", + "id": "def-public.VisualizeEmbeddable.catchError.$1", "type": "CompoundType", "tags": [], "label": "error", @@ -6198,7 +6183,7 @@ "section": "def-public.ExpressionRenderError", "text": "ExpressionRenderError" }, - ") => void; renderError: (domNode: HTMLElement, error: string | ", + ") => void; catchError: (error: string | ", { "pluginId": "expressions", "scope": "common", @@ -6206,7 +6191,7 @@ "section": "def-common.ErrorLike", "text": "ErrorLike" }, - ") => () => boolean; reload: () => Promise; supportedTriggers: () => string[]; inputIsRefType: (input: ", + ") => JSX.Element; reload: () => Promise; supportedTriggers: () => string[]; inputIsRefType: (input: ", { "pluginId": "visualizations", "scope": "public", @@ -6320,7 +6305,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - "> | ", + ", any> | ", { "pluginId": "embeddable", "scope": "public", diff --git a/api_docs/visualizations.mdx b/api_docs/visualizations.mdx index 1a3a83aa5a71..5a3244b95987 100644 --- a/api_docs/visualizations.mdx +++ b/api_docs/visualizations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visualizations title: "visualizations" image: https://source.unsplash.com/400x175/?github description: API docs for the visualizations plugin -date: 2022-10-24 +date: 2022-10-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visualizations'] --- import visualizationsObj from './visualizations.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 760 | 12 | 730 | 18 | +| 759 | 12 | 729 | 18 | ## Client diff --git a/docs/maps/search.asciidoc b/docs/maps/search.asciidoc index aa747611bdba..8575d9fda01e 100644 --- a/docs/maps/search.asciidoc +++ b/docs/maps/search.asciidoc @@ -107,7 +107,7 @@ You can create spatial filters in two ways: Spatial filters have the following properties: * *Geometry label* enables you to provide a meaningful name for your spatial filter. -* *Spatial relation* determines the {ref}/query-dsl-geo-shape-query.html#_spatial_relations[spatial relation operator] to use at search time. +* *Spatial relation* determines the {ref}/query-dsl-geo-shape-query.html#geo-shape-spatial-relations[spatial relation operator] to use at search time. * *Action* specifies whether to apply the filter to the current view or to a drilldown action. Only available when the map is a panel in a {kibana-ref}/dashboard.html[dashboard] with {kibana-ref}/drilldowns.html[drilldowns]. [role="screenshot"] diff --git a/docs/spaces/index.asciidoc b/docs/spaces/index.asciidoc index 00e50a41b6ce..9b20d1c23719 100644 --- a/docs/spaces/index.asciidoc +++ b/docs/spaces/index.asciidoc @@ -19,8 +19,6 @@ image::images/change-space.png["Change current space menu"] The `kibana_admin` role or equivalent is required to manage **Spaces**. -TIP: Looking to support multiple tenants? Refer to <> for more information. - [float] [[spaces-managing]] === View, create, and delete spaces diff --git a/docs/user/security/authorization/index.asciidoc b/docs/user/security/authorization/index.asciidoc index b478e8b7c38d..7127ebf84ae9 100644 --- a/docs/user/security/authorization/index.asciidoc +++ b/docs/user/security/authorization/index.asciidoc @@ -6,22 +6,6 @@ The Elastic Stack comes with the `kibana_admin` {ref}/built-in-roles.html[built- When you assign a user multiple roles, the user receives a union of the roles’ privileges. Therefore, assigning the `kibana_admin` role in addition to a custom role that grants {kib} privileges is ineffective because `kibana_admin` has access to all the features in all spaces. -[[xpack-security-multiple-tenants]] -==== Supporting multiple tenants - -There are two approaches to supporting multi-tenancy in {kib}: - -1. *Recommended:* Create a space and a limited role for each tenant, and configure each user with the appropriate role. See -<> for more details. -2. deprecated:[7.13.0,"In 8.0 and later, the `kibana.index` setting will no longer be supported."] Set up separate {kib} instances to work -with a single {es} cluster by changing the `kibana.index` setting in your `kibana.yml` file. -+ -NOTE: When using multiple {kib} instances this way, you cannot use the `kibana_admin` role to grant access. You must create custom roles -that authorize the user for each specific instance. - -Whichever approach you use, be careful when granting cluster privileges and index privileges. Both of these approaches share the same {es} -cluster, and {kib} spaces do not prevent you from granting users of two different tenants access to the same index. - [role="xpack"] [[kibana-role-management]] === {kib} role management diff --git a/nav-kibana-dev.docnav.json b/nav-kibana-dev.docnav.json index 14e27632c33f..8b49d43be942 100644 --- a/nav-kibana-dev.docnav.json +++ b/nav-kibana-dev.docnav.json @@ -106,6 +106,10 @@ }, { "id": "kibDevDocsEmbeddables" + }, + { + "id": "kibCloudExperimentsPlugin", + "label": "A/B testing on Elastic Cloud" } ] }, diff --git a/package.json b/package.json index 8a71f04064b8..7144902af8e3 100644 --- a/package.json +++ b/package.json @@ -99,7 +99,7 @@ }, "dependencies": { "@appland/sql-parser": "^1.5.1", - "@babel/runtime": "^7.19.0", + "@babel/runtime": "^7.19.4", "@dnd-kit/core": "^3.1.1", "@dnd-kit/sortable": "^4.0.0", "@dnd-kit/utilities": "^2.0.0", @@ -509,8 +509,8 @@ "he": "^1.2.0", "history": "^4.9.0", "hjson": "3.2.1", - "http-proxy-agent": "^2.1.0", - "https-proxy-agent": "^5.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", "i18n-iso-countries": "^4.3.1", "icalendar": "0.7.1", "immer": "^9.0.15", @@ -593,7 +593,7 @@ "react-fast-compare": "^2.0.4", "react-focus-on": "^3.6.0", "react-grid-layout": "^1.3.4", - "react-hook-form": "^7.37.0", + "react-hook-form": "^7.38.0", "react-intl": "^2.8.0", "react-is": "^17.0.2", "react-markdown": "^6.0.3", @@ -666,32 +666,32 @@ "vinyl": "^2.2.0", "whatwg-fetch": "^3.0.0", "xml2js": "^0.4.22", - "xterm": "^4.18.0", + "xterm": "^5.0.0", "yauzl": "^2.10.0", "yazl": "^2.5.1" }, "devDependencies": { "@apidevtools/swagger-parser": "^10.0.3", "@babel/cli": "^7.19.3", - "@babel/core": "^7.19.3", + "@babel/core": "^7.19.6", "@babel/eslint-parser": "^7.19.1", "@babel/eslint-plugin": "^7.19.1", - "@babel/generator": "^7.19.3", + "@babel/generator": "^7.19.6", "@babel/helper-plugin-utils": "^7.19.0", - "@babel/parser": "^7.19.3", + "@babel/parser": "^7.19.6", "@babel/plugin-proposal-class-properties": "^7.18.6", "@babel/plugin-proposal-export-namespace-from": "^7.18.9", "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", - "@babel/plugin-proposal-object-rest-spread": "^7.18.9", + "@babel/plugin-proposal-object-rest-spread": "^7.19.4", "@babel/plugin-proposal-optional-chaining": "^7.18.9", "@babel/plugin-proposal-private-methods": "^7.18.6", - "@babel/plugin-transform-runtime": "^7.19.1", - "@babel/preset-env": "^7.19.3", + "@babel/plugin-transform-runtime": "^7.19.6", + "@babel/preset-env": "^7.19.4", "@babel/preset-react": "^7.18.6", "@babel/preset-typescript": "^7.18.6", "@babel/register": "^7.18.9", - "@babel/traverse": "^7.19.3", - "@babel/types": "^7.19.3", + "@babel/traverse": "^7.19.6", + "@babel/types": "^7.19.4", "@bazel/ibazel": "^0.16.2", "@bazel/typescript": "4.6.2", "@cypress/code-coverage": "^3.10.0", @@ -705,9 +705,9 @@ "@emotion/jest": "^11.10.0", "@istanbuljs/nyc-config-typescript": "^1.0.2", "@istanbuljs/schema": "^0.1.2", - "@jest/console": "^26.6.2", - "@jest/reporters": "^26.6.2", - "@jest/types": "^26", + "@jest/console": "^27.5.1", + "@jest/reporters": "^27.5.1", + "@jest/types": "^27", "@kbn/ambient-storybook-types": "link:bazel-bin/packages/kbn-ambient-storybook-types", "@kbn/ambient-ui-types": "link:bazel-bin/packages/kbn-ambient-ui-types", "@kbn/apm-synthtrace": "link:bazel-bin/packages/kbn-apm-synthtrace", @@ -790,10 +790,10 @@ "@storybook/react-docgen-typescript-plugin": "^1.0.1", "@storybook/testing-react": "^1.3.0", "@storybook/theming": "^6.5.12", - "@testing-library/dom": "^8.12.0", - "@testing-library/jest-dom": "^5.16.3", + "@testing-library/dom": "^8.17.1", + "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^12.1.5", - "@testing-library/react-hooks": "^7.0.2", + "@testing-library/react-hooks": "^8.0.1", "@testing-library/user-event": "^13.5.0", "@types/apidoc": "^0.22.3", "@types/archiver": "^5.3.1", @@ -843,10 +843,9 @@ "@types/history": "^4.7.9", "@types/hjson": "^2.4.2", "@types/http-proxy": "^1.17.4", - "@types/http-proxy-agent": "^2.0.2", "@types/inquirer": "^7.3.1", "@types/intl-relativeformat": "^2.1.0", - "@types/jest": "^26.0.22", + "@types/jest": "^27.4.1", "@types/jest-axe": "^3.5.3", "@types/joi": "^17.2.3", "@types/jquery": "^3.3.31", @@ -1297,7 +1296,7 @@ "argsplit": "^1.0.5", "autoprefixer": "^10.4.7", "axe-core": "^4.0.2", - "babel-jest": "^26.6.3", + "babel-jest": "^27.5.1", "babel-loader": "^8.2.5", "babel-plugin-add-module-exports": "^1.0.4", "babel-plugin-istanbul": "^6.1.1", @@ -1332,7 +1331,7 @@ "dpdm": "3.5.0", "ejs": "^3.1.8", "enzyme": "^3.11.0", - "enzyme-to-json": "^3.6.1", + "enzyme-to-json": "^3.6.2", "eslint": "^7.32.0", "eslint-config-prettier": "^8.5.0", "eslint-module-utils": "^2.6.2", @@ -1370,21 +1369,21 @@ "html-loader": "^1.3.2", "http-proxy": "^1.18.1", "is-path-inside": "^3.0.2", - "jest": "^26.6.3", + "jest": "^27.5.1", "jest-axe": "^5.0.0", - "jest-canvas-mock": "^2.3.1", - "jest-circus": "^26.6.3", - "jest-cli": "^26.6.3", - "jest-config": "^26", - "jest-diff": "^26.6.2", - "jest-environment-jsdom": "^26.6.2", - "jest-mock": "^26.6.2", + "jest-canvas-mock": "^2.4.0", + "jest-cli": "^27.5.1", + "jest-config": "^27.5.1", + "jest-diff": "^27.5.1", + "jest-environment-jsdom": "^27.5.1", + "jest-matcher-utils": "^27.5.1", + "jest-mock": "^27.5.1", "jest-raw-loader": "^1.0.1", - "jest-runtime": "^26", + "jest-runtime": "^27.5.1", "jest-silent-reporter": "^0.5.0", - "jest-snapshot": "^26.6.2", - "jest-specific-snapshot": "^4.0.0", - "jest-styled-components": "^7.0.3", + "jest-snapshot": "^27.5.1", + "jest-specific-snapshot": "^5.0.0", + "jest-styled-components": "7.0.3", "jsdom": "^16.4.0", "json-schema-typed": "^8.0.1", "json5": "^1.0.1", diff --git a/packages/analytics/client/src/analytics_client/analytics_client.test.ts b/packages/analytics/client/src/analytics_client/analytics_client.test.ts index a53489e31208..601f94aa1e24 100644 --- a/packages/analytics/client/src/analytics_client/analytics_client.test.ts +++ b/packages/analytics/client/src/analytics_client/analytics_client.test.ts @@ -21,7 +21,7 @@ describe('AnalyticsClient', () => { let logger: MockedLogger; beforeEach(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); logger = loggerMock.create(); analyticsClient = new AnalyticsClient({ logger, diff --git a/packages/analytics/shippers/elastic_v3/browser/src/browser_shipper.test.ts b/packages/analytics/shippers/elastic_v3/browser/src/browser_shipper.test.ts index fae312137219..47728a99a511 100644 --- a/packages/analytics/shippers/elastic_v3/browser/src/browser_shipper.test.ts +++ b/packages/analytics/shippers/elastic_v3/browser/src/browser_shipper.test.ts @@ -33,7 +33,7 @@ describe('ElasticV3BrowserShipper', () => { let fetchMock: jest.Mock; beforeEach(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); fetchMock = jest.fn().mockResolvedValue({ status: 200, diff --git a/packages/analytics/shippers/elastic_v3/server/src/server_shipper.test.ts b/packages/analytics/shippers/elastic_v3/server/src/server_shipper.test.ts index 03356f7428da..091be6cb96e8 100644 --- a/packages/analytics/shippers/elastic_v3/server/src/server_shipper.test.ts +++ b/packages/analytics/shippers/elastic_v3/server/src/server_shipper.test.ts @@ -40,7 +40,7 @@ describe('ElasticV3ServerShipper', () => { const setLastBatchSent = (ms: number) => (shipper['lastBatchSent'] = ms); beforeEach(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); shipper = new ElasticV3ServerShipper( { version: '1.2.3', channelName: 'test-channel', debug: true }, diff --git a/packages/content-management/table_list/src/table_list_view.test.tsx b/packages/content-management/table_list/src/table_list_view.test.tsx index 8e5aff4ae0db..01b1a3b215ea 100644 --- a/packages/content-management/table_list/src/table_list_view.test.tsx +++ b/packages/content-management/table_list/src/table_list_view.test.tsx @@ -49,7 +49,7 @@ const requiredProps: TableListViewProps = { describe('TableListView', () => { beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.ts b/packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.ts index d3514cb80edf..177a01393a3e 100644 --- a/packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.ts +++ b/packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.ts @@ -12,6 +12,18 @@ import { injectedMetadataServiceMock } from '@kbn/core-injected-metadata-browser import { analyticsClientMock } from './analytics_service.test.mocks'; import { AnalyticsService } from './analytics_service'; +function findRegisteredContextProviderByName(contextProviderName: string) { + return analyticsClientMock.registerContextProvider.mock.calls.find( + ([{ name }]) => name === contextProviderName + )!; +} + +function findRegisteredEventTypeByName(eventTypeName: string) { + return analyticsClientMock.registerEventType.mock.calls.find( + ([{ eventType }]) => eventType === eventTypeName + )!; +} + describe('AnalyticsService', () => { let analyticsService: AnalyticsService; beforeEach(() => { @@ -19,34 +31,39 @@ describe('AnalyticsService', () => { analyticsService = new AnalyticsService(coreContextMock.create()); }); test('should register some context providers on creation', async () => { - expect(analyticsClientMock.registerContextProvider).toHaveBeenCalledTimes(3); - expect( - await firstValueFrom(analyticsClientMock.registerContextProvider.mock.calls[0][0].context$) - ).toMatchInlineSnapshot(` - Object { - "branch": "branch", - "buildNum": 100, - "buildSha": "buildSha", - "isDev": true, - "isDistributable": false, - "version": "version", - } - `); + expect(analyticsClientMock.registerContextProvider).toHaveBeenCalledTimes(4); + expect(await firstValueFrom(findRegisteredContextProviderByName('build info')[0].context$)) + .toMatchInlineSnapshot(` + Object { + "branch": "branch", + "buildNum": 100, + "buildSha": "buildSha", + "isDev": true, + "isDistributable": false, + "version": "version", + } + `); expect( - await firstValueFrom(analyticsClientMock.registerContextProvider.mock.calls[1][0].context$) + await firstValueFrom(findRegisteredContextProviderByName('session-id')[0].context$) ).toEqual({ session_id: expect.any(String) }); expect( - await firstValueFrom(analyticsClientMock.registerContextProvider.mock.calls[2][0].context$) + await firstValueFrom(findRegisteredContextProviderByName('browser info')[0].context$) ).toEqual({ preferred_language: 'en-US', preferred_languages: ['en-US', 'en'], user_agent: expect.any(String), }); + expect( + await firstValueFrom(findRegisteredContextProviderByName('viewport_size')[0].context$) + ).toEqual({ + viewport_width: 1024, + viewport_height: 768, + }); }); test('should register the `performance_metric` and `click` event types on creation', () => { - expect(analyticsClientMock.registerEventType).toHaveBeenCalledTimes(2); - expect(analyticsClientMock.registerEventType.mock.calls[0]).toMatchInlineSnapshot(` + expect(analyticsClientMock.registerEventType).toHaveBeenCalledTimes(3); + expect(findRegisteredEventTypeByName('performance_metric')).toMatchInlineSnapshot(` Array [ Object { "eventType": "performance_metric", @@ -144,7 +161,7 @@ describe('AnalyticsService', () => { }, ] `); - expect(analyticsClientMock.registerEventType.mock.calls[1]).toMatchInlineSnapshot(` + expect(findRegisteredEventTypeByName('click')).toMatchInlineSnapshot(` Array [ Object { "eventType": "click", @@ -162,6 +179,27 @@ describe('AnalyticsService', () => { }, ] `); + expect(findRegisteredEventTypeByName('viewport_resize')).toMatchInlineSnapshot(` + Array [ + Object { + "eventType": "viewport_resize", + "schema": Object { + "viewport_height": Object { + "_meta": Object { + "description": "The value seen as the CSS viewport @media (height)", + }, + "type": "long", + }, + "viewport_width": Object { + "_meta": Object { + "description": "The value seen as the CSS viewport @media (width)", + }, + "type": "long", + }, + }, + }, + ] + `); }); test('setup should expose all the register APIs, reportEvent and opt-in', () => { @@ -181,7 +219,7 @@ describe('AnalyticsService', () => { const injectedMetadata = injectedMetadataServiceMock.createSetupContract(); analyticsService.setup({ injectedMetadata }); expect( - await firstValueFrom(analyticsClientMock.registerContextProvider.mock.calls[3][0].context$) + await firstValueFrom(findRegisteredContextProviderByName('elasticsearch info')[0].context$) ).toMatchInlineSnapshot(`undefined`); }); @@ -194,14 +232,14 @@ describe('AnalyticsService', () => { }); analyticsService.setup({ injectedMetadata }); expect( - await firstValueFrom(analyticsClientMock.registerContextProvider.mock.calls[3][0].context$) + await firstValueFrom(findRegisteredContextProviderByName('elasticsearch info')[0].context$) ).toMatchInlineSnapshot(` - Object { - "cluster_name": "cluster_name", - "cluster_uuid": "cluster_uuid", - "cluster_version": "version", - } - `); + Object { + "cluster_name": "cluster_name", + "cluster_uuid": "cluster_uuid", + "cluster_version": "version", + } + `); }); test('setup should expose only the APIs report and opt-in', () => { diff --git a/packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts b/packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts index 938b0b043bc2..0dcd49bd69fc 100644 --- a/packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts +++ b/packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { of } from 'rxjs'; +import { of, Subscription } from 'rxjs'; import type { AnalyticsClient } from '@kbn/analytics-client'; import { createAnalytics } from '@kbn/analytics-client'; import { registerPerformanceMetricEventType } from '@kbn/ebt-tools'; @@ -16,6 +16,7 @@ import type { AnalyticsServiceSetup, AnalyticsServiceStart } from '@kbn/core-ana import { trackClicks } from './track_clicks'; import { getSessionId } from './get_session_id'; import { createLogger } from './logger'; +import { trackViewportSize } from './track_viewport_size'; /** @internal */ export interface AnalyticsServiceSetupDeps { @@ -24,6 +25,7 @@ export interface AnalyticsServiceSetupDeps { export class AnalyticsService { private readonly analyticsClient: AnalyticsClient; + private readonly subscriptionsHandler = new Subscription(); constructor(core: CoreContext) { this.analyticsClient = createAnalytics({ @@ -41,7 +43,8 @@ export class AnalyticsService { // and can benefit other consumers of the client. this.registerSessionIdContext(); this.registerBrowserInfoAnalyticsContext(); - trackClicks(this.analyticsClient, core.env.mode.dev); + this.subscriptionsHandler.add(trackClicks(this.analyticsClient, core.env.mode.dev)); + this.subscriptionsHandler.add(trackViewportSize(this.analyticsClient)); } public setup({ injectedMetadata }: AnalyticsServiceSetupDeps): AnalyticsServiceSetup { @@ -67,6 +70,7 @@ export class AnalyticsService { } public stop() { + this.subscriptionsHandler.unsubscribe(); this.analyticsClient.shutdown(); } diff --git a/packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts b/packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts new file mode 100644 index 000000000000..f99d6681e5ad --- /dev/null +++ b/packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { firstValueFrom, take, type Subscription, toArray } from 'rxjs'; +import { analyticsClientMock } from './analytics_service.test.mocks'; +import { trackViewportSize } from './track_viewport_size'; + +describe('trackViewportSize', () => { + const addEventListenerSpy = jest.spyOn(window, 'addEventListener'); + let subscription: Subscription | undefined; + + afterEach(() => { + subscription?.unsubscribe(); + jest.resetAllMocks(); + }); + + test('registers the analytics event type, the context provider, and a listener to the "resize" events', () => { + subscription = trackViewportSize(analyticsClientMock); + + expect(analyticsClientMock.registerContextProvider).toHaveBeenCalledTimes(1); + expect(analyticsClientMock.registerContextProvider).toHaveBeenCalledWith( + expect.objectContaining({ + name: 'viewport_size', + }) + ); + + expect(analyticsClientMock.registerEventType).toHaveBeenCalledTimes(1); + expect(analyticsClientMock.registerEventType).toHaveBeenCalledWith( + expect.objectContaining({ + eventType: 'viewport_resize', + }) + ); + expect(addEventListenerSpy).toHaveBeenCalledTimes(1); + expect(addEventListenerSpy).toHaveBeenCalledWith('resize', expect.any(Function), undefined); + }); + + test('emits a context update before the resize occurs', async () => { + subscription = trackViewportSize(analyticsClientMock); + + const { context$ } = analyticsClientMock.registerContextProvider.mock.calls[0][0]; + await expect(firstValueFrom(context$.pipe(take(1)))).resolves.toStrictEqual({ + viewport_width: 1024, + viewport_height: 768, + }); + }); + + test('reports an analytics event when a resize event occurs', async () => { + subscription = trackViewportSize(analyticsClientMock); + + const { context$ } = analyticsClientMock.registerContextProvider.mock.calls[0][0]; + + // window.resizeTo(100, 100) wouldn't trigger the event in Jest, so we need to call the listener straight away. + // eslint-disable-next-line dot-notation + window['innerWidth'] = 100; + // eslint-disable-next-line dot-notation + window['innerHeight'] = 100; + + expect(addEventListenerSpy).toHaveBeenCalledTimes(1); + (addEventListenerSpy.mock.calls[0][1] as Function)(); + + await expect(firstValueFrom(context$.pipe(take(2), toArray()))).resolves.toStrictEqual([ + { viewport_width: 1024, viewport_height: 768 }, + { viewport_width: 100, viewport_height: 100 }, + ]); + + expect(analyticsClientMock.reportEvent).toHaveBeenCalledTimes(1); + expect(analyticsClientMock.reportEvent).toHaveBeenCalledWith('viewport_resize', { + viewport_width: 100, + viewport_height: 100, + }); + }); +}); diff --git a/packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.ts b/packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.ts new file mode 100644 index 000000000000..17fab459f2e8 --- /dev/null +++ b/packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.ts @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { debounceTime, fromEvent, map, merge, of, shareReplay } from 'rxjs'; +import type { AnalyticsClient, RootSchema } from '@kbn/analytics-client'; + +export interface ViewportSize { + viewport_width: number; + viewport_height: number; +} + +const schema: RootSchema = { + viewport_width: { + type: 'long', + _meta: { description: 'The value seen as the CSS viewport @media (width)' }, + }, + viewport_height: { + type: 'long', + _meta: { description: 'The value seen as the CSS viewport @media (height)' }, + }, +}; + +/** + * Get the @media (width) and @media (height) in the format of {@link ViewportSize} + */ +function getViewportSize(): ViewportSize { + // Explanation of the math below: https://stackoverflow.com/questions/1248081/how-to-get-the-browser-viewport-dimensions + return { + viewport_width: Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0), + viewport_height: Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0), + }; +} + +/** + * Registers the event type "viewport_size" in the analytics client, and the context provider with the same name. + * Then it listens to all the "resize" events in the UI and reports their size as {@link ViewportSize} + * @param analytics + */ +export function trackViewportSize(analytics: AnalyticsClient) { + // window or document? + // According to MDN, it only emits on `window`: https://developer.mozilla.org/en-US/docs/Web/API/Window/resize_event + const resize$ = fromEvent(window, 'resize').pipe( + debounceTime(200), + map(() => getViewportSize()), + shareReplay(1) + ); + + analytics.registerEventType({ + eventType: 'viewport_resize', + schema, + }); + + analytics.registerContextProvider({ + name: 'viewport_size', + schema, + context$: merge( + of(getViewportSize()), // Emits an initial value so initial events' context is also populated + resize$ + ), + }); + + return resize$.subscribe((event) => analytics.reportEvent('viewport_resize', event)); +} diff --git a/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx b/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx index 3c4b42cddfae..f039fb7c2474 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx +++ b/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx @@ -23,6 +23,7 @@ import type { ChromeBadge, ChromeBreadcrumb, ChromeBreadcrumbsAppendExtension, + ChromeGlobalHelpExtensionMenuLink, ChromeHelpExtension, ChromeUserBanner, } from '@kbn/core-chrome-browser'; @@ -103,6 +104,9 @@ export class ChromeService { }: StartDeps): Promise { this.initVisibility(application); + const globalHelpExtensionMenuLinks$ = new BehaviorSubject( + [] + ); const helpExtension$ = new BehaviorSubject(undefined); const breadcrumbs$ = new BehaviorSubject([]); const breadcrumbsAppendExtension$ = new BehaviorSubject< @@ -213,6 +217,7 @@ export class ChromeService { customNavLink$={customNavLink$.pipe(takeUntil(this.stop$))} kibanaDocLink={docLinks.links.kibana.guide} forceAppSwitcherNavigation$={navLinks.getForceAppSwitcherNavigation$()} + globalHelpExtensionMenuLinks$={globalHelpExtensionMenuLinks$} helpExtension$={helpExtension$.pipe(takeUntil(this.stop$))} helpSupportUrl$={helpSupportUrl$.pipe(takeUntil(this.stop$))} homeHref={http.basePath.prepend('/app/home')} @@ -253,6 +258,17 @@ export class ChromeService { breadcrumbsAppendExtension$.next(breadcrumbsAppendExtension); }, + getGlobalHelpExtensionMenuLinks$: () => globalHelpExtensionMenuLinks$.asObservable(), + + registerGlobalHelpExtensionMenuLink: ( + globalHelpExtensionMenuLink: ChromeGlobalHelpExtensionMenuLink + ) => { + globalHelpExtensionMenuLinks$.next([ + ...globalHelpExtensionMenuLinks$.value, + globalHelpExtensionMenuLink, + ]); + }, + getHelpExtension$: () => helpExtension$.pipe(takeUntil(this.stop$)), setHelpExtension: (helpExtension?: ChromeHelpExtension) => { diff --git a/packages/core/chrome/core-chrome-browser-internal/src/ui/header/header.test.tsx b/packages/core/chrome/core-chrome-browser-internal/src/ui/header/header.test.tsx index 58ac26174f20..02fcce82d2c8 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/ui/header/header.test.tsx +++ b/packages/core/chrome/core-chrome-browser-internal/src/ui/header/header.test.tsx @@ -33,6 +33,7 @@ function mockProps() { customNavLink$: new BehaviorSubject(undefined), recentlyAccessed$: new BehaviorSubject([]), forceAppSwitcherNavigation$: new BehaviorSubject(false), + globalHelpExtensionMenuLinks$: new BehaviorSubject([]), helpExtension$: new BehaviorSubject(undefined), helpSupportUrl$: new BehaviorSubject(''), navControlsLeft$: new BehaviorSubject([]), diff --git a/packages/core/chrome/core-chrome-browser-internal/src/ui/header/header.tsx b/packages/core/chrome/core-chrome-browser-internal/src/ui/header/header.tsx index 5ff0989f1648..cfc79f2779a2 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/ui/header/header.tsx +++ b/packages/core/chrome/core-chrome-browser-internal/src/ui/header/header.tsx @@ -32,6 +32,7 @@ import type { ChromeRecentlyAccessedHistoryItem, ChromeBreadcrumbsAppendExtension, ChromeHelpExtension, + ChromeGlobalHelpExtensionMenuLink, ChromeUserBanner, } from '@kbn/core-chrome-browser'; import { LoadingIndicator } from '../loading_indicator'; @@ -60,6 +61,7 @@ export interface HeaderProps { navLinks$: Observable; recentlyAccessed$: Observable; forceAppSwitcherNavigation$: Observable; + globalHelpExtensionMenuLinks$: Observable; helpExtension$: Observable; helpSupportUrl$: Observable; navControlsLeft$: Observable; @@ -80,6 +82,7 @@ export function Header({ onIsLockedUpdate, homeHref, breadcrumbsAppendExtension$, + globalHelpExtensionMenuLinks$, ...observables }: HeaderProps) { const isVisible = useObservable(observables.isVisible$, false); @@ -145,6 +148,7 @@ export function Header({ , { + test('it only renders the default content', () => { + const application = applicationServiceMock.createInternalStartContract(); + const helpExtension$ = new BehaviorSubject(undefined); + const helpSupportUrl$ = new BehaviorSubject(''); + + const component = mountWithIntl( + + ); + + expect(component.find('EuiButtonEmpty').length).toBe(1); // only the toggle view on/off button + component.find('EuiButtonEmpty').simulate('click'); + + // 4 default links + the toggle button + expect(component.find('EuiButtonEmpty').length).toBe(5); + }); + + test('it renders the global custom content + the default content', () => { + const application = applicationServiceMock.createInternalStartContract(); + const helpExtension$ = new BehaviorSubject(undefined); + const helpSupportUrl$ = new BehaviorSubject(''); + + const component = mountWithIntl( + + ); + + expect(component.find('EuiButtonEmpty').length).toBe(1); // only the toggle view on/off button + component.find('EuiButtonEmpty').simulate('click'); + + // 2 custom global link + 4 default links + the toggle button + expect(component.find('EuiButtonEmpty').length).toBe(7); + + expect(component.find('[data-test-subj="my-test-custom-link"]').exists()).toBeTruthy(); + + // The first global component is the second button (first is the toggle button) + expect(component.find('EuiButtonEmpty').at(1).prop('data-test-subj')).toBe( + 'my-test-custom-link' + ); + }); +}); diff --git a/packages/core/chrome/core-chrome-browser-internal/src/ui/header/header_help_menu.tsx b/packages/core/chrome/core-chrome-browser-internal/src/ui/header/header_help_menu.tsx index 4e797df33554..f938608091ba 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/ui/header/header_help_menu.tsx +++ b/packages/core/chrome/core-chrome-browser-internal/src/ui/header/header_help_menu.tsx @@ -25,13 +25,17 @@ import { } from '@elastic/eui'; import type { InternalApplicationStart } from '@kbn/core-application-browser-internal'; -import type { ChromeHelpExtension } from '@kbn/core-chrome-browser'; +import type { + ChromeHelpExtension, + ChromeGlobalHelpExtensionMenuLink, +} from '@kbn/core-chrome-browser'; import { GITHUB_CREATE_ISSUE_LINK, KIBANA_FEEDBACK_LINK } from '../../constants'; import { HeaderExtension } from './header_extension'; import { isModifiedOrPrevented } from './nav_link'; interface Props { navigateToUrl: InternalApplicationStart['navigateToUrl']; + globalHelpExtensionMenuLinks$: Observable; helpExtension$: Observable; helpSupportUrl$: Observable; kibanaVersion: string; @@ -42,6 +46,7 @@ interface State { isOpen: boolean; helpExtension?: ChromeHelpExtension; helpSupportUrl: string; + globalHelpExtensionMenuLinks: ChromeGlobalHelpExtensionMenuLink[]; } export class HeaderHelpMenu extends Component { @@ -54,17 +59,20 @@ export class HeaderHelpMenu extends Component { isOpen: false, helpExtension: undefined, helpSupportUrl: '', + globalHelpExtensionMenuLinks: [], }; } public componentDidMount() { this.subscription = combineLatest( this.props.helpExtension$, - this.props.helpSupportUrl$ - ).subscribe(([helpExtension, helpSupportUrl]) => { + this.props.helpSupportUrl$, + this.props.globalHelpExtensionMenuLinks$ + ).subscribe(([helpExtension, helpSupportUrl, globalHelpExtensionMenuLinks]) => { this.setState({ helpExtension, helpSupportUrl, + globalHelpExtensionMenuLinks, }); }); } @@ -80,6 +88,7 @@ export class HeaderHelpMenu extends Component { const { kibanaVersion } = this.props; const defaultContent = this.renderDefaultContent(); + const globalCustomContent = this.renderGlobalCustomContent(); const customContent = this.renderCustomContent(); const button = ( @@ -126,8 +135,9 @@ export class HeaderHelpMenu extends Component {
+ {globalCustomContent} {defaultContent} - {defaultContent && customContent && } + {(defaultContent || customContent) && } {customContent}
@@ -183,6 +193,22 @@ export class HeaderHelpMenu extends Component { ); } + private renderGlobalCustomContent() { + const { navigateToUrl } = this.props; + const { globalHelpExtensionMenuLinks } = this.state; + + return globalHelpExtensionMenuLinks + .sort((a, b) => b.priority - a.priority) + .map((link, index) => { + const { linkType, content: text, href, ...rest } = link; + return createCustomLink(index, text, true, { + href, + onClick: this.createOnClickHandler(href, navigateToUrl), + ...rest, + }); + }); + } + private renderCustomContent() { const { helpExtension } = this.state; if (!helpExtension) { diff --git a/packages/core/chrome/core-chrome-browser-mocks/src/chrome_service.mock.ts b/packages/core/chrome/core-chrome-browser-mocks/src/chrome_service.mock.ts index fc2a48f1329d..2f5c4deb1f38 100644 --- a/packages/core/chrome/core-chrome-browser-mocks/src/chrome_service.mock.ts +++ b/packages/core/chrome/core-chrome-browser-mocks/src/chrome_service.mock.ts @@ -50,6 +50,8 @@ const createStartContractMock = () => { setBreadcrumbs: jest.fn(), getBreadcrumbsAppendExtension$: jest.fn(), setBreadcrumbsAppendExtension: jest.fn(), + getGlobalHelpExtensionMenuLinks$: jest.fn(), + registerGlobalHelpExtensionMenuLink: jest.fn(), getHelpExtension$: jest.fn(), setHelpExtension: jest.fn(), setHelpSupportUrl: jest.fn(), @@ -66,6 +68,7 @@ const createStartContractMock = () => { startContract.getBreadcrumbs$.mockReturnValue(new BehaviorSubject([{} as ChromeBreadcrumb])); startContract.getBreadcrumbsAppendExtension$.mockReturnValue(new BehaviorSubject(undefined)); startContract.getCustomNavLink$.mockReturnValue(new BehaviorSubject(undefined)); + startContract.getGlobalHelpExtensionMenuLinks$.mockReturnValue(new BehaviorSubject([])); startContract.getHelpExtension$.mockReturnValue(new BehaviorSubject(undefined)); startContract.getIsNavDrawerLocked$.mockReturnValue(new BehaviorSubject(false)); startContract.getBodyClasses$.mockReturnValue(new BehaviorSubject([])); diff --git a/packages/core/chrome/core-chrome-browser/index.ts b/packages/core/chrome/core-chrome-browser/index.ts index 16f0134afb7b..3fbef34126a4 100644 --- a/packages/core/chrome/core-chrome-browser/index.ts +++ b/packages/core/chrome/core-chrome-browser/index.ts @@ -23,6 +23,7 @@ export type { ChromeHelpExtensionMenuDocumentationLink, ChromeHelpExtensionMenuDiscussLink, ChromeHelpExtensionMenuCustomLink, + ChromeGlobalHelpExtensionMenuLink, ChromeDocTitle, ChromeStart, ChromeRecentlyAccessed, diff --git a/packages/core/chrome/core-chrome-browser/src/contracts.ts b/packages/core/chrome/core-chrome-browser/src/contracts.ts index e3a4f09c2cbb..a81d9c3c6338 100644 --- a/packages/core/chrome/core-chrome-browser/src/contracts.ts +++ b/packages/core/chrome/core-chrome-browser/src/contracts.ts @@ -14,6 +14,7 @@ import type { ChromeNavControls } from './nav_controls'; import type { ChromeHelpExtension } from './help_extension'; import type { ChromeBreadcrumb, ChromeBreadcrumbsAppendExtension } from './breadcrumb'; import type { ChromeBadge, ChromeUserBanner } from './types'; +import { ChromeGlobalHelpExtensionMenuLink } from './help_extension'; /** * ChromeStart allows plugins to customize the global chrome header UI and @@ -106,7 +107,19 @@ export interface ChromeStart { setCustomNavLink(newCustomNavLink?: Partial): void; /** - * Get an observable of the current custom help conttent + * Get the list of the registered global help extension menu links + */ + getGlobalHelpExtensionMenuLinks$(): Observable; + + /** + * Append a global help extension menu link + */ + registerGlobalHelpExtensionMenuLink( + globalHelpExtensionMenuLink: ChromeGlobalHelpExtensionMenuLink + ): void; + + /** + * Get an observable of the current custom help content */ getHelpExtension$(): Observable; diff --git a/packages/core/chrome/core-chrome-browser/src/help_extension.ts b/packages/core/chrome/core-chrome-browser/src/help_extension.ts index 3acebd168f49..f682a166ca29 100644 --- a/packages/core/chrome/core-chrome-browser/src/help_extension.ts +++ b/packages/core/chrome/core-chrome-browser/src/help_extension.ts @@ -94,6 +94,14 @@ export interface ChromeHelpExtensionMenuCustomLink extends ChromeHelpExtensionLi content: React.ReactNode; } +/** @public */ +export interface ChromeGlobalHelpExtensionMenuLink extends ChromeHelpExtensionMenuCustomLink { + /** + * Highest priority items are listed at the top of the list of links. + */ + priority: number; +} + /** @public */ export type ChromeHelpExtensionMenuLink = | ChromeHelpExtensionMenuGitHubLink diff --git a/packages/core/chrome/core-chrome-browser/src/index.ts b/packages/core/chrome/core-chrome-browser/src/index.ts index 8414de3193c4..716af097fded 100644 --- a/packages/core/chrome/core-chrome-browser/src/index.ts +++ b/packages/core/chrome/core-chrome-browser/src/index.ts @@ -18,6 +18,7 @@ export type { ChromeHelpExtensionMenuDiscussLink, ChromeHelpExtensionMenuDocumentationLink, ChromeHelpExtensionMenuGitHubLink, + ChromeGlobalHelpExtensionMenuLink, } from './help_extension'; export type { ChromeNavControls, ChromeNavControl } from './nav_controls'; export type { ChromeNavLinks, ChromeNavLink } from './nav_links'; diff --git a/packages/core/http/core-http-resources-server-internal/src/get_apm_config.ts b/packages/core/http/core-http-resources-server-internal/src/get_apm_config.ts index 881efe261d79..82b4f04410a6 100644 --- a/packages/core/http/core-http-resources-server-internal/src/get_apm_config.ts +++ b/packages/core/http/core-http-resources-server-internal/src/get_apm_config.ts @@ -23,8 +23,10 @@ export const getApmConfig = (requestPath: string) => { return null; } + // Cleanup RUM unsupported attrbiutes from base apm config. + const { contextPropagationOnly, logUncaughtExceptions, ...restOfConfig } = baseConfig; const config: Record = { - ...baseConfig, + ...restOfConfig, pageLoadTransactionName: requestPath, }; diff --git a/packages/core/logging/core-logging-server-internal/src/appenders/rewrite/policies/meta/meta_policy.test.ts b/packages/core/logging/core-logging-server-internal/src/appenders/rewrite/policies/meta/meta_policy.test.ts index 3c12e479cc83..86d4df2d1d70 100644 --- a/packages/core/logging/core-logging-server-internal/src/appenders/rewrite/policies/meta/meta_policy.test.ts +++ b/packages/core/logging/core-logging-server-internal/src/appenders/rewrite/policies/meta/meta_policy.test.ts @@ -124,7 +124,7 @@ describe('MetaRewritePolicy', () => { "error": Object {}, "tags": Array [ "0", - undefined, + , ], } `); diff --git a/packages/core/metrics/core-metrics-server-internal/src/metrics_service.test.ts b/packages/core/metrics/core-metrics-server-internal/src/metrics_service.test.ts index 351e2aca43f5..b17cb6c3195a 100644 --- a/packages/core/metrics/core-metrics-server-internal/src/metrics_service.test.ts +++ b/packages/core/metrics/core-metrics-server-internal/src/metrics_service.test.ts @@ -30,7 +30,7 @@ describe('MetricsService', () => { let metricsService: MetricsService; beforeEach(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); const configService = configServiceMock.create({ atPath: { interval: moment.duration(testInterval) }, diff --git a/packages/core/overlays/core-overlays-browser-internal/src/banners/user_banner_service.test.ts b/packages/core/overlays/core-overlays-browser-internal/src/banners/user_banner_service.test.ts index 378de1611a74..193621177423 100644 --- a/packages/core/overlays/core-overlays-browser-internal/src/banners/user_banner_service.test.ts +++ b/packages/core/overlays/core-overlays-browser-internal/src/banners/user_banner_service.test.ts @@ -56,7 +56,7 @@ describe('OverlayBannersService', () => { }); it('dismisses banner after timeout', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); startService('testing banner!'); expect(banners.remove).not.toHaveBeenCalled(); diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.test.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.test.ts index e906f119a657..83854c3aafda 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.test.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.test.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ +import { setImmediate } from 'timers/promises'; import { join } from 'path'; import loadJsonFile from 'load-json-file'; -import { setImmediate } from 'timers/promises'; import { clientProviderInstanceMock, diff --git a/packages/core/ui-settings/core-ui-settings-server-internal/src/cache.test.ts b/packages/core/ui-settings/core-ui-settings-server-internal/src/cache.test.ts index f4829d33dd71..06a91ce079e6 100644 --- a/packages/core/ui-settings/core-ui-settings-server-internal/src/cache.test.ts +++ b/packages/core/ui-settings/core-ui-settings-server-internal/src/cache.test.ts @@ -10,7 +10,7 @@ import { Cache } from './cache'; describe('Cache', () => { beforeEach(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterEach(() => { diff --git a/packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_client.test.ts b/packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_client.test.ts index 0467897d2fde..87a43026b614 100644 --- a/packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_client.test.ts +++ b/packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_client.test.ts @@ -707,7 +707,7 @@ describe('ui settings', () => { describe('caching', () => { describe('read operations cache user config', () => { beforeEach(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterEach(() => { diff --git a/packages/kbn-apm-synthtrace/BUILD.bazel b/packages/kbn-apm-synthtrace/BUILD.bazel index 2f87b8604491..1160e8a7a161 100644 --- a/packages/kbn-apm-synthtrace/BUILD.bazel +++ b/packages/kbn-apm-synthtrace/BUILD.bazel @@ -61,6 +61,7 @@ TYPES_DEPS = [ "@npm//p-limit", "@npm//@types/node-fetch", "@npm//@types/semver", + "@npm//@types/yargs", ] jsts_transpiler( diff --git a/packages/kbn-apm-synthtrace/src/lib/apm/serverless.ts b/packages/kbn-apm-synthtrace/src/lib/apm/serverless.ts index b67586c18a07..f2eb47685bf3 100644 --- a/packages/kbn-apm-synthtrace/src/lib/apm/serverless.ts +++ b/packages/kbn-apm-synthtrace/src/lib/apm/serverless.ts @@ -33,7 +33,6 @@ export class Serverless extends BaseSpan { ...fields, 'metricset.name': 'app', 'faas.execution': faasExection, - 'faas.id': fields['service.name'], }); } diff --git a/packages/kbn-apm-synthtrace/src/lib/apm/serverless_function.ts b/packages/kbn-apm-synthtrace/src/lib/apm/serverless_function.ts index e10bb23b1f93..c8287066cc27 100644 --- a/packages/kbn-apm-synthtrace/src/lib/apm/serverless_function.ts +++ b/packages/kbn-apm-synthtrace/src/lib/apm/serverless_function.ts @@ -7,7 +7,6 @@ */ import { Entity } from '../entity'; -import { generateShortId } from '../utils/generate_id'; import { ApmFields } from './apm_fields'; import { ServerlessInstance } from './serverless_instance'; @@ -33,7 +32,7 @@ export function serverlessFunction({ agentName: string; serviceName?: string; }) { - const faasId = `arn:aws:lambda:us-west-2:${generateShortId()}:function:${functionName}`; + const faasId = `arn:aws:lambda:us-west-2:001:function:${functionName}`; return new ServerlessFunction({ 'service.name': serviceName || faasId, 'faas.id': faasId, diff --git a/packages/kbn-coloring/src/shared_components/coloring/palette_configuration.test.tsx b/packages/kbn-coloring/src/shared_components/coloring/palette_configuration.test.tsx index 696baceffb54..f7f8367d9ed7 100644 --- a/packages/kbn-coloring/src/shared_components/coloring/palette_configuration.test.tsx +++ b/packages/kbn-coloring/src/shared_components/coloring/palette_configuration.test.tsx @@ -61,7 +61,7 @@ describe('palette panel', () => { dataBounds: { min: 0, max: 100 }, }; - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); function changePaletteIn(instance: ReactWrapper, newPaletteName: string) { diff --git a/packages/kbn-dev-cli-runner/src/run_with_commands.test.ts b/packages/kbn-dev-cli-runner/src/run_with_commands.test.ts index 329e858b08f5..150765f1d9b6 100644 --- a/packages/kbn-dev-cli-runner/src/run_with_commands.test.ts +++ b/packages/kbn-dev-cli-runner/src/run_with_commands.test.ts @@ -8,6 +8,7 @@ import { ToolingLog, ToolingLogCollectingWriter } from '@kbn/tooling-log'; import { ProcRunner } from '@kbn/dev-proc-runner'; +jest.mock('./metrics'); import { FlagsReader } from './flags_reader'; import { RunWithCommands } from './run_with_commands'; @@ -48,7 +49,7 @@ it('extends the context using extendContext()', async () => { flagsReader: expect.any(FlagsReader), addCleanupTask: expect.any(Function), procRunner: expect.any(ProcRunner), - statsMeta: expect.any(Map), + statsMeta: undefined, extraContext: true, }); diff --git a/packages/kbn-dev-utils/src/diff_strings.ts b/packages/kbn-dev-utils/src/diff_strings.ts index 11b7e574c756..03ca506fd4c9 100644 --- a/packages/kbn-dev-utils/src/diff_strings.ts +++ b/packages/kbn-dev-utils/src/diff_strings.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import jestDiff from 'jest-diff'; +import { diff as jestDiff } from 'jest-diff'; import stripAnsi from 'strip-ansi'; import Chalk from 'chalk'; diff --git a/packages/kbn-doc-links/src/get_doc_links.ts b/packages/kbn-doc-links/src/get_doc_links.ts index 8783f21a1af9..796e46ecbf6e 100644 --- a/packages/kbn-doc-links/src/get_doc_links.ts +++ b/packages/kbn-doc-links/src/get_doc_links.ts @@ -50,6 +50,10 @@ export const getDocLinks = ({ kibanaBranch }: GetDocLinkOptions): DocLinks => { overview: `${APM_DOCS}guide/${DOC_LINK_VERSION}/apm-overview.html`, tailSamplingPolicies: `${APM_DOCS}guide/${DOC_LINK_VERSION}/configure-tail-based-sampling.html`, elasticAgent: `${APM_DOCS}guide/${DOC_LINK_VERSION}/upgrade-to-apm-integration.html`, + storageExplorer: `${KIBANA_DOCS}storage-explorer.html`, + spanCompression: `${APM_DOCS}guide/${DOC_LINK_VERSION}/span-compression.html`, + transactionSampling: `${APM_DOCS}guide/${DOC_LINK_VERSION}/sampling.html`, + indexLifecycleManagement: `${APM_DOCS}guide/${DOC_LINK_VERSION}/ilm-how-to.html`, }, canvas: { guide: `${KIBANA_DOCS}canvas.html`, diff --git a/packages/kbn-doc-links/src/types.ts b/packages/kbn-doc-links/src/types.ts index 443de5027669..7affe129b817 100644 --- a/packages/kbn-doc-links/src/types.ts +++ b/packages/kbn-doc-links/src/types.ts @@ -35,6 +35,10 @@ export interface DocLinks { readonly overview: string; readonly tailSamplingPolicies: string; readonly elasticAgent: string; + readonly storageExplorer: string; + readonly spanCompression: string; + readonly transactionSampling: string; + readonly indexLifecycleManagement: string; }; readonly canvas: { readonly guide: string; diff --git a/packages/kbn-es-query/src/kuery/functions/is.test.ts b/packages/kbn-es-query/src/kuery/functions/is.test.ts index f36ea0d17dc8..bde7e956f0c4 100644 --- a/packages/kbn-es-query/src/kuery/functions/is.test.ts +++ b/packages/kbn-es-query/src/kuery/functions/is.test.ts @@ -361,6 +361,23 @@ describe('kuery functions', () => { expect(result).toEqual(expected); }); + + test('should use a term query for keyword fields', () => { + const node = nodeTypes.function.buildNode('is', 'machine.os.keyword', 'Win 7'); + const result = is.toElasticsearchQuery(node, indexPattern); + expect(result).toEqual({ + bool: { + should: [ + { + term: { + 'machine.os.keyword': 'Win 7', + }, + }, + ], + minimum_should_match: 1, + }, + }); + }); }); }); }); diff --git a/packages/kbn-es-query/src/kuery/functions/is.ts b/packages/kbn-es-query/src/kuery/functions/is.ts index f2db74857b02..5493a3a6072e 100644 --- a/packages/kbn-es-query/src/kuery/functions/is.ts +++ b/packages/kbn-es-query/src/kuery/functions/is.ts @@ -100,6 +100,7 @@ export function toElasticsearchQuery( } const queries = fields!.reduce((accumulator: any, field: DataViewFieldBase) => { + const isKeywordField = field.esTypes?.length === 1 && field.esTypes.includes('keyword'); const wrapWithNestedQuery = (query: any) => { // Wildcards can easily include nested and non-nested fields. There isn't a good way to let // users handle this themselves so we automatically add nested queries in this scenario. @@ -142,7 +143,7 @@ export function toElasticsearchQuery( }), ]; } else if (wildcard.isNode(valueArg)) { - const query = field.esTypes?.includes('keyword') + const query = isKeywordField ? { wildcard: { [field.name]: value, @@ -177,7 +178,7 @@ export function toElasticsearchQuery( }), ]; } else { - const queryType = type === 'phrase' ? 'match_phrase' : 'match'; + const queryType = isKeywordField ? 'term' : type === 'phrase' ? 'match_phrase' : 'match'; return [ ...accumulator, wrapWithNestedQuery({ diff --git a/packages/kbn-managed-vscode-config/src/update_vscode_config.test.ts b/packages/kbn-managed-vscode-config/src/update_vscode_config.test.ts index f9e3aecb89b9..cfa1964dd297 100644 --- a/packages/kbn-managed-vscode-config/src/update_vscode_config.test.ts +++ b/packages/kbn-managed-vscode-config/src/update_vscode_config.test.ts @@ -133,7 +133,6 @@ it('persists comments in the original file', () => { `); expect(newJson).toMatchInlineSnapshot(` // @managed - /** * This is a top level comment */ diff --git a/packages/kbn-monaco/src/painless/diagnostics_adapter.test.ts b/packages/kbn-monaco/src/painless/diagnostics_adapter.test.ts index 5d00ad726d03..7af335ed4698 100644 --- a/packages/kbn-monaco/src/painless/diagnostics_adapter.test.ts +++ b/packages/kbn-monaco/src/painless/diagnostics_adapter.test.ts @@ -24,9 +24,8 @@ const getMockWorker = async () => { } as any; }; -function flushPromises() { - return new Promise((resolve) => setImmediate(resolve)); -} +const flushPromises = () => + new Promise((resolve) => jest.requireActual('timers').setImmediate(resolve)); describe('Painless DiagnosticAdapter', () => { let diagnosticAdapter: DiagnosticsAdapter; @@ -35,7 +34,7 @@ describe('Painless DiagnosticAdapter', () => { let validation: LangValidation; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/packages/kbn-optimizer/src/common/rxjs_helpers.test.ts b/packages/kbn-optimizer/src/common/rxjs_helpers.test.ts index 694a60688395..c280890f2dff 100644 --- a/packages/kbn-optimizer/src/common/rxjs_helpers.test.ts +++ b/packages/kbn-optimizer/src/common/rxjs_helpers.test.ts @@ -11,7 +11,7 @@ import { fakeSchedulers } from 'rxjs-marbles/jest'; import { pipeClosure, debounceTimeBuffer, maybeMap, maybe } from './rxjs_helpers'; -jest.useFakeTimers(); +jest.useFakeTimers('legacy'); describe('pipeClosure()', () => { it('calls closure on each subscription to setup unique state', async () => { @@ -70,7 +70,7 @@ describe('maybeMap()', () => { describe('debounceTimeBuffer()', () => { beforeEach(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterEach(() => { diff --git a/packages/kbn-optimizer/src/integration_tests/watch_bundles_for_changes.test.ts b/packages/kbn-optimizer/src/integration_tests/watch_bundles_for_changes.test.ts index 91a2f7324806..08f6cdc61bce 100644 --- a/packages/kbn-optimizer/src/integration_tests/watch_bundles_for_changes.test.ts +++ b/packages/kbn-optimizer/src/integration_tests/watch_bundles_for_changes.test.ts @@ -58,7 +58,7 @@ const bundleCacheEvent$ = Rx.from(BUNDLES).pipe( ); beforeEach(async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterEach(async () => { diff --git a/packages/kbn-optimizer/src/optimizer/optimizer_cache_key.test.ts b/packages/kbn-optimizer/src/optimizer/optimizer_cache_key.test.ts index 1cfc1b184d87..d22ba9528200 100644 --- a/packages/kbn-optimizer/src/optimizer/optimizer_cache_key.test.ts +++ b/packages/kbn-optimizer/src/optimizer/optimizer_cache_key.test.ts @@ -23,7 +23,7 @@ jest.mock('@kbn/synthetic-package-map', () => { jest.mock('../common/hashes', () => { return { Hashes: class MockHashes { - static ofFiles = jest.fn(() => { + static ofFiles: any = jest.fn(() => { return new MockHashes(); }); diff --git a/packages/kbn-react-field/BUILD.bazel b/packages/kbn-react-field/BUILD.bazel index ccc12245d323..7b4e78f9868f 100644 --- a/packages/kbn-react-field/BUILD.bazel +++ b/packages/kbn-react-field/BUILD.bazel @@ -51,8 +51,8 @@ TYPES_DEPS = [ "@npm//@elastic/eui", "@npm//@types/classnames", "@npm//@types/jest", - "@npm//@types/prop-types", "@npm//@types/node", + "@npm//@types/prop-types", "@npm//@types/react", ] diff --git a/packages/kbn-test/jest-preset.js b/packages/kbn-test/jest-preset.js index 3cb0ce86602c..c13dbb5149f4 100644 --- a/packages/kbn-test/jest-preset.js +++ b/packages/kbn-test/jest-preset.js @@ -113,9 +113,6 @@ module.exports = { `integration_tests/`, ], - // This option allows use of a custom test runner - testRunner: 'jest-circus/runner', - // A map from regular expressions to paths to transformers transform: { '^.+\\.(js|tsx?)$': '/node_modules/@kbn/test/target_node/src/jest/babel_transform.js', diff --git a/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.ts b/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.ts index 02e96f62e248..b7efb33bcd50 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.ts @@ -13,8 +13,6 @@ import { addSerializer, } from 'jest-snapshot'; import path from 'path'; -import prettier from 'prettier'; -import babelTraverse from '@babel/traverse'; import { once } from 'lodash'; import { Lifecycle } from '../lifecycle'; import { Suite, Test } from '../../fake_mocha_types'; @@ -161,8 +159,8 @@ function getSnapshotState(file: string, updateSnapshot: SnapshotUpdateState) { path.join(dirname + `/__snapshots__/` + filename.replace(path.extname(filename), '.snap')), { updateSnapshot, - getPrettier: () => prettier, - getBabelTraverse: () => babelTraverse, + prettierPath: require.resolve('prettier'), + snapshotFormat: { escapeString: true, printBasicPrototype: true }, } ); diff --git a/packages/kbn-test/src/jest/babel_transform.js b/packages/kbn-test/src/jest/babel_transform.js index 8b25b635bbb1..f2fbbfe00b60 100644 --- a/packages/kbn-test/src/jest/babel_transform.js +++ b/packages/kbn-test/src/jest/babel_transform.js @@ -8,7 +8,7 @@ const babelJest = require('babel-jest'); -module.exports = babelJest.createTransformer({ +module.exports = babelJest.default.createTransformer({ presets: [ [ require.resolve('@kbn/babel-preset/node_preset'), diff --git a/packages/kbn-test/src/jest/configs/get_tests_for_config_paths.ts b/packages/kbn-test/src/jest/configs/get_tests_for_config_paths.ts index 9d0105977a12..10474413ba29 100644 --- a/packages/kbn-test/src/jest/configs/get_tests_for_config_paths.ts +++ b/packages/kbn-test/src/jest/configs/get_tests_for_config_paths.ts @@ -7,8 +7,8 @@ */ import { readConfig } from 'jest-config'; -import { createContext } from 'jest-runtime'; import { SearchSource } from 'jest'; +import Runtime from 'jest-runtime'; import { asyncMapWithLimit } from '@kbn/std'; const EMPTY_ARGV = { @@ -34,7 +34,7 @@ export async function getTestsForConfigPaths( return await asyncMapWithLimit(configPaths, 60, async (path) => { const config = await readConfig(EMPTY_ARGV, path); const searchSource = new SearchSource( - await createContext(config.projectConfig, { + await Runtime.createContext(config.projectConfig, { maxWorkers: 1, watchman: false, watch: false, diff --git a/packages/kbn-test/src/jest/integration_tests/junit_reporter.test.ts b/packages/kbn-test/src/jest/integration_tests/junit_reporter.test.ts index f2bf25067a9b..49b52a037e30 100644 --- a/packages/kbn-test/src/jest/integration_tests/junit_reporter.test.ts +++ b/packages/kbn-test/src/jest/integration_tests/junit_reporter.test.ts @@ -26,7 +26,8 @@ afterAll(async () => { }); const parseXml = promisify(xml2js.parseString); -it( +// FLAKY https://github.com/elastic/kibana/issues/143993 +it.skip( 'produces a valid junit report for failures', async () => { const result = await execa( diff --git a/packages/kbn-test/src/jest/run.ts b/packages/kbn-test/src/jest/run.ts index daf4706cebdb..8efcafbec6c8 100644 --- a/packages/kbn-test/src/jest/run.ts +++ b/packages/kbn-test/src/jest/run.ts @@ -20,31 +20,18 @@ import { resolve, relative, sep as osSep } from 'path'; import { existsSync } from 'fs'; import { run } from 'jest'; -import { buildArgv } from 'jest-cli/build/cli'; import { ToolingLog } from '@kbn/tooling-log'; import { getTimeReporter } from '@kbn/ci-stats-reporter'; import { REPO_ROOT } from '@kbn/utils'; import { map } from 'lodash'; +import getopts from 'getopts'; // yarn test:jest src/core/server/saved_objects // yarn test:jest src/core/public/core_system.test.ts // :kibana/src/core/server/saved_objects yarn test:jest -// Patch node 16 types to be compatible with jest 26 -// https://github.com/facebook/jest/issues/11640#issuecomment-893867514 -/* eslint-disable @typescript-eslint/no-namespace,@typescript-eslint/no-empty-interface,no-console */ -declare global { - namespace NodeJS { - interface Global {} - interface InspectOptions {} - - interface ConsoleConstructor extends console.ConsoleConstructor {} - } -} -/* eslint-enable */ - export function runJest(configName = 'jest.config.js') { - const argv = buildArgv(process.argv); + const argv = getopts(process.argv.slice(2)); const devConfigName = 'jest.config.dev.js'; const log = new ToolingLog({ @@ -60,7 +47,7 @@ export function runJest(configName = 'jest.config.js') { const cwd: string = process.env.INIT_CWD || process.cwd(); if (!argv.config) { - testFiles = argv._.splice(2).map((p) => resolve(cwd, p.toString())); + testFiles = argv._.map((p) => resolve(cwd, p.toString())); const commonTestFiles = commonBasePath(testFiles); const testFilesProvided = testFiles.length > 0; diff --git a/packages/kbn-test/src/jest/setup/setup_test.js b/packages/kbn-test/src/jest/setup/setup_test.js index a3eb15a2d263..b0038daf196c 100644 --- a/packages/kbn-test/src/jest/setup/setup_test.js +++ b/packages/kbn-test/src/jest/setup/setup_test.js @@ -13,3 +13,9 @@ import 'jest-styled-components'; import '@testing-library/jest-dom'; + +/** + * Removed in Jest 27/jsdom, used in some transitive dependencies + */ +global.setImmediate = require('core-js/stable/set-immediate'); +global.clearImmediate = require('core-js/stable/clear-immediate'); diff --git a/packages/shared-ux/storybook/config/BUILD.bazel b/packages/shared-ux/storybook/config/BUILD.bazel index 32e5b6cb440c..422fe45ee722 100644 --- a/packages/shared-ux/storybook/config/BUILD.bazel +++ b/packages/shared-ux/storybook/config/BUILD.bazel @@ -62,12 +62,12 @@ RUNTIME_DEPS = [ # # References to NPM packages work the same as RUNTIME_DEPS TYPES_DEPS = [ - "@npm//jest-mock", "//packages/kbn-storybook:npm_module_types", "//packages/kbn-ambient-ui-types:npm_module_types", "//packages/kbn-ambient-storybook-types:npm_module_types", "@npm//@types/node", "@npm//@types/jest", + "@npm//jest-mock", "@npm//@storybook/react", "@npm//@storybook/addon-actions", ] diff --git a/src/core/server/plugins/plugins_system.test.ts b/src/core/server/plugins/plugins_system.test.ts index 0d55db06c091..5dc8507302bc 100644 --- a/src/core/server/plugins/plugins_system.test.ts +++ b/src/core/server/plugins/plugins_system.test.ts @@ -552,7 +552,7 @@ test('`startPlugins` only starts plugins that were setup', async () => { describe('setup', () => { beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { jest.useRealTimers(); @@ -589,7 +589,7 @@ describe('setup', () => { describe('start', () => { beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { jest.useRealTimers(); @@ -748,7 +748,7 @@ describe('asynchronous plugins', () => { describe('stop', () => { beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/src/plugins/bfetch/common/buffer/tests/timed_item_buffer.test.ts b/src/plugins/bfetch/common/buffer/tests/timed_item_buffer.test.ts index 22d3fb2a4468..377ae064a439 100644 --- a/src/plugins/bfetch/common/buffer/tests/timed_item_buffer.test.ts +++ b/src/plugins/bfetch/common/buffer/tests/timed_item_buffer.test.ts @@ -9,7 +9,7 @@ import { TimedItemBuffer } from '../timed_item_buffer'; import { runItemBufferTests } from './run_item_buffer_tests'; -jest.useFakeTimers(); +jest.useFakeTimers('legacy'); beforeEach(() => { jest.clearAllTimers(); diff --git a/src/plugins/bfetch/public/batching/create_streaming_batched_function.test.ts b/src/plugins/bfetch/public/batching/create_streaming_batched_function.test.ts index 14fccf09d83b..d3aaaf256f2e 100644 --- a/src/plugins/bfetch/public/batching/create_streaming_batched_function.test.ts +++ b/src/plugins/bfetch/public/batching/create_streaming_batched_function.test.ts @@ -11,7 +11,8 @@ import { fetchStreaming as fetchStreamingReal } from '../streaming/fetch_streami import { AbortError, defer, of } from '@kbn/kibana-utils-plugin/public'; import { Subject } from 'rxjs'; -const flushPromises = () => new Promise((resolve) => setImmediate(resolve)); +const flushPromises = () => + new Promise((resolve) => jest.requireActual('timers').setImmediate(resolve)); const getPromiseState = (promise: Promise): Promise<'resolved' | 'rejected' | 'pending'> => Promise.race<'resolved' | 'rejected' | 'pending'>([ @@ -50,7 +51,7 @@ const setup = () => { describe('createStreamingBatchedFunction()', () => { beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx index ed8c87b5df14..10636f452004 100644 --- a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx +++ b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx @@ -115,7 +115,9 @@ function EditorUI({ initialTextValue, setEditorInstance }: EditorProps) { const loadBufferFromRemote = (url: string) => { const coreEditor = editor.getCoreEditor(); - if (/^https?:\/\//.test(url)) { + // Normalize and encode the URL to avoid issues with spaces and other special characters. + const encodedUrl = new URL(url).toString(); + if (/^https?:\/\//.test(encodedUrl)) { const loadFrom: Record = { url, // Having dataType here is required as it doesn't allow jQuery to `eval` content diff --git a/src/plugins/controls/common/control_group/control_group_panel_diff_system.ts b/src/plugins/controls/common/control_group/control_group_panel_diff_system.ts new file mode 100644 index 000000000000..0ef2438494d7 --- /dev/null +++ b/src/plugins/controls/common/control_group/control_group_panel_diff_system.ts @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import deepEqual from 'fast-deep-equal'; +import { omit, isEqual } from 'lodash'; +import { OptionsListEmbeddableInput, OPTIONS_LIST_CONTROL } from '../options_list/types'; + +import { ControlPanelState } from './types'; + +interface DiffSystem { + getPanelIsEqual: (initialInput: ControlPanelState, newInput: ControlPanelState) => boolean; +} + +export const genericControlPanelDiffSystem: DiffSystem = { + getPanelIsEqual: (initialInput, newInput) => { + return deepEqual(initialInput, newInput); + }, +}; + +export const ControlPanelDiffSystems: { + [key: string]: DiffSystem; +} = { + [OPTIONS_LIST_CONTROL]: { + getPanelIsEqual: (initialInput, newInput) => { + if (!deepEqual(omit(initialInput, 'explicitInput'), omit(newInput, 'explicitInput'))) { + return false; + } + + const { + exclude: excludeA, + selectedOptions: selectedA, + singleSelect: singleSelectA, + hideExclude: hideExcludeA, + runPastTimeout: runPastTimeoutA, + ...inputA + }: Partial = initialInput.explicitInput; + const { + exclude: excludeB, + selectedOptions: selectedB, + singleSelect: singleSelectB, + hideExclude: hideExcludeB, + runPastTimeout: runPastTimeoutB, + ...inputB + }: Partial = newInput.explicitInput; + + return ( + Boolean(excludeA) === Boolean(excludeB) && + Boolean(singleSelectA) === Boolean(singleSelectB) && + Boolean(hideExcludeA) === Boolean(hideExcludeB) && + Boolean(runPastTimeoutA) === Boolean(runPastTimeoutB) && + isEqual(selectedA ?? [], selectedB ?? []) && + deepEqual(inputA, inputB) + ); + }, + }, +}; diff --git a/src/plugins/controls/common/control_group/control_group_persistence.ts b/src/plugins/controls/common/control_group/control_group_persistence.ts index 16c06297b6fd..1dbe096307c5 100644 --- a/src/plugins/controls/common/control_group/control_group_persistence.ts +++ b/src/plugins/controls/common/control_group/control_group_persistence.ts @@ -9,7 +9,7 @@ import { SerializableRecord } from '@kbn/utility-types'; import deepEqual from 'fast-deep-equal'; -import { pick } from 'lodash'; +import { pick, omit, xor } from 'lodash'; import { ControlGroupInput } from '..'; import { DEFAULT_CONTROL_GROW, @@ -17,6 +17,10 @@ import { DEFAULT_CONTROL_WIDTH, } from './control_group_constants'; import { PersistableControlGroupInput, RawControlGroupAttributes } from './types'; +import { + ControlPanelDiffSystems, + genericControlPanelDiffSystem, +} from './control_group_panel_diff_system'; const safeJSONParse = (jsonString?: string): OutType | undefined => { if (!jsonString && typeof jsonString !== 'string') return; @@ -54,10 +58,40 @@ export const persistableControlGroupInputIsEqual = ( ...defaultInput, ...pick(b, ['panels', 'chainingSystem', 'controlStyle', 'ignoreParentSettings']), }; - if (deepEqual(inputA, inputB)) return true; + + if ( + getPanelsAreEqual(inputA.panels, inputB.panels) && + deepEqual(omit(inputA, 'panels'), omit(inputB, 'panels')) + ) + return true; + return false; }; +const getPanelsAreEqual = ( + originalPanels: PersistableControlGroupInput['panels'], + newPanels: PersistableControlGroupInput['panels'] +) => { + const originalPanelIds = Object.keys(originalPanels); + const newPanelIds = Object.keys(newPanels); + const panelIdDiff = xor(originalPanelIds, newPanelIds); + if (panelIdDiff.length > 0) { + return false; + } + + for (const panelId of newPanelIds) { + const newPanelType = newPanels[panelId].type; + const panelIsEqual = ControlPanelDiffSystems[newPanelType] + ? ControlPanelDiffSystems[newPanelType].getPanelIsEqual( + originalPanels[panelId], + newPanels[panelId] + ) + : genericControlPanelDiffSystem.getPanelIsEqual(originalPanels[panelId], newPanels[panelId]); + if (!panelIsEqual) return false; + } + return true; +}; + export const controlGroupInputToRawControlGroupAttributes = ( controlGroupInput: Omit ): RawControlGroupAttributes => { diff --git a/src/plugins/controls/common/options_list/mocks.tsx b/src/plugins/controls/common/options_list/mocks.tsx index 09f6d9caa33b..ccbe3a113247 100644 --- a/src/plugins/controls/common/options_list/mocks.tsx +++ b/src/plugins/controls/common/options_list/mocks.tsx @@ -28,6 +28,8 @@ const mockOptionsListEmbeddableInput = { selectedOptions: [], runPastTimeout: false, singleSelect: false, + allowExclude: false, + exclude: false, } as OptionsListEmbeddableInput; const mockOptionsListOutput = { diff --git a/src/plugins/controls/common/options_list/types.ts b/src/plugins/controls/common/options_list/types.ts index 25db6c50349c..59b78ee38d0b 100644 --- a/src/plugins/controls/common/options_list/types.ts +++ b/src/plugins/controls/common/options_list/types.ts @@ -17,6 +17,8 @@ export interface OptionsListEmbeddableInput extends DataControlInput { selectedOptions?: string[]; runPastTimeout?: boolean; singleSelect?: boolean; + hideExclude?: boolean; + exclude?: boolean; } export type OptionsListField = FieldSpec & { diff --git a/src/plugins/controls/public/options_list/components/options_list_control.tsx b/src/plugins/controls/public/options_list/components/options_list_control.tsx index 7eb8fe783cc0..93bf0cbef864 100644 --- a/src/plugins/controls/public/options_list/components/options_list_control.tsx +++ b/src/plugins/controls/public/options_list/components/options_list_control.tsx @@ -11,7 +11,13 @@ import classNames from 'classnames'; import { debounce, isEmpty } from 'lodash'; import React, { useCallback, useEffect, useMemo, useState, useRef } from 'react'; -import { EuiFilterButton, EuiFilterGroup, EuiPopover, useResizeObserver } from '@elastic/eui'; +import { + EuiFilterButton, + EuiFilterGroup, + EuiPopover, + EuiTextColor, + useResizeObserver, +} from '@elastic/eui'; import { useReduxEmbeddableContext } from '@kbn/presentation-util-plugin/public'; import { OptionsListStrings } from './options_list_strings'; @@ -43,6 +49,7 @@ export const OptionsListControl = ({ typeaheadSubject }: { typeaheadSubject: Sub const controlStyle = select((state) => state.explicitInput.controlStyle); const singleSelect = select((state) => state.explicitInput.singleSelect); const id = select((state) => state.explicitInput.id); + const exclude = select((state) => state.explicitInput.exclude); const loading = select((state) => state.output.loading); @@ -75,6 +82,11 @@ export const OptionsListControl = ({ typeaheadSubject }: { typeaheadSubject: Sub validSelectionsCount: validSelections?.length, selectionDisplayNode: ( <> + {exclude && ( + + {OptionsListStrings.control.getNegate()}{' '} + + )} {validSelections && ( {validSelections?.join(OptionsListStrings.control.getSeparator())} )} @@ -86,7 +98,7 @@ export const OptionsListControl = ({ typeaheadSubject }: { typeaheadSubject: Sub ), }; - }, [validSelections, invalidSelections]); + }, [exclude, validSelections, invalidSelections]); const button = (
diff --git a/src/plugins/controls/public/options_list/components/options_list_editor_options.tsx b/src/plugins/controls/public/options_list/components/options_list_editor_options.tsx index 0cd96232b714..0723fcaad6ba 100644 --- a/src/plugins/controls/public/options_list/components/options_list_editor_options.tsx +++ b/src/plugins/controls/public/options_list/components/options_list_editor_options.tsx @@ -8,7 +8,8 @@ import React, { useState } from 'react'; -import { EuiFormRow, EuiSwitch } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiIconTip, EuiSwitch } from '@elastic/eui'; +import { css } from '@emotion/react'; import { OptionsListStrings } from './options_list_strings'; import { ControlEditorProps, OptionsListEmbeddableInput } from '../..'; @@ -16,6 +17,7 @@ import { ControlEditorProps, OptionsListEmbeddableInput } from '../..'; interface OptionsListEditorState { singleSelect?: boolean; runPastTimeout?: boolean; + hideExclude?: boolean; } export const OptionsListEditorOptions = ({ @@ -25,6 +27,7 @@ export const OptionsListEditorOptions = ({ const [state, setState] = useState({ singleSelect: initialInput?.singleSelect, runPastTimeout: initialInput?.runPastTimeout, + hideExclude: initialInput?.hideExclude, }); return ( @@ -41,14 +44,40 @@ export const OptionsListEditorOptions = ({ { - onChange({ runPastTimeout: !state.runPastTimeout }); - setState((s) => ({ ...s, runPastTimeout: !s.runPastTimeout })); + onChange({ hideExclude: !state.hideExclude }); + setState((s) => ({ ...s, hideExclude: !s.hideExclude })); + if (initialInput?.exclude) onChange({ exclude: false }); }} /> + + + + { + onChange({ runPastTimeout: !state.runPastTimeout }); + setState((s) => ({ ...s, runPastTimeout: !s.runPastTimeout })); + }} + /> + + + + + + ); }; diff --git a/src/plugins/controls/public/options_list/components/options_list_popover.test.tsx b/src/plugins/controls/public/options_list/components/options_list_popover.test.tsx index f30d9a785ffe..eca6fe72376a 100644 --- a/src/plugins/controls/public/options_list/components/options_list_popover.test.tsx +++ b/src/plugins/controls/public/options_list/components/options_list_popover.test.tsx @@ -97,4 +97,22 @@ describe('Options list popover', () => { expect(child.text()).toBe(selections[i]); }); }); + + test('should default to exclude = false', () => { + const popover = mountComponent(); + const includeButton = findTestSubject(popover, 'optionsList__includeResults'); + const excludeButton = findTestSubject(popover, 'optionsList__excludeResults'); + expect(includeButton.prop('checked')).toBe(true); + expect(excludeButton.prop('checked')).toBeFalsy(); + }); + + test('if exclude = true, select appropriate button in button group', () => { + const popover = mountComponent({ + explicitInput: { exclude: true }, + }); + const includeButton = findTestSubject(popover, 'optionsList__includeResults'); + const excludeButton = findTestSubject(popover, 'optionsList__excludeResults'); + expect(includeButton.prop('checked')).toBeFalsy(); + expect(excludeButton.prop('checked')).toBe(true); + }); }); diff --git a/src/plugins/controls/public/options_list/components/options_list_popover.tsx b/src/plugins/controls/public/options_list/components/options_list_popover.tsx index 8863a3d1978a..48be0d925328 100644 --- a/src/plugins/controls/public/options_list/components/options_list_popover.tsx +++ b/src/plugins/controls/public/options_list/components/options_list_popover.tsx @@ -22,7 +22,11 @@ import { EuiBadge, EuiIcon, EuiTitle, + EuiPopoverFooter, + EuiButtonGroup, + useEuiBackgroundColor, } from '@elastic/eui'; +import { css } from '@emotion/react'; import { useReduxEmbeddableContext } from '@kbn/presentation-util-plugin/public'; import { optionsListReducers } from '../options_list_reducers'; @@ -34,12 +38,23 @@ export interface OptionsListPopoverProps { updateSearchString: (newSearchString: string) => void; } +const aggregationToggleButtons = [ + { + id: 'optionsList__includeResults', + label: OptionsListStrings.popover.getIncludeLabel(), + }, + { + id: 'optionsList__excludeResults', + label: OptionsListStrings.popover.getExcludeLabel(), + }, +]; + export const OptionsListPopover = ({ width, updateSearchString }: OptionsListPopoverProps) => { // Redux embeddable container Context const { useEmbeddableDispatch, useEmbeddableSelector: select, - actions: { selectOption, deselectOption, clearSelections, replaceSelection }, + actions: { selectOption, deselectOption, clearSelections, replaceSelection, setExclude }, } = useReduxEmbeddableContext(); const dispatch = useEmbeddableDispatch(); @@ -52,8 +67,10 @@ export const OptionsListPopover = ({ width, updateSearchString }: OptionsListPop const field = select((state) => state.componentState.field); const selectedOptions = select((state) => state.explicitInput.selectedOptions); + const hideExclude = select((state) => state.explicitInput.hideExclude); const singleSelect = select((state) => state.explicitInput.singleSelect); const title = select((state) => state.explicitInput.title); + const exclude = select((state) => state.explicitInput.exclude); const loading = select((state) => state.output.loading); @@ -65,6 +82,7 @@ export const OptionsListPopover = ({ width, updateSearchString }: OptionsListPop ); const [showOnlySelected, setShowOnlySelected] = useState(false); + const euiBackgroundColor = useEuiBackgroundColor('subdued'); return ( <> @@ -77,6 +95,7 @@ export const OptionsListPopover = ({ width, updateSearchString }: OptionsListPop direction="row" justifyContent="spaceBetween" alignItems="center" + responsive={false} > )}
+ {!hideExclude && ( + + + dispatch(setExclude(optionId === 'optionsList__excludeResults')) + } + buttonSize="compressed" + data-test-subj="optionsList__includeExcludeButtonGroup" + /> + + )} ); }; diff --git a/src/plugins/controls/public/options_list/components/options_list_strings.ts b/src/plugins/controls/public/options_list/components/options_list_strings.ts index 8e3d55ae6b76..cfab9633b81e 100644 --- a/src/plugins/controls/public/options_list/components/options_list_strings.ts +++ b/src/plugins/controls/public/options_list/components/options_list_strings.ts @@ -18,6 +18,10 @@ export const OptionsListStrings = { i18n.translate('controls.optionsList.control.placeholder', { defaultMessage: 'Any', }), + getNegate: () => + i18n.translate('controls.optionsList.control.negate', { + defaultMessage: 'NOT', + }), }, editor: { getAllowMultiselectTitle: () => @@ -26,7 +30,16 @@ export const OptionsListStrings = { }), getRunPastTimeoutTitle: () => i18n.translate('controls.optionsList.editor.runPastTimeout', { - defaultMessage: 'Run past timeout', + defaultMessage: 'Ignore timeout for results', + }), + getRunPastTimeoutTooltip: () => + i18n.translate('controls.optionsList.editor.runPastTimeout.tooltip', { + defaultMessage: + 'Wait to display results until the list is complete. This setting is useful for large data sets, but the results might take longer to populate.', + }), + getHideExcludeTitle: () => + i18n.translate('controls.optionsList.editor.hideExclude', { + defaultMessage: 'Allow selections to be excluded', }), }, popover: { @@ -86,5 +99,17 @@ export const OptionsListStrings = { '{selectedOptions} selected {selectedOptions, plural, one {option} other {options}} {selectedOptions, plural, one {is} other {are}} ignored because {selectedOptions, plural, one {it is} other {they are}} no longer in the data.', values: { selectedOptions }, }), + getIncludeLabel: () => + i18n.translate('controls.optionsList.popover.includeLabel', { + defaultMessage: 'Include', + }), + getExcludeLabel: () => + i18n.translate('controls.optionsList.popover.excludeLabel', { + defaultMessage: 'Exclude', + }), + getIncludeExcludeLegend: () => + i18n.translate('controls.optionsList.popover.excludeOptionsLegend', { + defaultMessage: 'Include or exclude selections', + }), }, }; diff --git a/src/plugins/controls/public/options_list/embeddable/options_list_embeddable.tsx b/src/plugins/controls/public/options_list/embeddable/options_list_embeddable.tsx index 292e7cb6b059..ffead8d9c20b 100644 --- a/src/plugins/controls/public/options_list/embeddable/options_list_embeddable.tsx +++ b/src/plugins/controls/public/options_list/embeddable/options_list_embeddable.tsx @@ -134,6 +134,7 @@ export class OptionsListEmbeddable extends Embeddable isEqual(a.selectedOptions, b.selectedOptions))) + .pipe( + distinctUntilChanged( + (a, b) => isEqual(a.selectedOptions, b.selectedOptions) && a.exclude === b.exclude + ) + ) .subscribe(async ({ selectedOptions: newSelectedOptions }) => { const { actions: { @@ -364,6 +369,7 @@ export class OptionsListEmbeddable extends Embeddable { const { getState } = this.reduxEmbeddableTools; const { validSelections } = getState().componentState ?? {}; + const { exclude } = this.getInput(); if (!validSelections || isEmpty(validSelections)) { return []; @@ -379,6 +385,7 @@ export class OptionsListEmbeddable extends Embeddable) => { if (state.explicitInput.selectedOptions) state.explicitInput.selectedOptions = []; }, + setExclude: (state: WritableDraft, action: PayloadAction) => { + state.explicitInput.exclude = action.payload; + }, clearValidAndInvalidSelections: (state: WritableDraft) => { state.componentState.invalidSelections = []; state.componentState.validSelections = []; diff --git a/src/plugins/dashboard/public/application/embeddable/dashboard_container.test.tsx b/src/plugins/dashboard/public/application/embeddable/dashboard_container.test.tsx index b6c3d2055d88..7ad4b6432450 100644 --- a/src/plugins/dashboard/public/application/embeddable/dashboard_container.test.tsx +++ b/src/plugins/dashboard/public/application/embeddable/dashboard_container.test.tsx @@ -46,7 +46,7 @@ beforeEach(() => { application = applicationServiceMock.createStartContract(); }); -test('DashboardContainer initializes embeddables', async (done) => { +test('DashboardContainer initializes embeddables', (done) => { const initialInput = getSampleDashboardInput({ panels: { '123': getSampleDashboardPanel({ @@ -96,7 +96,7 @@ test('DashboardContainer.addNewEmbeddable', async () => { expect(embeddableInContainer.id).toBe(embeddable.id); }); -test('DashboardContainer.replacePanel', async (done) => { +test('DashboardContainer.replacePanel', (done) => { const ID = '123'; const initialInput = getSampleDashboardInput({ panels: { @@ -139,7 +139,7 @@ test('DashboardContainer.replacePanel', async (done) => { }); }); -test('Container view mode change propagates to existing children', async (done) => { +test('Container view mode change propagates to existing children', async () => { const initialInput = getSampleDashboardInput({ panels: { '123': getSampleDashboardPanel({ @@ -154,7 +154,6 @@ test('Container view mode change propagates to existing children', async (done) expect(embeddable.getInput().viewMode).toBe(ViewMode.VIEW); container.updateInput({ viewMode: ViewMode.EDIT }); expect(embeddable.getInput().viewMode).toBe(ViewMode.EDIT); - done(); }); test('Container view mode change propagates to new children', async () => { diff --git a/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.test.tsx b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.test.tsx index 41e8b900d360..6972a521026b 100644 --- a/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.test.tsx +++ b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.test.tsx @@ -139,7 +139,7 @@ test.skip('DashboardGrid renders expanded panel', () => { }); // unhandled promise rejection: https://github.com/elastic/kibana/issues/112699 -test.skip('DashboardGrid unmount unsubscribes', async (done) => { +test.skip('DashboardGrid unmount unsubscribes', (done) => { const { props } = prepare(); const component = mountWithIntl( diff --git a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx index 27c86a8ff6c0..a7bad078b56e 100644 --- a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx +++ b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx @@ -160,7 +160,7 @@ test.skip('renders exit full screen button when in full screen mode and empty sc }); // unhandled promise rejection: https://github.com/elastic/kibana/issues/112699 -test.skip('DashboardViewport unmount unsubscribes', async (done) => { +test.skip('DashboardViewport unmount unsubscribes', (done) => { const { props } = getProps(); const component = mount( diff --git a/src/plugins/dashboard/public/application/lib/help_menu_util.ts b/src/plugins/dashboard/public/application/lib/help_menu_util.ts index 9407fa31f545..d93b2593386b 100644 --- a/src/plugins/dashboard/public/application/lib/help_menu_util.ts +++ b/src/plugins/dashboard/public/application/lib/help_menu_util.ts @@ -12,9 +12,8 @@ import { pluginServices } from '../../services/plugin_services'; export function addHelpMenuToAppChrome() { const { chrome: { setHelpExtension }, - documentationLinks: { kibanaGuideDocLink }, + documentationLinks: { dashboardDocLink }, } = pluginServices.getServices(); - setHelpExtension({ appName: i18n.translate('dashboard.helpMenu.appName', { defaultMessage: 'Dashboards', @@ -22,7 +21,7 @@ export function addHelpMenuToAppChrome() { links: [ { linkType: 'documentation', - href: `${kibanaGuideDocLink}`, + href: `${dashboardDocLink}`, }, ], }); diff --git a/src/plugins/dashboard/public/services/documentation_links/documentation_links.stub.ts b/src/plugins/dashboard/public/services/documentation_links/documentation_links.stub.ts index 3fab07396b4f..dfdd74bfe418 100644 --- a/src/plugins/dashboard/public/services/documentation_links/documentation_links.stub.ts +++ b/src/plugins/dashboard/public/services/documentation_links/documentation_links.stub.ts @@ -18,5 +18,6 @@ export const documentationLinksServiceFactory: DocumentationLinksServiceFactory return { indexPatternsDocLink: corePluginMock.docLinks.links.indexPatterns.introduction, kibanaGuideDocLink: corePluginMock.docLinks.links.kibana.guide, + dashboardDocLink: corePluginMock.docLinks.links.dashboard.guide, }; }; diff --git a/src/plugins/dashboard/public/services/documentation_links/documentation_links_service.ts b/src/plugins/dashboard/public/services/documentation_links/documentation_links_service.ts index fe00d376675f..eb65f639d57f 100644 --- a/src/plugins/dashboard/public/services/documentation_links/documentation_links_service.ts +++ b/src/plugins/dashboard/public/services/documentation_links/documentation_links_service.ts @@ -21,14 +21,16 @@ export const documentationLinksServiceFactory: DocumentationLinksServiceFactory const { docLinks: { links: { - kibana: { guide }, + kibana, indexPatterns: { introduction }, + dashboard, }, }, } = coreStart; return { indexPatternsDocLink: introduction, - kibanaGuideDocLink: guide, + kibanaGuideDocLink: kibana.guide, + dashboardDocLink: dashboard.guide, }; }; diff --git a/src/plugins/dashboard/public/services/documentation_links/types.ts b/src/plugins/dashboard/public/services/documentation_links/types.ts index ee7e52047165..d47fbee6ed77 100644 --- a/src/plugins/dashboard/public/services/documentation_links/types.ts +++ b/src/plugins/dashboard/public/services/documentation_links/types.ts @@ -11,4 +11,5 @@ import { CoreStart } from '@kbn/core/public'; export interface DashboardDocumentationLinksService { indexPatternsDocLink: CoreStart['docLinks']['links']['indexPatterns']['introduction']; kibanaGuideDocLink: CoreStart['docLinks']['links']['kibana']['guide']; + dashboardDocLink: CoreStart['docLinks']['links']['dashboard']['guide']; } diff --git a/src/plugins/data/common/search/aggs/agg_configs.test.ts b/src/plugins/data/common/search/aggs/agg_configs.test.ts index cd0495a3f78c..c3b7ffe937b3 100644 --- a/src/plugins/data/common/search/aggs/agg_configs.test.ts +++ b/src/plugins/data/common/search/aggs/agg_configs.test.ts @@ -16,6 +16,15 @@ import type { DataView } from '@kbn/data-views-plugin/common'; import { stubIndexPattern } from '../../stubs'; import { IEsSearchResponse } from '..'; +// Mute moment.tz warnings about not finding a mock timezone +jest.mock('../utils', () => { + const original = jest.requireActual('../utils'); + return { + ...original, + getUserTimeZone: jest.fn(() => 'US/Pacific'), + }; +}); + describe('AggConfigs', () => { const indexPattern: DataView = stubIndexPattern; let typesRegistry: AggTypesRegistryStart; @@ -563,6 +572,82 @@ describe('AggConfigs', () => { '1-bucket>_count' ); }); + + it('prepends a sampling agg whenever sampling is enabled', () => { + const configStates = [ + { + enabled: true, + id: '1', + type: 'avg_bucket', + schema: 'metric', + params: { + customBucket: { + id: '1-bucket', + type: 'date_histogram', + schema: 'bucketAgg', + params: { + field: '@timestamp', + interval: '10s', + }, + }, + customMetric: { + id: '1-metric', + type: 'count', + schema: 'metricAgg', + params: {}, + }, + }, + }, + { + enabled: true, + id: '2', + type: 'terms', + schema: 'bucket', + params: { + field: 'clientip', + }, + }, + { + enabled: true, + id: '3', + type: 'terms', + schema: 'bucket', + params: { + field: 'machine.os.raw', + }, + }, + ]; + + const ac = new AggConfigs( + indexPattern, + configStates, + { typesRegistry, hierarchical: true, probability: 0.5 }, + jest.fn() + ); + const topLevelDsl = ac.toDsl(); + + expect(Object.keys(topLevelDsl)).toContain('sampling'); + expect(Object.keys(topLevelDsl.sampling)).toEqual(['random_sampler', 'aggs']); + expect(Object.keys(topLevelDsl.sampling.aggs)).toContain('2'); + expect(Object.keys(topLevelDsl.sampling.aggs['2'].aggs)).toEqual(['1', '3', '1-bucket']); + }); + + it('should not prepend a sampling agg when no nested agg is avaialble', () => { + const ac = new AggConfigs( + indexPattern, + [ + { + enabled: true, + type: 'count', + schema: 'metric', + }, + ], + { typesRegistry, probability: 0.5 }, + jest.fn() + ); + const topLevelDsl = ac.toDsl(); + expect(Object.keys(topLevelDsl)).not.toContain('sampling'); + }); }); describe('#postFlightTransform', () => { @@ -854,4 +939,74 @@ describe('AggConfigs', () => { `); }); }); + + describe('isSamplingEnabled', () => { + it('should return false if probability is 1', () => { + const ac = new AggConfigs( + indexPattern, + [{ enabled: true, type: 'avg', schema: 'metric', params: { field: 'bytes' } }], + { typesRegistry, probability: 1 }, + jest.fn() + ); + + expect(ac.isSamplingEnabled()).toBeFalsy(); + }); + + it('should return true if probability is less than 1', () => { + const ac = new AggConfigs( + indexPattern, + [{ enabled: true, type: 'avg', schema: 'metric', params: { field: 'bytes' } }], + { typesRegistry, probability: 0.1 }, + jest.fn() + ); + + expect(ac.isSamplingEnabled()).toBeTruthy(); + }); + + it('should return false when all aggs have hasNoDsl flag enabled', () => { + const ac = new AggConfigs( + indexPattern, + [ + { + enabled: true, + type: 'count', + schema: 'metric', + }, + ], + { typesRegistry, probability: 1 }, + jest.fn() + ); + + expect(ac.isSamplingEnabled()).toBeFalsy(); + }); + + it('should return false when no nested aggs are avaialble', () => { + const ac = new AggConfigs( + indexPattern, + [{ enabled: false, type: 'avg', schema: 'metric', params: { field: 'bytes' } }], + { typesRegistry, probability: 1 }, + jest.fn() + ); + + expect(ac.isSamplingEnabled()).toBeFalsy(); + }); + + it('should return true if at least one nested agg is available and probability < 1', () => { + const ac = new AggConfigs( + indexPattern, + [ + { + enabled: true, + type: 'count', + schema: 'metric', + }, + { enabled: true, type: 'avg', schema: 'metric', params: { field: 'bytes' } }, + ], + { typesRegistry, probability: 0.1 }, + jest.fn() + ); + + expect(ac.isSamplingEnabled()).toBeTruthy(); + }); + }); }); diff --git a/src/plugins/data/common/search/aggs/agg_configs.ts b/src/plugins/data/common/search/aggs/agg_configs.ts index c61ca69e0c6d..7cab863fba11 100644 --- a/src/plugins/data/common/search/aggs/agg_configs.ts +++ b/src/plugins/data/common/search/aggs/agg_configs.ts @@ -26,6 +26,7 @@ import { AggTypesDependencies, GetConfigFn, getUserTimeZone } from '../..'; import { getTime, calculateBounds } from '../..'; import type { IBucketAggConfig } from './buckets'; import { insertTimeShiftSplit, mergeTimeShifts } from './utils/time_splits'; +import { createSamplerAgg, isSamplingEnabled } from './utils/sampler'; function removeParentAggs(obj: any) { for (const prop in obj) { @@ -55,6 +56,8 @@ export interface AggConfigsOptions { hierarchical?: boolean; aggExecutionContext?: AggTypesDependencies['aggExecutionContext']; partialRows?: boolean; + probability?: number; + samplerSeed?: number; } export type CreateAggConfigParams = Assign; @@ -107,6 +110,17 @@ export class AggConfigs { return this.opts.partialRows ?? false; } + public get samplerConfig() { + return { probability: this.opts.probability ?? 1, seed: this.opts.samplerSeed }; + } + + isSamplingEnabled() { + return ( + isSamplingEnabled(this.opts.probability) && + this.getRequestAggs().filter((agg) => !agg.type.hasNoDsl).length > 0 + ); + } + setTimeFields(timeFields: string[] | undefined) { this.timeFields = timeFields; } @@ -225,7 +239,7 @@ export class AggConfigs { } toDsl(): Record { - const dslTopLvl = {}; + const dslTopLvl: Record = {}; let dslLvlCursor: Record; let nestedMetrics: Array<{ config: AggConfig; dsl: Record }> | []; @@ -254,10 +268,21 @@ export class AggConfigs { (config) => 'splitForTimeShift' in config.type && config.type.splitForTimeShift(config, this) ); + if (this.isSamplingEnabled()) { + dslTopLvl.sampling = createSamplerAgg({ + probability: this.opts.probability ?? 1, + seed: this.opts.samplerSeed, + }); + } + requestAggs.forEach((config: AggConfig, i: number, list) => { if (!dslLvlCursor) { // start at the top level dslLvlCursor = dslTopLvl; + // when sampling jump directly to the aggs + if (this.isSamplingEnabled()) { + dslLvlCursor = dslLvlCursor.sampling.aggs; + } } else { const prevConfig: AggConfig = list[i - 1]; const prevDsl = dslLvlCursor[prevConfig.id]; @@ -452,7 +477,12 @@ export class AggConfigs { doc_count: response.rawResponse.hits?.total as estypes.AggregationsAggregate, }; } - const aggCursor = transformedRawResponse.aggregations!; + const aggCursor = this.isSamplingEnabled() + ? (transformedRawResponse.aggregations!.sampling! as Record< + string, + estypes.AggregationsAggregate + >) + : transformedRawResponse.aggregations!; mergeTimeShifts(this, aggCursor); return { @@ -531,6 +561,8 @@ export class AggConfigs { metricsAtAllLevels: this.hierarchical, partialRows: this.partialRows, aggs: this.aggs.map((agg) => buildExpression(agg.toExpressionAst())), + probability: this.opts.probability, + samplerSeed: this.opts.samplerSeed, }), ]).toAst(); } diff --git a/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.test.ts b/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.test.ts index 6ce588b98fa9..be2e969f279b 100644 --- a/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.test.ts +++ b/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.test.ts @@ -18,6 +18,8 @@ import { AggConfigs, CreateAggConfigParams } from '../agg_configs'; import { BUCKET_TYPES } from './bucket_agg_types'; import { IBucketAggConfig } from './bucket_agg_type'; import { mockAggTypesRegistry } from '../test_helpers'; +import { estypes } from '@elastic/elasticsearch'; +import { isSamplingEnabled } from '../utils/sampler'; const indexPattern = { id: '1234', @@ -281,71 +283,63 @@ const nestedOtherResponse = { describe('Terms Agg Other bucket helper', () => { const typesRegistry = mockAggTypesRegistry(); - const getAggConfigs = (aggs: CreateAggConfigParams[] = []) => { - return new AggConfigs(indexPattern, [...aggs], { typesRegistry }, jest.fn()); - }; - - describe('buildOtherBucketAgg', () => { - test('returns a function', () => { - const aggConfigs = getAggConfigs(singleTerm.aggs); - const agg = buildOtherBucketAgg( - aggConfigs, - aggConfigs.aggs[0] as IBucketAggConfig, - singleTermResponse - ); - expect(typeof agg).toBe('function'); - }); - - test('correctly builds query with single terms agg', () => { - const aggConfigs = getAggConfigs(singleTerm.aggs); - const agg = buildOtherBucketAgg( - aggConfigs, - aggConfigs.aggs[0] as IBucketAggConfig, - singleTermResponse - ); - const expectedResponse = { - aggs: undefined, - filters: { - filters: { - '': { - bool: { - must: [], - filter: [{ exists: { field: 'machine.os.raw' } }], - should: [], - must_not: [ - { match_phrase: { 'machine.os.raw': 'ios' } }, - { match_phrase: { 'machine.os.raw': 'win xp' } }, - ], - }, - }, + for (const probability of [1, 0.5, undefined]) { + function getTitlePostfix() { + if (!isSamplingEnabled(probability)) { + return ''; + } + return ` - with sampling (probability = ${probability})`; + } + function enrichResponseWithSampling(response: any) { + if (!isSamplingEnabled(probability)) { + return response; + } + return { + ...response, + aggregations: { + sampling: { + ...response.aggregations, }, }, }; - expect(agg).toBeDefined(); - if (agg) { - expect(agg()['other-filter']).toEqual(expectedResponse); - } - }); + } - test('correctly builds query for nested terms agg', () => { - const aggConfigs = getAggConfigs(nestedTerm.aggs); - const agg = buildOtherBucketAgg( - aggConfigs, - aggConfigs.aggs[1] as IBucketAggConfig, - nestedTermResponse - ); - const expectedResponse = { - 'other-filter': { + function getAggConfigs(aggs: CreateAggConfigParams[] = []) { + return new AggConfigs(indexPattern, [...aggs], { typesRegistry, probability }, jest.fn()); + } + + function getTopAggregations(updatedResponse: estypes.SearchResponse) { + return !isSamplingEnabled(probability) + ? updatedResponse.aggregations! + : (updatedResponse.aggregations!.sampling as Record); + } + + describe(`buildOtherBucketAgg${getTitlePostfix()}`, () => { + test('returns a function', () => { + const aggConfigs = getAggConfigs(singleTerm.aggs); + const agg = buildOtherBucketAgg( + aggConfigs, + aggConfigs.aggs[0] as IBucketAggConfig, + enrichResponseWithSampling(singleTermResponse) + ); + expect(typeof agg).toBe('function'); + }); + + test('correctly builds query with single terms agg', () => { + const aggConfigs = getAggConfigs(singleTerm.aggs); + const agg = buildOtherBucketAgg( + aggConfigs, + aggConfigs.aggs[0] as IBucketAggConfig, + enrichResponseWithSampling(singleTermResponse) + ); + const expectedResponse = { aggs: undefined, filters: { filters: { - [`${SEP}IN-with-dash`]: { + '': { bool: { must: [], - filter: [ - { match_phrase: { 'geo.src': 'IN-with-dash' } }, - { exists: { field: 'machine.os.raw' } }, - ], + filter: [{ exists: { field: 'machine.os.raw' } }], should: [], must_not: [ { match_phrase: { 'machine.os.raw': 'ios' } }, @@ -353,272 +347,322 @@ describe('Terms Agg Other bucket helper', () => { ], }, }, - [`${SEP}US-with-dash`]: { - bool: { - must: [], - filter: [ - { match_phrase: { 'geo.src': 'US-with-dash' } }, - { exists: { field: 'machine.os.raw' } }, - ], - should: [], - must_not: [ - { match_phrase: { 'machine.os.raw': 'ios' } }, - { match_phrase: { 'machine.os.raw': 'win xp' } }, - ], + }, + }, + }; + expect(agg).toBeDefined(); + if (agg) { + const resp = agg(); + const topAgg = !isSamplingEnabled(probability) ? resp : resp.sampling!.aggs; + expect(topAgg['other-filter']).toEqual(expectedResponse); + } + }); + + test('correctly builds query for nested terms agg', () => { + const aggConfigs = getAggConfigs(nestedTerm.aggs); + const agg = buildOtherBucketAgg( + aggConfigs, + aggConfigs.aggs[1] as IBucketAggConfig, + enrichResponseWithSampling(nestedTermResponse) + ); + const expectedResponse = { + 'other-filter': { + aggs: undefined, + filters: { + filters: { + [`${SEP}IN-with-dash`]: { + bool: { + must: [], + filter: [ + { match_phrase: { 'geo.src': 'IN-with-dash' } }, + { exists: { field: 'machine.os.raw' } }, + ], + should: [], + must_not: [ + { match_phrase: { 'machine.os.raw': 'ios' } }, + { match_phrase: { 'machine.os.raw': 'win xp' } }, + ], + }, + }, + [`${SEP}US-with-dash`]: { + bool: { + must: [], + filter: [ + { match_phrase: { 'geo.src': 'US-with-dash' } }, + { exists: { field: 'machine.os.raw' } }, + ], + should: [], + must_not: [ + { match_phrase: { 'machine.os.raw': 'ios' } }, + { match_phrase: { 'machine.os.raw': 'win xp' } }, + ], + }, }, }, }, }, - }, - }; - expect(agg).toBeDefined(); - if (agg) { - expect(agg()).toEqual(expectedResponse); - } - }); + }; + expect(agg).toBeDefined(); + if (agg) { + const resp = agg(); + const topAgg = !isSamplingEnabled(probability) ? resp : resp.sampling!.aggs; + // console.log({ probability }, JSON.stringify(topAgg, null, 2)); + expect(topAgg).toEqual(expectedResponse); + } + }); - test('correctly builds query for nested terms agg with one disabled', () => { - const oneDisabledNestedTerms = { - aggs: [ - { - id: '2', - type: BUCKET_TYPES.TERMS, - enabled: false, - params: { - field: { - name: 'machine.os.raw', - indexPattern, - filterable: true, + test('correctly builds query for nested terms agg with one disabled', () => { + const oneDisabledNestedTerms = { + aggs: [ + { + id: '2', + type: BUCKET_TYPES.TERMS, + enabled: false, + params: { + field: { + name: 'machine.os.raw', + indexPattern, + filterable: true, + }, + size: 2, + otherBucket: false, + missingBucket: true, }, - size: 2, - otherBucket: false, - missingBucket: true, }, - }, - { - id: '1', - type: BUCKET_TYPES.TERMS, - params: { - field: { - name: 'geo.src', - indexPattern, - filterable: true, + { + id: '1', + type: BUCKET_TYPES.TERMS, + params: { + field: { + name: 'geo.src', + indexPattern, + filterable: true, + }, + size: 2, + otherBucket: true, + missingBucket: false, }, - size: 2, - otherBucket: true, - missingBucket: false, }, - }, - ], - }; - const aggConfigs = getAggConfigs(oneDisabledNestedTerms.aggs); - const agg = buildOtherBucketAgg( - aggConfigs, - aggConfigs.aggs[1] as IBucketAggConfig, - singleTermResponse - ); - const expectedResponse = { - 'other-filter': { - aggs: undefined, - filters: { + ], + }; + const aggConfigs = getAggConfigs(oneDisabledNestedTerms.aggs); + const agg = buildOtherBucketAgg( + aggConfigs, + aggConfigs.aggs[1] as IBucketAggConfig, + enrichResponseWithSampling(singleTermResponse) + ); + const expectedResponse = { + 'other-filter': { + aggs: undefined, filters: { - '': { - bool: { - filter: [ - { - exists: { - field: 'geo.src', + filters: { + '': { + bool: { + filter: [ + { + exists: { + field: 'geo.src', + }, }, - }, - ], - must: [], - must_not: [ - { - match_phrase: { - 'geo.src': 'ios', + ], + must: [], + must_not: [ + { + match_phrase: { + 'geo.src': 'ios', + }, }, - }, - { - match_phrase: { - 'geo.src': 'win xp', + { + match_phrase: { + 'geo.src': 'win xp', + }, }, - }, - ], - should: [], + ], + should: [], + }, }, }, }, }, - }, - }; - expect(agg).toBeDefined(); - if (agg) { - expect(agg()).toEqual(expectedResponse); - } - }); + }; + expect(agg).toBeDefined(); + if (agg) { + const resp = agg(); + const topAgg = !isSamplingEnabled(probability) ? resp : resp.sampling!.aggs; + expect(topAgg).toEqual(expectedResponse); + } + }); - test('does not build query if sum_other_doc_count is 0 (exhaustive terms)', () => { - const aggConfigs = getAggConfigs(nestedTerm.aggs); - expect( - buildOtherBucketAgg( + test('does not build query if sum_other_doc_count is 0 (exhaustive terms)', () => { + const aggConfigs = getAggConfigs(nestedTerm.aggs); + expect( + buildOtherBucketAgg( + aggConfigs, + aggConfigs.aggs[1] as IBucketAggConfig, + enrichResponseWithSampling(exhaustiveNestedTermResponse) + ) + ).toBeFalsy(); + }); + + test('excludes exists filter for scripted fields', () => { + const aggConfigs = getAggConfigs(nestedTerm.aggs); + aggConfigs.aggs[1].params.field = { + ...aggConfigs.aggs[1].params.field, + scripted: true, + }; + const agg = buildOtherBucketAgg( aggConfigs, aggConfigs.aggs[1] as IBucketAggConfig, - exhaustiveNestedTermResponse - ) - ).toBeFalsy(); - }); - - test('excludes exists filter for scripted fields', () => { - const aggConfigs = getAggConfigs(nestedTerm.aggs); - aggConfigs.aggs[1].params.field.scripted = true; - const agg = buildOtherBucketAgg( - aggConfigs, - aggConfigs.aggs[1] as IBucketAggConfig, - nestedTermResponse - ); - const expectedResponse = { - 'other-filter': { - aggs: undefined, - filters: { + enrichResponseWithSampling(nestedTermResponse) + ); + const expectedResponse = { + 'other-filter': { + aggs: undefined, filters: { - [`${SEP}IN-with-dash`]: { - bool: { - must: [], - filter: [{ match_phrase: { 'geo.src': 'IN-with-dash' } }], - should: [], - must_not: [ - { - script: { + filters: { + [`${SEP}IN-with-dash`]: { + bool: { + must: [], + filter: [{ match_phrase: { 'geo.src': 'IN-with-dash' } }], + should: [], + must_not: [ + { script: { - lang: undefined, - params: { value: 'ios' }, - source: '(undefined) == value', + script: { + lang: undefined, + params: { value: 'ios' }, + source: '(undefined) == value', + }, }, }, - }, - { - script: { + { script: { - lang: undefined, - params: { value: 'win xp' }, - source: '(undefined) == value', + script: { + lang: undefined, + params: { value: 'win xp' }, + source: '(undefined) == value', + }, }, }, - }, - ], + ], + }, }, - }, - [`${SEP}US-with-dash`]: { - bool: { - must: [], - filter: [{ match_phrase: { 'geo.src': 'US-with-dash' } }], - should: [], - must_not: [ - { - script: { + [`${SEP}US-with-dash`]: { + bool: { + must: [], + filter: [{ match_phrase: { 'geo.src': 'US-with-dash' } }], + should: [], + must_not: [ + { script: { - lang: undefined, - params: { value: 'ios' }, - source: '(undefined) == value', + script: { + lang: undefined, + params: { value: 'ios' }, + source: '(undefined) == value', + }, }, }, - }, - { - script: { + { script: { - lang: undefined, - params: { value: 'win xp' }, - source: '(undefined) == value', + script: { + lang: undefined, + params: { value: 'win xp' }, + source: '(undefined) == value', + }, }, }, - }, - ], + ], + }, }, }, }, }, - }, - }; - expect(agg).toBeDefined(); - if (agg) { - expect(agg()).toEqual(expectedResponse); - } - }); + }; + expect(agg).toBeDefined(); + if (agg) { + const resp = agg(); + const topAgg = !isSamplingEnabled(probability) ? resp : resp.sampling!.aggs; + expect(topAgg).toEqual(expectedResponse); + } + }); - test('returns false when nested terms agg has no buckets', () => { - const aggConfigs = getAggConfigs(nestedTerm.aggs); - const agg = buildOtherBucketAgg( - aggConfigs, - aggConfigs.aggs[1] as IBucketAggConfig, - nestedTermResponseNoResults - ); + test('returns false when nested terms agg has no buckets', () => { + const aggConfigs = getAggConfigs(nestedTerm.aggs); + const agg = buildOtherBucketAgg( + aggConfigs, + aggConfigs.aggs[1] as IBucketAggConfig, + enrichResponseWithSampling(nestedTermResponseNoResults) + ); - expect(agg).toEqual(false); + expect(agg).toEqual(false); + }); }); - }); - describe('mergeOtherBucketAggResponse', () => { - test('correctly merges other bucket with single terms agg', () => { - const aggConfigs = getAggConfigs(singleTerm.aggs); - const otherAggConfig = buildOtherBucketAgg( - aggConfigs, - aggConfigs.aggs[0] as IBucketAggConfig, - singleTermResponse - ); - - expect(otherAggConfig).toBeDefined(); - if (otherAggConfig) { - const mergedResponse = mergeOtherBucketAggResponse( + describe(`mergeOtherBucketAggResponse${getTitlePostfix()}`, () => { + test('correctly merges other bucket with single terms agg', () => { + const aggConfigs = getAggConfigs(singleTerm.aggs); + const otherAggConfig = buildOtherBucketAgg( aggConfigs, - singleTermResponse, - singleOtherResponse, aggConfigs.aggs[0] as IBucketAggConfig, - otherAggConfig(), - constructSingleTermOtherFilter + enrichResponseWithSampling(singleTermResponse) ); - expect((mergedResponse!.aggregations!['1'] as any).buckets[3].key).toEqual('__other__'); - } - }); - test('correctly merges other bucket with nested terms agg', () => { - const aggConfigs = getAggConfigs(nestedTerm.aggs); - const otherAggConfig = buildOtherBucketAgg( - aggConfigs, - aggConfigs.aggs[1] as IBucketAggConfig, - nestedTermResponse - ); + expect(otherAggConfig).toBeDefined(); + if (otherAggConfig) { + const mergedResponse = mergeOtherBucketAggResponse( + aggConfigs, + enrichResponseWithSampling(singleTermResponse), + enrichResponseWithSampling(singleOtherResponse), + aggConfigs.aggs[0] as IBucketAggConfig, + otherAggConfig(), + constructSingleTermOtherFilter + ); - expect(otherAggConfig).toBeDefined(); - if (otherAggConfig) { - const mergedResponse = mergeOtherBucketAggResponse( + const topAgg = getTopAggregations(mergedResponse); + expect((topAgg['1'] as any).buckets[3].key).toEqual('__other__'); + } + }); + + test('correctly merges other bucket with nested terms agg', () => { + const aggConfigs = getAggConfigs(nestedTerm.aggs); + const otherAggConfig = buildOtherBucketAgg( aggConfigs, - nestedTermResponse, - nestedOtherResponse, aggConfigs.aggs[1] as IBucketAggConfig, - otherAggConfig(), - constructSingleTermOtherFilter + enrichResponseWithSampling(nestedTermResponse) ); - expect((mergedResponse!.aggregations!['1'] as any).buckets[1]['2'].buckets[3].key).toEqual( - '__other__' - ); - } + expect(otherAggConfig).toBeDefined(); + if (otherAggConfig) { + const mergedResponse = mergeOtherBucketAggResponse( + aggConfigs, + enrichResponseWithSampling(nestedTermResponse), + enrichResponseWithSampling(nestedOtherResponse), + aggConfigs.aggs[1] as IBucketAggConfig, + otherAggConfig(), + constructSingleTermOtherFilter + ); + + const topAgg = getTopAggregations(mergedResponse); + expect((topAgg['1'] as any).buckets[1]['2'].buckets[3].key).toEqual('__other__'); + } + }); }); - }); - describe('updateMissingBucket', () => { - test('correctly updates missing bucket key', () => { - const aggConfigs = getAggConfigs(nestedTerm.aggs); - const updatedResponse = updateMissingBucket( - singleTermResponse, - aggConfigs, - aggConfigs.aggs[0] as IBucketAggConfig - ); - expect( - (updatedResponse!.aggregations!['1'] as any).buckets.find( - (bucket: Record) => bucket.key === '__missing__' - ) - ).toBeDefined(); + describe(`updateMissingBucket${getTitlePostfix()}`, () => { + test('correctly updates missing bucket key', () => { + const aggConfigs = getAggConfigs(nestedTerm.aggs); + const updatedResponse = updateMissingBucket( + enrichResponseWithSampling(singleTermResponse), + aggConfigs, + aggConfigs.aggs[0] as IBucketAggConfig + ); + const topAgg = getTopAggregations(updatedResponse); + expect( + (topAgg['1'] as any).buckets.find( + (bucket: Record) => bucket.key === '__missing__' + ) + ).toBeDefined(); + }); }); - }); + } }); diff --git a/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.ts b/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.ts index f695dc1b1d39..68c64f67ef27 100644 --- a/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.ts +++ b/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.ts @@ -20,6 +20,7 @@ import { AggGroupNames } from '../agg_groups'; import { IAggConfigs } from '../agg_configs'; import { IAggType } from '../agg_type'; import { IAggConfig } from '../agg_config'; +import { createSamplerAgg } from '../utils/sampler'; export const OTHER_BUCKET_SEPARATOR = '╰┄►'; @@ -128,6 +129,28 @@ const getOtherAggTerms = (requestAgg: Record, key: string, otherAgg .map((filter: Record) => filter.match_phrase[otherAgg.params.field.name]); }; +/** + * Helper function to handle sampling case and get the correct cursor agg from a request object + */ +const getCorrectAggCursorFromRequest = ( + requestAgg: Record, + aggConfigs: IAggConfigs +) => { + return aggConfigs.isSamplingEnabled() ? requestAgg.sampling.aggs : requestAgg; +}; + +/** + * Helper function to handle sampling case and get the correct cursor agg from a response object + */ +const getCorrectAggregationsCursorFromResponse = ( + response: estypes.SearchResponse, + aggConfigs: IAggConfigs +) => { + return aggConfigs.isSamplingEnabled() + ? (response.aggregations?.sampling as Record) + : response.aggregations; +}; + export const buildOtherBucketAgg = ( aggConfigs: IAggConfigs, aggWithOtherBucket: IAggConfig, @@ -234,7 +257,13 @@ export const buildOtherBucketAgg = ( bool: buildQueryFromFilters(filters, indexPattern), }; }; - walkBucketTree(0, response.aggregations, bucketAggs[0].id, [], ''); + walkBucketTree( + 0, + getCorrectAggregationsCursorFromResponse(response, aggConfigs), + bucketAggs[0].id, + [], + '' + ); // bail if there were no bucket results if (noAggBucketResults || exhaustiveBuckets) { @@ -242,6 +271,14 @@ export const buildOtherBucketAgg = ( } return () => { + if (aggConfigs.isSamplingEnabled()) { + return { + sampling: { + ...createSamplerAgg(aggConfigs.samplerConfig), + aggs: { 'other-filter': resultAgg }, + }, + }; + } return { 'other-filter': resultAgg, }; @@ -257,16 +294,27 @@ export const mergeOtherBucketAggResponse = ( otherFilterBuilder: (requestAgg: Record, key: string, otherAgg: IAggConfig) => Filter ): estypes.SearchResponse => { const updatedResponse = cloneDeep(response); - each(otherResponse.aggregations['other-filter'].buckets, (bucket, key) => { + const aggregationsRoot = getCorrectAggregationsCursorFromResponse(otherResponse, aggsConfig); + const updatedAggregationsRoot = getCorrectAggregationsCursorFromResponse( + updatedResponse, + aggsConfig + ); + const buckets = + 'buckets' in aggregationsRoot!['other-filter'] ? aggregationsRoot!['other-filter'].buckets : {}; + each(buckets, (bucket, key) => { if (!bucket.doc_count || key === undefined) return; const bucketKey = key.replace(new RegExp(`^${OTHER_BUCKET_SEPARATOR}`), ''); const aggResultBuckets = getAggResultBuckets( aggsConfig, - updatedResponse.aggregations, + updatedAggregationsRoot, otherAgg, bucketKey ); - const otherFilter = otherFilterBuilder(requestAgg, key, otherAgg); + const otherFilter = otherFilterBuilder( + getCorrectAggCursorFromRequest(requestAgg, aggsConfig), + key, + otherAgg + ); bucket.filters = [otherFilter]; bucket.key = '__other__'; @@ -290,7 +338,10 @@ export const updateMissingBucket = ( agg: IAggConfig ) => { const updatedResponse = cloneDeep(response); - const aggResultBuckets = getAggConfigResultMissingBuckets(updatedResponse.aggregations, agg.id); + const aggResultBuckets = getAggConfigResultMissingBuckets( + getCorrectAggregationsCursorFromResponse(updatedResponse, aggConfigs), + agg.id + ); aggResultBuckets.forEach((bucket) => { bucket.key = '__missing__'; }); diff --git a/src/plugins/data/common/search/aggs/utils/sampler.ts b/src/plugins/data/common/search/aggs/utils/sampler.ts new file mode 100644 index 000000000000..5a6fde63b0a2 --- /dev/null +++ b/src/plugins/data/common/search/aggs/utils/sampler.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export function createSamplerAgg({ + type = 'random_sampler', + probability, + seed, +}: { + type?: string; + probability: number; + seed?: number; +}) { + return { + [type]: { + probability, + seed, + }, + aggs: {}, + }; +} + +export function isSamplingEnabled(probability: number | undefined) { + return probability != null && probability !== 1; +} diff --git a/src/plugins/data/common/search/aggs/utils/time_splits.ts b/src/plugins/data/common/search/aggs/utils/time_splits.ts index c2fe8aaca0fb..b1262683446f 100644 --- a/src/plugins/data/common/search/aggs/utils/time_splits.ts +++ b/src/plugins/data/common/search/aggs/utils/time_splits.ts @@ -427,11 +427,11 @@ export function insertTimeShiftSplit( const timeRange = aggConfigs.timeRange; const filters: Record = {}; const timeField = aggConfigs.timeFields[0]; + const timeFilter = getTime(aggConfigs.indexPattern, timeRange, { + fieldName: timeField, + forceNow: aggConfigs.forceNow, + }) as RangeFilter; Object.entries(timeShifts).forEach(([key, shift]) => { - const timeFilter = getTime(aggConfigs.indexPattern, timeRange, { - fieldName: timeField, - forceNow: aggConfigs.forceNow, - }) as RangeFilter; if (timeFilter) { filters[key] = { range: { diff --git a/src/plugins/data/common/search/expressions/esaggs/esaggs_fn.ts b/src/plugins/data/common/search/expressions/esaggs/esaggs_fn.ts index c8296b3c0655..f0b55de8ffef 100644 --- a/src/plugins/data/common/search/expressions/esaggs/esaggs_fn.ts +++ b/src/plugins/data/common/search/expressions/esaggs/esaggs_fn.ts @@ -32,6 +32,8 @@ interface Arguments { metricsAtAllLevels?: boolean; partialRows?: boolean; timeFields?: string[]; + probability?: number; + samplerSeed?: number; } export type EsaggsExpressionFunctionDefinition = ExpressionFunctionDefinition< @@ -94,6 +96,21 @@ export const getEsaggsMeta: () => Omit defaultMessage: 'Provide time fields to get the resolved time ranges for the query', }), }, + probability: { + types: ['number'], + default: 1, + help: i18n.translate('data.search.functions.esaggs.probability.help', { + defaultMessage: + 'The probability that a document will be included in the aggregated data. Uses random sampler.', + }), + }, + samplerSeed: { + types: ['number'], + help: i18n.translate('data.search.functions.esaggs.samplerSeed.help', { + defaultMessage: + 'The seed to generate the random sampling of documents. Uses random sampler.', + }), + }, }, }); diff --git a/src/plugins/data/common/search/tabify/tabify.test.ts b/src/plugins/data/common/search/tabify/tabify.test.ts index d7d983fabfda..90ef53623c29 100644 --- a/src/plugins/data/common/search/tabify/tabify.test.ts +++ b/src/plugins/data/common/search/tabify/tabify.test.ts @@ -11,184 +11,217 @@ import type { DataView } from '@kbn/data-views-plugin/common'; import { AggConfigs, BucketAggParam, IAggConfig, IAggConfigs } from '../aggs'; import { mockAggTypesRegistry } from '../aggs/test_helpers'; import { metricOnly, threeTermBuckets } from './fixtures/fake_hierarchical_data'; +import { isSamplingEnabled } from '../aggs/utils/sampler'; describe('tabifyAggResponse Integration', () => { const typesRegistry = mockAggTypesRegistry(); - const createAggConfigs = (aggs: IAggConfig[] = []) => { - const field = { - name: '@timestamp', - }; - - const indexPattern = { - id: '1234', - title: 'logstash-*', - fields: { - getByName: () => field, - filter: () => [field], - }, - getFormatterForField: () => ({ - toJSON: () => '{}', - }), - } as unknown as DataView; - - return new AggConfigs(indexPattern, aggs, { typesRegistry }, jest.fn()); - }; - - const mockAggConfig = (agg: any): IAggConfig => agg as unknown as IAggConfig; - - test('transforms a simple response properly', () => { - const aggConfigs = createAggConfigs([{ type: 'count' } as any]); - - const resp = tabifyAggResponse(aggConfigs, metricOnly, { - metricsAtAllLevels: true, - }); - - expect(resp).toHaveProperty('rows'); - expect(resp).toHaveProperty('columns'); + for (const probability of [1, 0.5, undefined]) { + function getTitlePostfix() { + if (!isSamplingEnabled(probability)) { + return ''; + } + return ` - with sampling (probability = ${probability})`; + } - expect(resp.rows).toHaveLength(1); - expect(resp.columns).toHaveLength(1); + function enrichResponseWithSampling(response: any) { + if (!isSamplingEnabled(probability)) { + return response; + } + return { + ...response, + aggregations: { + sampling: { + ...response.aggregations, + }, + }, + }; + } - expect(resp.rows[0]).toEqual({ 'col-0-1': 1000 }); - expect(resp.columns[0]).toHaveProperty('name', aggConfigs.aggs[0].makeLabel()); + const createAggConfigs = (aggs: IAggConfig[] = []) => { + const field = { + name: '@timestamp', + }; + + const indexPattern = { + id: '1234', + title: 'logstash-*', + fields: { + getByName: () => field, + filter: () => [field], + }, + getFormatterForField: () => ({ + toJSON: () => '{}', + }), + } as unknown as DataView; + + return new AggConfigs(indexPattern, aggs, { typesRegistry, probability }, jest.fn()); + }; - expect(resp).toHaveProperty('meta.type', 'esaggs'); - expect(resp).toHaveProperty('meta.source', '1234'); - expect(resp).toHaveProperty('meta.statistics.totalCount', 1000); - }); + const mockAggConfig = (agg: any): IAggConfig => agg as unknown as IAggConfig; - describe('scaleMetricValues performance check', () => { - beforeAll(() => { - typesRegistry.get('count').params.push({ - name: 'scaleMetricValues', - default: false, - write: () => {}, - advanced: true, - } as any as BucketAggParam); - }); - test('does not call write if scaleMetricValues is not set', () => { + test(`transforms a simple response properly${getTitlePostfix()}`, () => { const aggConfigs = createAggConfigs([{ type: 'count' } as any]); - const writeMock = jest.fn(); - aggConfigs.getRequestAggs()[0].write = writeMock; - - tabifyAggResponse(aggConfigs, metricOnly, { + const resp = tabifyAggResponse(aggConfigs, enrichResponseWithSampling(metricOnly), { metricsAtAllLevels: true, }); - expect(writeMock).not.toHaveBeenCalled(); - }); - test('does call write if scaleMetricValues is set', () => { - const aggConfigs = createAggConfigs([ - { type: 'count', params: { scaleMetricValues: true } } as any, - ]); + expect(resp).toHaveProperty('rows'); + expect(resp).toHaveProperty('columns'); - const writeMock = jest.fn(() => ({})); - aggConfigs.getRequestAggs()[0].write = writeMock; - - tabifyAggResponse(aggConfigs, metricOnly, { - metricsAtAllLevels: true, - }); - expect(writeMock).toHaveBeenCalled(); - }); - }); - - describe('transforms a complex response', () => { - let esResp: typeof threeTermBuckets; - let aggConfigs: IAggConfigs; - let avg: IAggConfig; - let ext: IAggConfig; - let src: IAggConfig; - let os: IAggConfig; - - beforeEach(() => { - aggConfigs = createAggConfigs([ - mockAggConfig({ type: 'avg', schema: 'metric', params: { field: '@timestamp' } }), - mockAggConfig({ type: 'terms', schema: 'split', params: { field: '@timestamp' } }), - mockAggConfig({ type: 'terms', schema: 'segment', params: { field: '@timestamp' } }), - mockAggConfig({ type: 'terms', schema: 'segment', params: { field: '@timestamp' } }), - ]); - - [avg, ext, src, os] = aggConfigs.aggs; - - esResp = threeTermBuckets; - esResp.aggregations.agg_2.buckets[1].agg_3.buckets[0].agg_4.buckets = []; - }); + expect(resp.rows).toHaveLength(1); + expect(resp.columns).toHaveLength(1); - // check that the columns of a table are formed properly - function expectColumns(table: ReturnType, aggs: IAggConfig[]) { - expect(table.columns).toHaveLength(aggs.length); + expect(resp.rows[0]).toEqual({ 'col-0-1': 1000 }); + expect(resp.columns[0]).toHaveProperty('name', aggConfigs.aggs[0].makeLabel()); - aggs.forEach((agg, i) => { - expect(table.columns[i]).toHaveProperty('name', agg.makeLabel()); - }); - } + expect(resp).toHaveProperty('meta.type', 'esaggs'); + expect(resp).toHaveProperty('meta.source', '1234'); + expect(resp).toHaveProperty('meta.statistics.totalCount', 1000); + }); - // check that a row has expected values - function expectRow( - row: Record, - asserts: Array<(val: string | number) => void> - ) { - expect(typeof row).toBe('object'); - - asserts.forEach((assert, i: number) => { - if (row[`col-${i}`]) { - assert(row[`col-${i}`]); - } + describe(`scaleMetricValues performance check${getTitlePostfix()}`, () => { + beforeAll(() => { + typesRegistry.get('count').params.push({ + name: 'scaleMetricValues', + default: false, + write: () => {}, + advanced: true, + } as any as BucketAggParam); }); - } + test('does not call write if scaleMetricValues is not set', () => { + const aggConfigs = createAggConfigs([{ type: 'count' } as any]); - // check for two character country code - function expectCountry(val: string | number) { - expect(typeof val).toBe('string'); - expect(val).toHaveLength(2); - } + const writeMock = jest.fn(); + aggConfigs.getRequestAggs()[0].write = writeMock; - // check for an OS term - function expectExtension(val: string | number) { - expect(val).toMatch(/^(js|png|html|css|jpg)$/); - } + tabifyAggResponse(aggConfigs, enrichResponseWithSampling(metricOnly), { + metricsAtAllLevels: true, + }); + expect(writeMock).not.toHaveBeenCalled(); + }); - // check for an OS term - function expectOS(val: string | number) { - expect(val).toMatch(/^(win|mac|linux)$/); - } + test('does call write if scaleMetricValues is set', () => { + const aggConfigs = createAggConfigs([ + { type: 'count', params: { scaleMetricValues: true } } as any, + ]); - // check for something like an average bytes result - function expectAvgBytes(val: string | number) { - expect(typeof val).toBe('number'); - expect(val === 0 || val > 1000).toBeDefined(); - } + const writeMock = jest.fn(() => ({})); + aggConfigs.getRequestAggs()[0].write = writeMock; - test('for non-hierarchical vis', () => { - // the default for a non-hierarchical vis is to display - // only complete rows, and only put the metrics at the end. + tabifyAggResponse(aggConfigs, enrichResponseWithSampling(metricOnly), { + metricsAtAllLevels: true, + }); + expect(writeMock).toHaveBeenCalled(); + }); + }); - const tabbed = tabifyAggResponse(aggConfigs, esResp, { metricsAtAllLevels: false }); + describe(`transforms a complex response${getTitlePostfix()}`, () => { + let esResp: typeof threeTermBuckets; + let aggConfigs: IAggConfigs; + let avg: IAggConfig; + let ext: IAggConfig; + let src: IAggConfig; + let os: IAggConfig; + + function getTopAggregations( + rawResp: typeof threeTermBuckets + ): typeof threeTermBuckets['aggregations'] { + return !isSamplingEnabled(probability) + ? rawResp.aggregations! + : // @ts-ignore + rawResp.aggregations!.sampling!; + } + + beforeEach(() => { + aggConfigs = createAggConfigs([ + mockAggConfig({ type: 'avg', schema: 'metric', params: { field: '@timestamp' } }), + mockAggConfig({ type: 'terms', schema: 'split', params: { field: '@timestamp' } }), + mockAggConfig({ type: 'terms', schema: 'segment', params: { field: '@timestamp' } }), + mockAggConfig({ type: 'terms', schema: 'segment', params: { field: '@timestamp' } }), + ]); - expectColumns(tabbed, [ext, src, os, avg]); + [avg, ext, src, os] = aggConfigs.aggs; - tabbed.rows.forEach((row) => { - expectRow(row, [expectExtension, expectCountry, expectOS, expectAvgBytes]); + esResp = enrichResponseWithSampling(threeTermBuckets); + getTopAggregations(esResp).agg_2.buckets[1].agg_3.buckets[0].agg_4.buckets = []; }); - }); - - test('for hierarchical vis', () => { - const tabbed = tabifyAggResponse(aggConfigs, esResp, { metricsAtAllLevels: true }); - expectColumns(tabbed, [ext, avg, src, avg, os, avg]); + // check that the columns of a table are formed properly + function expectColumns(table: ReturnType, aggs: IAggConfig[]) { + expect(table.columns).toHaveLength(aggs.length); + + aggs.forEach((agg, i) => { + expect(table.columns[i]).toHaveProperty('name', agg.makeLabel()); + }); + } + + // check that a row has expected values + function expectRow( + row: Record, + asserts: Array<(val: string | number) => void> + ) { + expect(typeof row).toBe('object'); + + asserts.forEach((assert, i: number) => { + if (row[`col-${i}`]) { + assert(row[`col-${i}`]); + } + }); + } + + // check for two character country code + function expectCountry(val: string | number) { + expect(typeof val).toBe('string'); + expect(val).toHaveLength(2); + } + + // check for an OS term + function expectExtension(val: string | number) { + expect(val).toMatch(/^(js|png|html|css|jpg)$/); + } + + // check for an OS term + function expectOS(val: string | number) { + expect(val).toMatch(/^(win|mac|linux)$/); + } + + // check for something like an average bytes result + function expectAvgBytes(val: string | number) { + expect(typeof val).toBe('number'); + expect(val === 0 || val > 1000).toBeDefined(); + } + + test('for non-hierarchical vis', () => { + // the default for a non-hierarchical vis is to display + // only complete rows, and only put the metrics at the end. + + const tabbed = tabifyAggResponse(aggConfigs, esResp, { metricsAtAllLevels: false }); + + expectColumns(tabbed, [ext, src, os, avg]); + + tabbed.rows.forEach((row) => { + expectRow(row, [expectExtension, expectCountry, expectOS, expectAvgBytes]); + }); + }); - tabbed.rows.forEach((row) => { - expectRow(row, [ - expectExtension, - expectAvgBytes, - expectCountry, - expectAvgBytes, - expectOS, - expectAvgBytes, - ]); + test('for hierarchical vis', () => { + const tabbed = tabifyAggResponse(aggConfigs, esResp, { metricsAtAllLevels: true }); + + expectColumns(tabbed, [ext, avg, src, avg, os, avg]); + + tabbed.rows.forEach((row) => { + expectRow(row, [ + expectExtension, + expectAvgBytes, + expectCountry, + expectAvgBytes, + expectOS, + expectAvgBytes, + ]); + }); }); }); - }); + } }); diff --git a/src/plugins/data/common/search/tabify/tabify.ts b/src/plugins/data/common/search/tabify/tabify.ts index ea26afd30129..2c332c1ad6a7 100644 --- a/src/plugins/data/common/search/tabify/tabify.ts +++ b/src/plugins/data/common/search/tabify/tabify.ts @@ -147,7 +147,9 @@ export function tabifyAggResponse( const write = new TabbedAggResponseWriter(aggConfigs, respOpts || {}); const topLevelBucket: AggResponseBucket = { - ...esResponse.aggregations, + ...(aggConfigs.isSamplingEnabled() + ? esResponse.aggregations.sampling + : esResponse.aggregations), doc_count: esResponse.aggregations?.doc_count || esResponse.hits?.total, }; diff --git a/src/plugins/data/public/query/filter_manager/lib/generate_mapping_chain.test.ts b/src/plugins/data/public/query/filter_manager/lib/generate_mapping_chain.test.ts index 7c86581b42a6..e0cd902836df 100644 --- a/src/plugins/data/public/query/filter_manager/lib/generate_mapping_chain.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/generate_mapping_chain.test.ts @@ -86,7 +86,7 @@ describe('filter manager utilities', () => { expect(result).toEqual({ key: 'test', value: 'example' }); }); - test('should throw an error if no functions match', async (done) => { + test('should throw an error if no functions match', async () => { const filter = buildEmptyFilter(true); mapping.throws(filter); @@ -98,7 +98,6 @@ describe('filter manager utilities', () => { } catch (err) { expect(err).toBeInstanceOf(Error); expect(err.message).toBe('No mappings have been found for filter.'); - done(); } }); }); diff --git a/src/plugins/data/public/query/filter_manager/lib/map_filter.test.ts b/src/plugins/data/public/query/filter_manager/lib/map_filter.test.ts index ff9c6d47660f..e0bc31787df0 100644 --- a/src/plugins/data/public/query/filter_manager/lib/map_filter.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/map_filter.test.ts @@ -54,7 +54,7 @@ describe('filter manager utilities', () => { expect(after.meta).toHaveProperty('negate', false); }); - test('should finish with a catch', async (done) => { + test('should finish with a catch', async () => { const before: any = { meta: { index: 'logstash-*' } }; try { @@ -62,8 +62,6 @@ describe('filter manager utilities', () => { } catch (e) { expect(e).toBeInstanceOf(Error); expect(e.message).toBe('No mappings have been found for filter.'); - - done(); } }); }); diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_exists.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_exists.test.ts index 0e642b3f99dc..b46f5b4c488e 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_exists.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_exists.test.ts @@ -29,14 +29,13 @@ describe('filter manager utilities', () => { expect(result).toHaveProperty('value', 'exists'); }); - test('should return undefined for none matching', async (done) => { + test('should return undefined for none matching', async () => { const filter = buildEmptyFilter(true); try { mapQueryString(filter); } catch (e) { expect(e).toBe(filter); - done(); } }); }); diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrase.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrase.test.ts index 88e819bb7f9a..93d5547f1b9e 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrase.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrase.test.ts @@ -23,7 +23,7 @@ describe('filter manager utilities', () => { expect(result).toHaveProperty('key', '_type'); }); - test('should return undefined for none matching', async (done) => { + test('should return undefined for none matching', async () => { const filter = { meta: { index: 'logstash-*' }, query: { query_string: { query: 'foo:bar' } }, @@ -33,7 +33,6 @@ describe('filter manager utilities', () => { mapPhrase(filter); } catch (e) { expect(e).toBe(filter); - done(); } }); }); diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrases.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrases.test.ts index 4a219e23aff0..6c69f0ff948d 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrases.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrases.test.ts @@ -29,7 +29,7 @@ describe('filter manager utilities', () => { expect(result).toHaveProperty('value', ['hello', 1, 'world']); }); - test('should return undefined for none matching', async (done) => { + test('should return undefined for none matching', (done) => { const filter = { meta: { index: 'logstash-*' }, query: { query_string: { query: 'foo:bar' } }, diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_query_string.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_query_string.test.ts index f54cea316491..df5347c33e6a 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_query_string.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_query_string.test.ts @@ -19,14 +19,13 @@ describe('filter manager utilities', () => { expect(result).toHaveProperty('value', 'foo:bar'); }); - test('should return undefined for none matching', async (done) => { + test('should return undefined for none matching', async () => { const filter = buildEmptyFilter(true); try { mapQueryString(filter as Filter); } catch (e) { expect(e).toBe(filter); - done(); } }); }); diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_range.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_range.test.ts index 065e3da18999..82c701a510df 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_range.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_range.test.ts @@ -22,7 +22,7 @@ describe('filter manager utilities', () => { expect(result).toHaveProperty('value', { gt: 1024, lt: 2048 }); }); - test('should return undefined for none matching', async (done) => { + test('should return undefined for none matching', async () => { const filter = { meta: { index: 'logstash-*' }, query: { query_string: { query: 'foo:bar' } }, @@ -32,8 +32,6 @@ describe('filter manager utilities', () => { mapRange(filter); } catch (e) { expect(e).toBe(filter); - - done(); } }); }); diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_spatial_filter.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_spatial_filter.test.ts index 9065ccfa1785..67dd343dd33c 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_spatial_filter.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_spatial_filter.test.ts @@ -109,7 +109,7 @@ describe('mapSpatialFilter()', () => { expect(result).toHaveProperty('type', FILTERS.SPATIAL_FILTER); }); - test('should return undefined for none matching', async (done) => { + test('should return undefined for none matching', async () => { const filter = { meta: { key: 'location', @@ -124,8 +124,6 @@ describe('mapSpatialFilter()', () => { mapSpatialFilter(filter); } catch (e) { expect(e).toBe(filter); - - done(); } }); }); diff --git a/src/plugins/data/public/query/timefilter/lib/auto_refresh_loop.test.ts b/src/plugins/data/public/query/timefilter/lib/auto_refresh_loop.test.ts index 3c8b316c3b87..fc30a120c445 100644 --- a/src/plugins/data/public/query/timefilter/lib/auto_refresh_loop.test.ts +++ b/src/plugins/data/public/query/timefilter/lib/auto_refresh_loop.test.ts @@ -8,7 +8,7 @@ import { createAutoRefreshLoop, AutoRefreshDoneFn } from './auto_refresh_loop'; -jest.useFakeTimers(); +jest.useFakeTimers('legacy'); test('triggers refresh with interval', () => { const { loop$, start, stop } = createAutoRefreshLoop(); diff --git a/src/plugins/data/public/query/timefilter/timefilter.test.ts b/src/plugins/data/public/query/timefilter/timefilter.test.ts index 0152076c7b8a..7ba6f07835f7 100644 --- a/src/plugins/data/public/query/timefilter/timefilter.test.ts +++ b/src/plugins/data/public/query/timefilter/timefilter.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -jest.useFakeTimers(); +jest.useFakeTimers('legacy'); import sinon from 'sinon'; import moment from 'moment'; diff --git a/src/plugins/data/public/search/expressions/esaggs.test.ts b/src/plugins/data/public/search/expressions/esaggs.test.ts index aa26ceab9ae6..1a04e4ffeb83 100644 --- a/src/plugins/data/public/search/expressions/esaggs.test.ts +++ b/src/plugins/data/public/search/expressions/esaggs.test.ts @@ -50,6 +50,7 @@ describe('esaggs expression function - public', () => { metricsAtAllLevels: true, partialRows: false, timeFields: ['@timestamp', 'utc_time'], + probability: 1, }; beforeEach(() => { @@ -88,7 +89,7 @@ describe('esaggs expression function - public', () => { expect(startDependencies.aggs.createAggConfigs).toHaveBeenCalledWith( {}, args.aggs.map((agg) => agg.value), - { hierarchical: true, partialRows: false } + { hierarchical: true, partialRows: false, probability: 1, samplerSeed: undefined } ); }); @@ -98,6 +99,8 @@ describe('esaggs expression function - public', () => { expect(startDependencies.aggs.createAggConfigs).toHaveBeenCalledWith({}, [], { hierarchical: true, partialRows: false, + probability: 1, + samplerSeed: undefined, }); }); diff --git a/src/plugins/data/public/search/expressions/esaggs.ts b/src/plugins/data/public/search/expressions/esaggs.ts index d944dcd5c1a5..b82401d4d8ca 100644 --- a/src/plugins/data/public/search/expressions/esaggs.ts +++ b/src/plugins/data/public/search/expressions/esaggs.ts @@ -48,7 +48,12 @@ export function getFunctionDefinition({ const aggConfigs = aggs.createAggConfigs( indexPattern, args.aggs?.map((agg) => agg.value) ?? [], - { hierarchical: args.metricsAtAllLevels, partialRows: args.partialRows } + { + hierarchical: args.metricsAtAllLevels, + partialRows: args.partialRows, + probability: args.probability, + samplerSeed: args.samplerSeed, + } ); const { handleEsaggsRequest } = await import('../../../common/search/expressions'); diff --git a/src/plugins/data/public/search/search_interceptor/search_abort_controller.test.ts b/src/plugins/data/public/search/search_interceptor/search_abort_controller.test.ts index 5d9d8a990332..82d917216f30 100644 --- a/src/plugins/data/public/search/search_interceptor/search_abort_controller.test.ts +++ b/src/plugins/data/public/search/search_interceptor/search_abort_controller.test.ts @@ -10,7 +10,7 @@ import { SearchAbortController } from './search_abort_controller'; const timeTravel = (msToRun = 0) => { jest.advanceTimersByTime(msToRun); - return new Promise((resolve) => setImmediate(resolve)); + return new Promise((resolve) => jest.requireActual('timers').setImmediate(resolve)); }; describe('search abort controller', () => { @@ -75,7 +75,7 @@ describe('search abort controller', () => { describe('timeout abort', () => { beforeEach(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterEach(() => { diff --git a/src/plugins/data/public/search/search_interceptor/search_interceptor.test.ts b/src/plugins/data/public/search/search_interceptor/search_interceptor.test.ts index 35bbecfdb5c4..73ce6672064a 100644 --- a/src/plugins/data/public/search/search_interceptor/search_interceptor.test.ts +++ b/src/plugins/data/public/search/search_interceptor/search_interceptor.test.ts @@ -41,8 +41,10 @@ let mockCoreSetup: MockedKeys; let bfetchSetup: jest.Mocked; let fetchMock: jest.Mock; -const flushPromises = () => new Promise((resolve) => setImmediate(resolve)); -jest.useFakeTimers(); +const flushPromises = () => + new Promise((resolve) => jest.requireActual('timers').setImmediate(resolve)); + +jest.useFakeTimers('legacy'); const timeTravel = async (msToRun = 0) => { await flushPromises(); @@ -1531,7 +1533,7 @@ describe('SearchInterceptor', () => { await flushPromises(); }); - test('Immediately aborts if passed an aborted abort signal', async (done) => { + test('Immediately aborts if passed an aborted abort signal', async () => { const abort = new AbortController(); const mockRequest: IEsSearchRequest = { params: {}, @@ -1542,7 +1544,6 @@ describe('SearchInterceptor', () => { error.mockImplementation((e) => { expect(e).toBeInstanceOf(AbortError); expect(fetchMock).not.toBeCalled(); - done(); }); response.subscribe({ error }); diff --git a/src/plugins/data/public/search/session/session_helpers.test.ts b/src/plugins/data/public/search/session/session_helpers.test.ts index bc092a4f6ac3..8d33d58b55ac 100644 --- a/src/plugins/data/public/search/session/session_helpers.test.ts +++ b/src/plugins/data/public/search/session/session_helpers.test.ts @@ -65,7 +65,7 @@ beforeEach(() => { describe('waitUntilNextSessionCompletes$', () => { beforeEach(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterEach(() => { jest.useRealTimers(); diff --git a/src/plugins/data/public/search/session/session_indicator/connected_search_session_indicator/connected_search_session_indicator.test.tsx b/src/plugins/data/public/search/session/session_indicator/connected_search_session_indicator/connected_search_session_indicator.test.tsx index 9f29e2866fb6..1174142db213 100644 --- a/src/plugins/data/public/search/session/session_indicator/connected_search_session_indicator/connected_search_session_indicator.test.tsx +++ b/src/plugins/data/public/search/session/session_indicator/connected_search_session_indicator/connected_search_session_indicator.test.tsx @@ -226,7 +226,7 @@ describe('Completed inactivity', () => { describe('tour steps', () => { describe('loading state', () => { beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/src/plugins/data/public/search/session/sessions_mgmt/components/table/table.test.tsx b/src/plugins/data/public/search/session/sessions_mgmt/components/table/table.test.tsx index ac554af701d0..be398ba7294f 100644 --- a/src/plugins/data/public/search/session/sessions_mgmt/components/table/table.test.tsx +++ b/src/plugins/data/public/search/session/sessions_mgmt/components/table/table.test.tsx @@ -152,7 +152,7 @@ describe('Background Search Session Management Table', () => { // FLAKY: https://github.com/elastic/kibana/issues/88928 describe.skip('fetching sessions data', () => { test('re-fetches data', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); sessionsClient.find = jest.fn(); mockConfig = { ...mockConfig, diff --git a/src/plugins/data/server/lib/get_request_aborted_signal.test.ts b/src/plugins/data/server/lib/get_request_aborted_signal.test.ts index 3a94d020de4d..eb0c590c9c7b 100644 --- a/src/plugins/data/server/lib/get_request_aborted_signal.test.ts +++ b/src/plugins/data/server/lib/get_request_aborted_signal.test.ts @@ -10,7 +10,7 @@ import { Subject } from 'rxjs'; import { getRequestAbortedSignal } from './get_request_aborted_signal'; describe('abortableRequestHandler', () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); it('should call abort if disconnected', () => { const abortedSubject = new Subject(); diff --git a/src/plugins/data/server/search/expressions/esaggs.test.ts b/src/plugins/data/server/search/expressions/esaggs.test.ts index 166f2719e356..9954fb245796 100644 --- a/src/plugins/data/server/search/expressions/esaggs.test.ts +++ b/src/plugins/data/server/search/expressions/esaggs.test.ts @@ -51,6 +51,7 @@ describe('esaggs expression function - server', () => { metricsAtAllLevels: true, partialRows: false, timeFields: ['@timestamp', 'utc_time'], + probability: 1, }; beforeEach(() => { diff --git a/src/plugins/data/server/search/strategies/es_search/es_search_strategy.test.ts b/src/plugins/data/server/search/strategies/es_search/es_search_strategy.test.ts index d8413703808c..15a6a4df7eed 100644 --- a/src/plugins/data/server/search/strategies/es_search/es_search_strategy.test.ts +++ b/src/plugins/data/server/search/strategies/es_search/es_search_strategy.test.ts @@ -58,7 +58,7 @@ describe('ES search strategy', () => { expect(typeof esSearch.search).toBe('function'); }); - it('calls the API caller with the params with defaults', async (done) => { + it('calls the API caller with the params with defaults', async () => { const params = { index: 'logstash-*' }; await esSearchStrategyProvider(mockConfig$, mockLogger) @@ -70,11 +70,10 @@ describe('ES search strategy', () => { ignore_unavailable: true, track_total_hits: true, }); - done(); }); }); - it('calls the API caller with overridden defaults', async (done) => { + it('calls the API caller with overridden defaults', async () => { const params = { index: 'logstash-*', ignore_unavailable: false, timeout: '1000ms' }; await esSearchStrategyProvider(mockConfig$, mockLogger) @@ -85,11 +84,10 @@ describe('ES search strategy', () => { ...params, track_total_hits: true, }); - done(); }); }); - it('has all response parameters', async (done) => + it('has all response parameters', async () => await esSearchStrategyProvider(mockConfig$, mockLogger) .search( { @@ -103,7 +101,6 @@ describe('ES search strategy', () => { expect(data.isPartial).toBe(false); expect(data).toHaveProperty('loaded'); expect(data).toHaveProperty('rawResponse'); - done(); })); it('calls the client with transport options', async () => { @@ -137,7 +134,7 @@ describe('ES search strategy', () => { expect(esClient.search.mock.calls[0][1]).toEqual({ signal: expect.any(AbortSignal) }); }); - it('throws normalized error if ResponseError is thrown', async (done) => { + it('throws normalized error if ResponseError is thrown', async () => { const params = { index: 'logstash-*', ignore_unavailable: false, timeout: '1000ms' }; const errResponse = new errors.ResponseError({ body: indexNotFoundException, @@ -157,11 +154,10 @@ describe('ES search strategy', () => { expect(e.statusCode).toBe(404); expect(e.message).toBe(errResponse.message); expect(e.errBody).toBe(indexNotFoundException); - done(); } }); - it('throws normalized error if ElasticsearchClientError is thrown', async (done) => { + it('throws normalized error if ElasticsearchClientError is thrown', async () => { const params = { index: 'logstash-*', ignore_unavailable: false, timeout: '1000ms' }; const errResponse = new errors.ElasticsearchClientError('This is a general ESClient error'); @@ -175,11 +171,10 @@ describe('ES search strategy', () => { expect(e.statusCode).toBe(500); expect(e.message).toBe(errResponse.message); expect(e.errBody).toBe(undefined); - done(); } }); - it('throws normalized error if ESClient throws unknown error', async (done) => { + it('throws normalized error if ESClient throws unknown error', async () => { const params = { index: 'logstash-*', ignore_unavailable: false, timeout: '1000ms' }; const errResponse = new Error('ESClient error'); @@ -193,11 +188,10 @@ describe('ES search strategy', () => { expect(e.statusCode).toBe(500); expect(e.message).toBe(errResponse.message); expect(e.errBody).toBe(undefined); - done(); } }); - it('throws KbnServerError for unknown index type', async (done) => { + it('throws KbnServerError for unknown index type', async () => { const params = { index: 'logstash-*', ignore_unavailable: false, timeout: '1000ms' }; try { @@ -210,7 +204,6 @@ describe('ES search strategy', () => { expect(e.message).toBe('Unsupported index pattern type banana'); expect(e.statusCode).toBe(400); expect(e.errBody).toBe(undefined); - done(); } }); }); diff --git a/src/plugins/data/server/ui_settings.ts b/src/plugins/data/server/ui_settings.ts index a1ac225ba4b3..8d666590b3d3 100644 --- a/src/plugins/data/server/ui_settings.ts +++ b/src/plugins/data/server/ui_settings.ts @@ -346,8 +346,18 @@ export function getUiSettings( "to": "now" }`, type: 'json', - description: i18n.translate('data.advancedSettings.timepicker.timeDefaultsText', { - defaultMessage: 'The timefilter selection to use when Kibana is started without one', + description: i18n.translate('data.advancedSettings.timepicker.timeDefaultsDescription', { + defaultMessage: + 'The timefilter selection to use when Kibana is started without one. Must be an object containing "from" and "to" (see {acceptedFormatsLink}).', + values: { + acceptedFormatsLink: + `` + + i18n.translate('data.advancedSettings.timepicker.quickRanges.acceptedFormatsLinkText', { + defaultMessage: 'accepted formats', + }) + + '', + }, }), requiresPageReload: true, schema: schema.object({ diff --git a/src/plugins/data_view_editor/public/components/data_view_editor_flyout_content.tsx b/src/plugins/data_view_editor/public/components/data_view_editor_flyout_content.tsx index 9c8617079596..e9b2b8de5c5f 100644 --- a/src/plugins/data_view_editor/public/components/data_view_editor_flyout_content.tsx +++ b/src/plugins/data_view_editor/public/components/data_view_editor_flyout_content.tsx @@ -335,7 +335,7 @@ const IndexPatternEditorFlyoutContentComponent = ({ })} > {i18n.translate('indexPatternEditor.goToManagementPage', { - defaultMessage: 'View on data view management page', + defaultMessage: 'Manage settings and view field details', })} )} diff --git a/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor.test.tsx b/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor.test.tsx index 50dce2567925..825402fd00a2 100644 --- a/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor.test.tsx +++ b/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor.test.tsx @@ -65,7 +65,7 @@ describe('', () => { }; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor_flyout_content.test.ts b/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor_flyout_content.test.ts index 51cd024f0b53..094c38e0eb71 100644 --- a/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor_flyout_content.test.ts +++ b/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor_flyout_content.test.ts @@ -18,7 +18,7 @@ describe('', () => { const { httpRequestsMockHelpers } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor_flyout_preview.test.ts b/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor_flyout_preview.test.ts index 8659e1290976..66cf2bab43d3 100644 --- a/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor_flyout_preview.test.ts +++ b/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor_flyout_preview.test.ts @@ -27,7 +27,7 @@ describe('Field editor Preview panel', () => { const { server, httpRequestsMockHelpers } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/src/plugins/discover/public/application/main/utils/get_fetch_observeable.test.ts b/src/plugins/discover/public/application/main/utils/get_fetch_observeable.test.ts index e39642ac2bce..0d0f1f0802bf 100644 --- a/src/plugins/discover/public/application/main/utils/get_fetch_observeable.test.ts +++ b/src/plugins/discover/public/application/main/utils/get_fetch_observeable.test.ts @@ -51,7 +51,7 @@ describe('getFetchObservable', () => { jest.useRealTimers(); }); - test('refetch$.next should trigger fetch$.next', async (done) => { + test('refetch$.next should trigger fetch$.next', (done) => { const searchSessionManagerMock = createSearchSessionMock(); const main$ = new BehaviorSubject({ fetchStatus: FetchStatus.UNINITIALIZED }); @@ -75,7 +75,7 @@ describe('getFetchObservable', () => { test( 'getAutoRefreshFetch$ should trigger fetch$.next', fakeSchedulers((advance) => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); const searchSessionManagerMock = createSearchSessionMock(); const autoRefreshFetch$ = new Subject(); diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable.test.tsx b/src/plugins/embeddable/public/lib/embeddables/embeddable.test.tsx index de0bcf3f8654..079a31089d70 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable.test.tsx +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable.test.tsx @@ -43,7 +43,7 @@ class OutputTestEmbeddable extends Embeddable { reload() {} } -test('Embeddable calls input subscribers when changed', async (done) => { +test('Embeddable calls input subscribers when changed', (done) => { const hello = new ContactCardEmbeddable( { id: '123', firstName: 'Brienne', lastName: 'Tarth' }, { execAction: (() => null) as any } diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx index 8f096020ae60..d482354ca509 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx @@ -57,7 +57,7 @@ setup.registerEmbeddableFactory(embeddableReactFactory.type, embeddableReactFact const start = doStart(); const getEmbeddableFactory = start.getEmbeddableFactory; -test('HelloWorldContainer initializes embeddables', async (done) => { +test('HelloWorldContainer initializes embeddables', (done) => { const container = new HelloWorldContainer( { id: '123', diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_action.test.ts b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_action.test.ts index 983f9ceedf36..58f15c326be7 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_action.test.ts +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_action.test.ts @@ -53,7 +53,7 @@ beforeEach(async () => { } }); -test('Updates the embeddable title when given', async (done) => { +test('Updates the embeddable title when given', async () => { const getUserData = () => Promise.resolve({ title: 'What is up?' }); const customizePanelAction = new CustomizePanelTitleAction(getUserData); expect(embeddable.getInput().title).toBeUndefined(); @@ -66,11 +66,10 @@ test('Updates the embeddable title when given', async (done) => { // Recreating the container should preserve the custom title. const containerClone = createHelloWorldContainer(container.getInput()); // Need to wait for the container to tell us the embeddable has been loaded. - const subscription = containerClone.getOutput$().subscribe(() => { + const subscription = await containerClone.getOutput$().subscribe(() => { if (containerClone.getOutput().embeddableLoaded[embeddable.id]) { expect(embeddable.getInput().title).toBe('What is up?'); subscription.unsubscribe(); - done(); } }); }); diff --git a/src/plugins/embeddable/public/tests/container.test.ts b/src/plugins/embeddable/public/tests/container.test.ts index 8b487d9f02f7..c6088e95069c 100644 --- a/src/plugins/embeddable/public/tests/container.test.ts +++ b/src/plugins/embeddable/public/tests/container.test.ts @@ -128,7 +128,7 @@ describe('container initialization', () => { expect(embeddable.id).toBe('123'); }; - it('initializes embeddables', async (done) => { + it('initializes embeddables', async () => { const { container } = await createHelloWorldContainerAndEmbeddable({ id: 'hello', panels, @@ -137,10 +137,9 @@ describe('container initialization', () => { expectEmbeddableLoaded(container, '123'); expectEmbeddableLoaded(container, '456'); expectEmbeddableLoaded(container, '789'); - done(); }); - it('initializes embeddables once and only once with multiple input updates', async (done) => { + it('initializes embeddables once and only once with multiple input updates', async () => { const { container, contactCardCreateSpy } = await createHelloWorldContainerAndEmbeddable({ id: 'hello', panels, @@ -148,10 +147,9 @@ describe('container initialization', () => { container.updateInput({ lastReloadRequestTime: 1 }); container.updateInput({ lastReloadRequestTime: 2 }); expect(contactCardCreateSpy).toHaveBeenCalledTimes(4); - done(); }); - it('initializes embeddables in order', async (done) => { + it('initializes embeddables in order', async () => { const childIdInitializeOrder = ['456', '123', '789']; const { contactCardCreateSpy } = await createHelloWorldContainerAndEmbeddable( { @@ -170,10 +168,9 @@ describe('container initialization', () => { expect.anything() // parent passed into create method ); } - done(); }); - it('initializes embeddables in order with partial order arg', async (done) => { + it('initializes embeddables in order with partial order arg', async () => { const childIdInitializeOrder = ['789', 'idontexist']; const { contactCardCreateSpy } = await createHelloWorldContainerAndEmbeddable( { @@ -193,10 +190,9 @@ describe('container initialization', () => { expect.anything() // parent passed into create method ); } - done(); }); - it('initializes embeddables in order, awaiting each', async (done) => { + it('initializes embeddables in order, awaiting each', async () => { const childIdInitializeOrder = ['456', '123', '789']; const { container, contactCardCreateSpy } = await createHelloWorldContainerAndEmbeddable( { @@ -220,7 +216,6 @@ describe('container initialization', () => { ); expect(untilEmbeddableLoadedMock).toHaveBeenCalledWith(orderedId); } - done(); }); }); @@ -244,7 +239,7 @@ test('Container.addNewEmbeddable', async () => { expect(embeddableInContainer.id).toBe(embeddable.id); }); -test('Container.removeEmbeddable removes and cleans up', async (done) => { +test('Container.removeEmbeddable removes and cleans up', async () => { const { start, testPanel } = await createHelloWorldContainerAndEmbeddable(); const container = new HelloWorldContainer( @@ -288,12 +283,10 @@ test('Container.removeEmbeddable removes and cleans up', async (done) => { expect(container.getInput().panels[embeddable.id]).toBeUndefined(); if (isErrorEmbeddable(embeddable)) { expect(false).toBe(true); - done(); } expect(() => embeddable.updateInput({ nameTitle: 'Sir' })).toThrowError(); expect(container.getOutput().embeddableLoaded[embeddable.id]).toBeUndefined(); - done(); }); container.removeEmbeddable(embeddable.id); @@ -403,7 +396,7 @@ test('Container view mode change propagates to children', async () => { expect(embeddable.getInput().viewMode).toBe(ViewMode.EDIT); }); -test(`Container updates its state when a child's input is updated`, async (done) => { +test(`Container updates its state when a child's input is updated`, async () => { const { container, embeddable, start, coreStart, uiActions } = await createHelloWorldContainerAndEmbeddable( { id: 'hello', panels: {}, viewMode: ViewMode.VIEW }, @@ -450,7 +443,6 @@ test(`Container updates its state when a child's input is updated`, async (done) childClone.getInput().nameTitle === 'Dr.' ) { cloneSubscription.unsubscribe(); - done(); } }); } @@ -487,7 +479,7 @@ test(`Derived container state passed to children`, async () => { subscription.unsubscribe(); }); -test(`Can subscribe to children embeddable updates`, async (done) => { +test(`Can subscribe to children embeddable updates`, async () => { const { embeddable } = await createHelloWorldContainerAndEmbeddable( { id: 'hello container', @@ -504,13 +496,12 @@ test(`Can subscribe to children embeddable updates`, async (done) => { const subscription = embeddable.getInput$().subscribe((input: ContactCardEmbeddableInput) => { if (input.nameTitle === 'Dr.') { subscription.unsubscribe(); - done(); } }); embeddable.updateInput({ nameTitle: 'Dr.' }); }); -test('Test nested reactions', async (done) => { +test('Test nested reactions', async () => { const { container, embeddable } = await createHelloWorldContainerAndEmbeddable( { id: 'hello', panels: {}, viewMode: ViewMode.VIEW }, { @@ -533,7 +524,6 @@ test('Test nested reactions', async (done) => { ) { containerSubscription.unsubscribe(); embeddableSubscription.unsubscribe(); - done(); } }); @@ -578,7 +568,7 @@ test('Explicit embeddable input mapped to undefined will default to inherited', ]); }); -test('Explicit embeddable input mapped to undefined with no inherited value will get passed to embeddable', async (done) => { +test('Explicit embeddable input mapped to undefined with no inherited value will get passed to embeddable', async () => { const { container } = await createHelloWorldContainerAndEmbeddable({ id: 'hello', panels: {} }); const embeddable = await container.addNewEmbeddable< @@ -601,7 +591,6 @@ test('Explicit embeddable input mapped to undefined with no inherited value will .subscribe(() => { if (embeddable.getInput().filters === undefined) { subscription.unsubscribe(); - done(); } }); @@ -666,7 +655,7 @@ test('Panel added to input state', async () => { expect(container.getOutput().embeddableLoaded[embeddable2.id]).toBe(true); }); -test('Container changes made directly after adding a new embeddable are propagated', async (done) => { +test('Container changes made directly after adding a new embeddable are propagated', async () => { const coreSetup = coreMock.createSetup(); const coreStart = coreMock.createStart(); const { setup, doStart, uiActions } = testPlugin(coreSetup, coreStart); @@ -710,7 +699,6 @@ test('Container changes made directly after adding a new embeddable are propagat const embeddable = container.getChild(embeddableId); if (embeddable.getInput().viewMode === ViewMode.VIEW) { subscription.unsubscribe(); - done(); } } } @@ -724,7 +712,7 @@ test('Container changes made directly after adding a new embeddable are propagat container.updateInput({ viewMode: ViewMode.VIEW }); }); -test('container stores ErrorEmbeddables when a factory for a child cannot be found (so the panel can be removed)', async (done) => { +test('container stores ErrorEmbeddables when a factory for a child cannot be found (so the panel can be removed)', async () => { const { container } = await createHelloWorldContainerAndEmbeddable({ id: 'hello', panels: { @@ -740,12 +728,11 @@ test('container stores ErrorEmbeddables when a factory for a child cannot be fou if (container.getOutput().embeddableLoaded['123']) { const child = container.getChild('123'); expect(child.type).toBe(ERROR_EMBEDDABLE_TYPE); - done(); } }); }); -test('container stores ErrorEmbeddables when a saved object cannot be found', async (done) => { +test('container stores ErrorEmbeddables when a saved object cannot be found', async () => { const { container } = await createHelloWorldContainerAndEmbeddable({ id: 'hello', panels: { @@ -761,12 +748,11 @@ test('container stores ErrorEmbeddables when a saved object cannot be found', as if (container.getOutput().embeddableLoaded['123']) { const child = container.getChild('123'); expect(child.type).toBe(ERROR_EMBEDDABLE_TYPE); - done(); } }); }); -test('ErrorEmbeddables get updated when parent does', async (done) => { +test('ErrorEmbeddables get updated when parent does', async () => { const { container } = await createHelloWorldContainerAndEmbeddable({ id: 'hello', panels: { @@ -787,7 +773,6 @@ test('ErrorEmbeddables get updated when parent does', async (done) => { container.updateInput({ viewMode: ViewMode.VIEW }); expect(embeddable.getInput().viewMode).toBe(ViewMode.VIEW); - done(); } }); }); @@ -831,7 +816,7 @@ test('untilEmbeddableLoaded() throws an error if there is no such child panel in expect((error as Error).message).toMatchInlineSnapshot(`"Panel not found"`); }); -test('untilEmbeddableLoaded() resolves if child is loaded in the container', async (done) => { +test('untilEmbeddableLoaded() resolves if child is loaded in the container', async () => { const { setup, doStart, coreStart, uiActions } = testPlugin( coreMock.createSetup(), coreMock.createStart() @@ -866,10 +851,9 @@ test('untilEmbeddableLoaded() resolves if child is loaded in the container', asy const child = await container.untilEmbeddableLoaded('123'); expect(child).toBeDefined(); expect(child.type).toBe(HELLO_WORLD_EMBEDDABLE); - done(); }); -test('untilEmbeddableLoaded resolves with undefined if child is subsequently removed', async (done) => { +test('untilEmbeddableLoaded resolves with undefined if child is subsequently removed', async () => { const { doStart, setup, coreStart, uiActions } = testPlugin( coreMock.createSetup(), coreMock.createStart() @@ -907,13 +891,12 @@ test('untilEmbeddableLoaded resolves with undefined if child is subsequently rem container.untilEmbeddableLoaded('123').then((embed) => { expect(embed).toBeUndefined(); - done(); }); container.updateInput({ panels: {} }); }); -test('adding a panel then subsequently removing it before its loaded removes the panel', async (done) => { +test('adding a panel then subsequently removing it before its loaded removes the panel', (done) => { const { doStart, coreStart, uiActions, setup } = testPlugin( coreMock.createSetup(), coreMock.createStart() diff --git a/src/plugins/embeddable/public/tests/explicit_input.test.ts b/src/plugins/embeddable/public/tests/explicit_input.test.ts index d9a7b72ada15..4ed4c12a8039 100644 --- a/src/plugins/embeddable/public/tests/explicit_input.test.ts +++ b/src/plugins/embeddable/public/tests/explicit_input.test.ts @@ -68,7 +68,7 @@ test('Explicit embeddable input mapped to undefined will default to inherited', ]); }); -test('Explicit embeddable input mapped to undefined with no inherited value will get passed to embeddable', async (done) => { +test('Explicit embeddable input mapped to undefined with no inherited value will get passed to embeddable', async () => { const testPanel = createEmbeddablePanelMock({ getActions: uiActions.getTriggerCompatibleActions, getEmbeddableFactory: start.getEmbeddableFactory, @@ -95,13 +95,12 @@ test('Explicit embeddable input mapped to undefined with no inherited value will expect(container.getInputForChild(embeddable.id).filters).toEqual([]); - const subscription = embeddable + const subscription = await embeddable .getInput$() .pipe(skip(1)) .subscribe(() => { if (embeddable.getInput().filters === undefined) { subscription.unsubscribe(); - done(); } }); diff --git a/src/plugins/es_ui_shared/public/request/use_request.test.helpers.tsx b/src/plugins/es_ui_shared/public/request/use_request.test.helpers.tsx index db694a29b793..b75b645846ee 100644 --- a/src/plugins/es_ui_shared/public/request/use_request.test.helpers.tsx +++ b/src/plugins/es_ui_shared/public/request/use_request.test.helpers.tsx @@ -49,7 +49,7 @@ const errorWithBodyResponse = { body: errorValue }; export const createUseRequestHelpers = (): UseRequestHelpers => { // The behavior we're testing involves state changes over time, so we need finer control over // timing. - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); const flushPromiseJobQueue = async () => { // See https://stackoverflow.com/questions/52177631/jest-timer-and-promise-dont-work-well-settimeout-and-async-function diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_array.test.tsx b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_array.test.tsx index 8e9bdd35ffb7..7a2d61c55d6c 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_array.test.tsx +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_array.test.tsx @@ -18,7 +18,7 @@ import { UseArray } from './use_array'; describe('', () => { beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx index 36fd16209f5d..6ea28fc60186 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx @@ -19,7 +19,7 @@ import { UseField } from './use_field'; describe('', () => { beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_multi_fields.test.tsx b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_multi_fields.test.tsx index 94c36613c44b..09b0c898fc0d 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_multi_fields.test.tsx +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_multi_fields.test.tsx @@ -15,7 +15,7 @@ import { UseMultiFields } from './use_multi_fields'; describe('', () => { beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.test.tsx b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.test.tsx index 71aafa637688..4a3b5c99b978 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.test.tsx +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.test.tsx @@ -17,7 +17,7 @@ import { FieldHook, FieldValidateResponse, VALIDATION_TYPES, FieldConfig } from describe('useField() hook', () => { beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.test.tsx b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.test.tsx index 9f57432b5d6a..740dbf5ee5e7 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.test.tsx +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.test.tsx @@ -40,7 +40,7 @@ const onFormHook = (_form: FormHook) => { describe('useForm() hook', () => { beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/src/plugins/expressions/common/execution/execution.abortion.test.ts b/src/plugins/expressions/common/execution/execution.abortion.test.ts index 1664dd86870a..71dca4f60c72 100644 --- a/src/plugins/expressions/common/execution/execution.abortion.test.ts +++ b/src/plugins/expressions/common/execution/execution.abortion.test.ts @@ -13,7 +13,7 @@ import { parseExpression } from '../ast'; import { createUnitTestExecutor } from '../test_helpers'; import { ExpressionFunctionDefinition } from '../expression_functions'; -jest.useFakeTimers(); +jest.useFakeTimers('legacy'); beforeEach(() => { jest.clearAllTimers(); @@ -82,7 +82,7 @@ describe('Execution abortion tests', () => { expect(result).toBe(null); - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); test('nested expressions are aborted when parent aborted', async () => { @@ -151,6 +151,6 @@ describe('Execution abortion tests', () => { expect(aborted).toHaveBeenCalledTimes(1); expect(completed).toHaveBeenCalledTimes(0); - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); }); diff --git a/src/plugins/expressions/common/execution/execution.test.ts b/src/plugins/expressions/common/execution/execution.test.ts index a5e03084a697..357555240191 100644 --- a/src/plugins/expressions/common/execution/execution.test.ts +++ b/src/plugins/expressions/common/execution/execution.test.ts @@ -14,6 +14,7 @@ import { parseExpression, ExpressionAstExpression } from '../ast'; import { createUnitTestExecutor } from '../test_helpers'; import { ExpressionFunctionDefinition } from '..'; import { ExecutionContract } from './execution_contract'; +import { ExpressionValueBoxed } from '../expression_types'; beforeAll(() => { if (typeof performance === 'undefined') { @@ -386,7 +387,7 @@ describe('Execution', () => { }); test('result is undefined until execution completes', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); const execution = createExecution('sleep 10'); expect(execution.state.get().result).toBe(undefined); execution.start(null).subscribe(jest.fn()); @@ -560,7 +561,7 @@ describe('Execution', () => { }); test('execution state is "pending" while execution is in progress', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); const execution = createExecution('sleep 20'); execution.start(null); jest.advanceTimersByTime(5); @@ -744,6 +745,79 @@ describe('Execution', () => { }); }); }); + + test('continues execution when error state is gone', async () => { + testScheduler.run(({ cold, expectObservable, flush }) => { + const a = 1; + const b = 2; + const c = 3; + const d = 4; + const observable$ = cold('abcd|', { a, b, c, d }); + const flakyFn = jest + .fn() + .mockImplementationOnce((value) => value) + .mockImplementationOnce(() => { + throw new Error('Some error.'); + }) + .mockReturnValueOnce({ type: 'something' }) + .mockImplementationOnce((value) => value); + const spyFn = jest.fn((input, { arg }) => arg); + + const executor = createUnitTestExecutor(); + executor.registerFunction({ + name: 'observable', + args: {}, + help: '', + fn: () => observable$, + }); + executor.registerFunction({ + name: 'flaky', + args: {}, + help: '', + fn: (value) => flakyFn(value), + }); + executor.registerFunction({ + name: 'spy', + args: { + arg: { + help: '', + types: ['number'], + }, + }, + help: '', + fn: (input, args) => spyFn(input, args), + }); + + const result = executor.run('spy arg={observable | flaky}', null, {}); + + expectObservable(result).toBe('abcd|', { + a: { partial: true, result: a }, + b: { + partial: true, + result: { + type: 'error', + error: expect.objectContaining({ message: '[spy] > [flaky] > Some error.' }), + }, + }, + c: { + partial: true, + result: { + type: 'error', + error: expect.objectContaining({ + message: `[spy] > Can not cast 'something' to any of 'number'`, + }), + }, + }, + d: { partial: false, result: d }, + }); + + flush(); + + expect(spyFn).toHaveBeenCalledTimes(2); + expect(spyFn).toHaveBeenNthCalledWith(1, null, { arg: a }); + expect(spyFn).toHaveBeenNthCalledWith(2, null, { arg: d }); + }); + }); }); describe('when arguments are missing', () => { @@ -847,6 +921,38 @@ describe('Execution', () => { }); }); + describe('when arguments are incorrect', () => { + it('when required argument is missing and has not alias, returns error', async () => { + const incorrectArg: ExpressionFunctionDefinition< + 'incorrectArg', + unknown, + { arg: ExpressionValueBoxed<'something'> }, + unknown + > = { + name: 'incorrectArg', + args: { + arg: { + help: '', + required: true, + types: ['something'], + }, + }, + help: '', + fn: jest.fn(), + }; + const executor = createUnitTestExecutor(); + executor.registerFunction(incorrectArg); + const { result } = await lastValueFrom(executor.run('incorrectArg arg="string"', null, {})); + + expect(result).toMatchObject({ + type: 'error', + error: { + message: `[incorrectArg] > Can not cast 'string' to any of 'something'`, + }, + }); + }); + }); + describe('debug mode', () => { test('can execute expression in debug mode', async () => { const execution = createExecution('add val=1 | add val=2 | add val=3', {}, true); diff --git a/src/plugins/expressions/common/execution/execution.ts b/src/plugins/expressions/common/execution/execution.ts index 30016f36d77d..8edf6d3227c0 100644 --- a/src/plugins/expressions/common/execution/execution.ts +++ b/src/plugins/expressions/common/execution/execution.ts @@ -352,20 +352,30 @@ export class Execution< // actually have `then` or `subscribe` methods which would be treated as a `Promise` // or an `Observable` accordingly. return this.resolveArgs(fn, currentInput, fnArgs).pipe( - tap((args) => this.execution.params.debug && Object.assign(head.debug, { args })), - switchMap((args) => this.invokeFunction(fn, currentInput, args)), - switchMap((output) => (getType(output) === 'error' ? throwError(output) : of(output))), - tap((output) => this.execution.params.debug && Object.assign(head.debug, { output })), - switchMap((output) => this.invokeChain(tail, output)), - catchError((rawError) => { - const error = createError(rawError); - error.error.message = `[${fnName}] > ${error.error.message}`; - - if (this.execution.params.debug) { - Object.assign(head.debug, { error, rawError, success: false }); - } - - return of(error); + switchMap((resolvedArgs) => { + const args$ = isExpressionValueError(resolvedArgs) + ? throwError(resolvedArgs.error) + : of(resolvedArgs); + + return args$.pipe( + tap((args) => this.execution.params.debug && Object.assign(head.debug, { args })), + switchMap((args) => this.invokeFunction(fn, currentInput, args)), + switchMap((output) => + getType(output) === 'error' ? throwError(output) : of(output) + ), + tap((output) => this.execution.params.debug && Object.assign(head.debug, { output })), + switchMap((output) => this.invokeChain(tail, output)), + catchError((rawError) => { + const error = createError(rawError); + error.error.message = `[${fnName}] > ${error.error.message}`; + + if (this.execution.params.debug) { + Object.assign(head.debug, { error, rawError, success: false }); + } + + return of(error); + }) + ); }), finalize(() => { if (this.execution.params.debug) { @@ -449,7 +459,10 @@ export class Execution< } } - throw new Error(`Can not cast '${fromTypeName}' to any of '${toTypeNames.join(', ')}'`); + throw createError({ + name: 'invalid value', + message: `Can not cast '${fromTypeName}' to any of '${toTypeNames.join(', ')}'`, + }); } validate(value: Type, argDef: ExpressionFunctionParameter): void { @@ -459,7 +472,10 @@ export class Execution< }': '${argDef.options.join("', '")}'`; if (argDef.strict) { - throw new Error(message); + throw createError({ + message, + name: 'invalid argument', + }); } this.logger?.warn(message); @@ -471,7 +487,7 @@ export class Execution< fnDef: Fn, input: unknown, argAsts: Record - ): Observable> { + ): Observable | ExpressionValueError> { return defer(() => { const { args: argDefs } = fnDef; @@ -481,7 +497,10 @@ export class Execution< (acc, argAst, argName) => { const argDef = getByAlias(argDefs, argName); if (!argDef) { - throw new Error(`Unknown argument '${argName}' passed to function '${fnDef.name}'`); + throw createError({ + name: 'unknown argument', + message: `Unknown argument '${argName}' passed to function '${fnDef.name}'`, + }); } if (argDef.deprecated && !acc[argDef.name]) { this.logger?.warn(`Argument '${argName}' is deprecated in function '${fnDef.name}'`); @@ -502,7 +521,10 @@ export class Execution< continue; } - throw new Error(`${fnDef.name} requires the "${name}" argument`); + throw createError({ + name: 'missing argument', + message: `${fnDef.name} requires the "${name}" argument`, + }); } // Create the functions to resolve the argument ASTs into values @@ -513,14 +535,17 @@ export class Execution< (subInput = input) => this.interpret(item, subInput).pipe( pluck('result'), - map((output) => { + switchMap((output) => { if (isExpressionValueError(output)) { - throw output.error; + return of(output); } - return this.cast(output, argDefs[argName].types); - }), - tap((value) => this.validate(value, argDefs[argName])) + return of(output).pipe( + map((value) => this.cast(value, argDefs[argName].types)), + tap((value) => this.validate(value, argDefs[argName])), + catchError((error) => of(error)) + ); + }) ) ) ); @@ -531,7 +556,7 @@ export class Execution< return from([{}]); } - const resolvedArgValuesObservable = combineLatest( + return combineLatest( argNames.map((argName) => { const interpretFns = resolveArgFns[argName]; @@ -542,23 +567,25 @@ export class Execution< } return argDefs[argName].resolve - ? combineLatest(interpretFns.map((fn) => fn())) + ? combineLatest(interpretFns.map((fn) => fn())).pipe( + map((values) => values.find(isExpressionValueError) ?? values) + ) : of(interpretFns); }) - ); - - return resolvedArgValuesObservable.pipe( - map((resolvedArgValues) => - mapValues( - // Return an object here because the arguments themselves might actually have a 'then' - // function which would be treated as a promise - zipObject(argNames, resolvedArgValues), - // Just return the last unless the argument definition allows multiple - (argValues, argName) => (argDefs[argName].multi ? argValues : last(argValues)) - ) + ).pipe( + map( + (values) => + values.find(isExpressionValueError) ?? + mapValues( + // Return an object here because the arguments themselves might actually have a 'then' + // function which would be treated as a promise + zipObject(argNames, values as unknown[][]), + // Just return the last unless the argument definition allows multiple + (argValues, argName) => (argDefs[argName].multi ? argValues : last(argValues)) + ) ) ); - }); + }).pipe(catchError((error) => of(error))); } interpret(ast: ExpressionAstNode, input: T): Observable> { diff --git a/src/plugins/guided_onboarding/public/components/guide_panel.styles.ts b/src/plugins/guided_onboarding/public/components/guide_panel.styles.ts index 1ba565c9caee..7e7f47670edf 100644 --- a/src/plugins/guided_onboarding/public/components/guide_panel.styles.ts +++ b/src/plugins/guided_onboarding/public/components/guide_panel.styles.ts @@ -40,8 +40,12 @@ export const getGuidePanelStyles = (euiTheme: EuiThemeComputed) => ({ `, flyoutFooter: css` border-radius: 0 0 6px 6px; - background: ${euiTheme.colors.ghost}; + background: transparent; padding: 24px 30px; `, + flyoutFooterLink: css` + color: ${euiTheme.colors.darkShade}; + font-weight: 400; + `, }, }); diff --git a/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx b/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx index 5c2861a1543c..83a030b38599 100644 --- a/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx +++ b/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx @@ -274,6 +274,62 @@ describe('Guided setup', () => { // Dependent on the Search guide config, which expects another step to start expect(find('activeStepButton').text()).toEqual('Start'); }); + + test('should render the step description as a paragraph if it is only one sentence', async () => { + const { component, find } = testBed; + + const mockSingleSentenceStepDescriptionGuideState: GuideState = { + guideId: 'observability', + isActive: true, + status: 'in_progress', + steps: [ + { + id: 'add_data', + status: 'complete', + }, + { + id: 'view_dashboard', + status: 'complete', + }, + { + id: 'tour_observability', + status: 'in_progress', + }, + ], + }; + + await updateComponentWithState( + component, + mockSingleSentenceStepDescriptionGuideState, + true + ); + + expect( + find('guidePanelStepDescription') + .last() + .containsMatchingElement( +

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

+ ) + ).toBe(true); + }); + + test('should render the step description as an unordered list if it is more than one sentence', async () => { + const { component, find } = testBed; + + await updateComponentWithState(component, mockActiveSearchGuideState, true); + + expect( + find('guidePanelStepDescription') + .first() + .containsMatchingElement( +
    + {guidesConfig.search.steps[0].descriptionList.map((description, i) => ( +
  • {description}
  • + ))} +
+ ) + ).toBe(true); + }); }); describe('Quit guide modal', () => { diff --git a/src/plugins/guided_onboarding/public/components/guide_panel.tsx b/src/plugins/guided_onboarding/public/components/guide_panel.tsx index ed021bc39c0e..def898cae8a6 100644 --- a/src/plugins/guided_onboarding/public/components/guide_panel.tsx +++ b/src/plugins/guided_onboarding/public/components/guide_panel.tsx @@ -27,7 +27,6 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; import { ApplicationStart } from '@kbn/core/public'; import type { GuideState, GuideStep as GuideStepStatus } from '@kbn/guided-onboarding'; @@ -290,57 +289,59 @@ export const GuidePanel = ({ api, application }: GuidePanelProps) => { - - - - {i18n.translate('guidedOnboarding.dropdownPanel.footer.exitGuideButtonLabel', { - defaultMessage: 'Quit setup guide', + + + + {i18n.translate('guidedOnboarding.dropdownPanel.footer.support', { + defaultMessage: 'Need help?', })} - - - - - {i18n.translate('guidedOnboarding.dropdownPanel.footer.feedbackLabel', { - defaultMessage: 'feedback', - })} - - ), - }} - /> + + + | - - - - - {i18n.translate( - 'guidedOnboarding.dropdownPanel.footer.helpTextDescription', - { - defaultMessage: 'here to help', - } - )} - - ), - }} - /> + + + {i18n.translate('guidedOnboarding.dropdownPanel.footer.feedback', { + defaultMessage: 'Give feedback', + })} + + + + + | + + + {i18n.translate('guidedOnboarding.dropdownPanel.footer.exitGuideButtonLabel', { + defaultMessage: 'Quit guide', + })} + + diff --git a/src/plugins/guided_onboarding/public/components/guide_panel_step.tsx b/src/plugins/guided_onboarding/public/components/guide_panel_step.tsx index fa72afff87dc..32ebd14dee80 100644 --- a/src/plugins/guided_onboarding/public/components/guide_panel_step.tsx +++ b/src/plugins/guided_onboarding/public/components/guide_panel_step.tsx @@ -93,13 +93,16 @@ export const GuideStep = ({ > <> - - -
    - {stepConfig.descriptionList.map((description, index) => { - return
  • {description}
  • ; - })} -
+ + {stepConfig.descriptionList.length === 1 ? ( +

{stepConfig.descriptionList[0]}

// If there is only one description, render it as a paragraph + ) : ( +
    + {stepConfig.descriptionList.map((description, index) => { + return
  • {description}
  • ; + })} +
+ )}
diff --git a/src/plugins/guided_onboarding/public/constants/guides_config/observability.ts b/src/plugins/guided_onboarding/public/constants/guides_config/observability.ts index 76ee412dbd38..5862d0d13273 100644 --- a/src/plugins/guided_onboarding/public/constants/guides_config/observability.ts +++ b/src/plugins/guided_onboarding/public/constants/guides_config/observability.ts @@ -15,12 +15,12 @@ export const observabilityConfig: GuideConfig = { defaultMessage: 'Observe my Kubernetes infrastructure', }), description: i18n.translate('guidedOnboarding.observabilityGuide.description', { - defaultMessage: `We'll help you quickly gain visibility into your Kubernetes environment using Elastic's out-of-the-box integration. Gain deep insights from your logs, metrics, and traces, and proactively detect issues and take action to resolve issues.`, + defaultMessage: `We'll help you quickly get visibility into your Kubernetes environment with our Elastic integration. Gain deep insights from your logs, metrics, and traces to proactively detect issues and take action to resolve them.`, }), guideName: 'Kubernetes', docs: { text: i18n.translate('guidedOnboarding.observabilityGuide.documentationLink', { - defaultMessage: 'Kubernetes documentation', + defaultMessage: 'Learn more', }), url: 'https://docs.elastic.co/en/integrations/kubernetes', }, @@ -28,7 +28,7 @@ export const observabilityConfig: GuideConfig = { { id: 'add_data', title: i18n.translate('guidedOnboarding.observabilityGuide.addDataStep.title', { - defaultMessage: 'Add data', + defaultMessage: 'Add and verify your data', }), integration: 'kubernetes', descriptionList: [ @@ -59,13 +59,13 @@ export const observabilityConfig: GuideConfig = { title: i18n.translate( 'guidedOnboarding.observabilityGuide.viewDashboardStep.manualCompletionPopoverTitle', { - defaultMessage: 'Explore the pre-built Kubernetes dashboards', + defaultMessage: 'Explore Kubernetes dashboards', } ), description: i18n.translate( 'guidedOnboarding.observabilityGuide.viewDashboardStep.manualCompletionPopoverDescription', { - defaultMessage: `Take your time to explore out-of-the-box dashboards that are included with the Kubernetes integration. When you're ready, you can access the next step of the guide in the button above.`, + defaultMessage: `Take your time to explore these pre-built dashboards included with the Kubernetes integration. When you’re ready, click the Setup guide button to continue.`, } ), readyToCompleteOnNavigation: true, diff --git a/src/plugins/guided_onboarding/public/services/api.test.ts b/src/plugins/guided_onboarding/public/services/api.test.ts index 3503b68e5466..229630416664 100644 --- a/src/plugins/guided_onboarding/public/services/api.test.ts +++ b/src/plugins/guided_onboarding/public/services/api.test.ts @@ -108,7 +108,7 @@ describe('GuidedOnboarding ApiService', () => { }); describe('isGuideStepActive$', () => { - it('returns true if the step has been started', async (done) => { + it('returns true if the step has been started', (done) => { const updatedState: GuideState = { ...searchAddDataActiveState, steps: [ @@ -120,8 +120,7 @@ describe('GuidedOnboarding ApiService', () => { searchAddDataActiveState.steps[2], ], }; - await apiService.updateGuideState(updatedState, false); - + apiService.updateGuideState(updatedState, false); subscription = apiService .isGuideStepActive$(searchGuide, firstStep) .subscribe((isStepActive) => { @@ -131,8 +130,8 @@ describe('GuidedOnboarding ApiService', () => { }); }); - it('returns false if the step is not been started', async (done) => { - await apiService.updateGuideState(searchAddDataActiveState, false); + it('returns false if the step is not been started', (done) => { + apiService.updateGuideState(searchAddDataActiveState, false); subscription = apiService .isGuideStepActive$(searchGuide, firstStep) .subscribe((isStepActive) => { @@ -371,7 +370,7 @@ describe('GuidedOnboarding ApiService', () => { }); describe('isGuidedOnboardingActiveForIntegration$', () => { - it('returns true if the integration is part of the active step', async (done) => { + it('returns true if the integration is part of the active step', (done) => { httpClient.get.mockResolvedValue({ state: [securityAddDataInProgressState], }); @@ -385,7 +384,7 @@ describe('GuidedOnboarding ApiService', () => { }); }); - it('returns false if another integration is part of the active step', async (done) => { + it('returns false if another integration is part of the active step', (done) => { httpClient.get.mockResolvedValue({ state: [securityAddDataInProgressState], }); @@ -399,7 +398,7 @@ describe('GuidedOnboarding ApiService', () => { }); }); - it('returns false if no guide is active', async (done) => { + it('returns false if no guide is active', (done) => { httpClient.get.mockResolvedValue({ state: [noGuideActiveState], }); diff --git a/src/plugins/interactive_setup/server/elasticsearch_service.test.ts b/src/plugins/interactive_setup/server/elasticsearch_service.test.ts index 717c6bf56481..bfc3f1b2daba 100644 --- a/src/plugins/interactive_setup/server/elasticsearch_service.test.ts +++ b/src/plugins/interactive_setup/server/elasticsearch_service.test.ts @@ -86,7 +86,7 @@ describe('ElasticsearchService', () => { }); describe('#connectionStatus$', () => { - beforeEach(() => jest.useFakeTimers()); + beforeEach(() => jest.useFakeTimers('legacy')); afterEach(() => jest.useRealTimers()); it('does not repeat ping request if have multiple subscriptions', async () => { diff --git a/src/plugins/kibana_utils/common/abort_utils.test.ts b/src/plugins/kibana_utils/common/abort_utils.test.ts index 0d34a7852fb4..a528f3e48476 100644 --- a/src/plugins/kibana_utils/common/abort_utils.test.ts +++ b/src/plugins/kibana_utils/common/abort_utils.test.ts @@ -8,9 +8,10 @@ import { AbortError, abortSignalToPromise } from './abort_utils'; -jest.useFakeTimers(); +jest.useFakeTimers('legacy'); -const flushPromises = () => new Promise((resolve) => setImmediate(resolve)); +const flushPromises = () => + new Promise((resolve) => jest.requireActual('timers').setImmediate(resolve)); describe('AbortUtils', () => { describe('AbortError', () => { diff --git a/src/plugins/kibana_utils/public/storage/hashed_item_store/hashed_item_store.test.ts b/src/plugins/kibana_utils/public/storage/hashed_item_store/hashed_item_store.test.ts index 20cc81bc3b75..5148d1ad6c03 100644 --- a/src/plugins/kibana_utils/public/storage/hashed_item_store/hashed_item_store.test.ts +++ b/src/plugins/kibana_utils/public/storage/hashed_item_store/hashed_item_store.test.ts @@ -109,7 +109,7 @@ describe('hashedItemStore', () => { beforeEach(() => { // Control time. - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); sessionStorage = new StubBrowserStorage(); hashedItemStore = new HashedItemStore(sessionStorage); @@ -199,7 +199,7 @@ describe('hashedItemStore', () => { beforeEach(() => { // Control time. - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); sessionStorage = new StubBrowserStorage(); hashedItemStore = new HashedItemStore(sessionStorage); @@ -350,7 +350,7 @@ describe('hashedItemStore', () => { beforeEach(() => { // Control time. - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); sessionStorage = new StubBrowserStorage(); hashedItemStore = new HashedItemStore(sessionStorage); }); diff --git a/src/plugins/newsfeed/public/plugin.test.ts b/src/plugins/newsfeed/public/plugin.test.ts index a082b81dc84b..2ab54026a4cf 100644 --- a/src/plugins/newsfeed/public/plugin.test.ts +++ b/src/plugins/newsfeed/public/plugin.test.ts @@ -16,7 +16,7 @@ describe('Newsfeed plugin', () => { let plugin: NewsfeedPublicPlugin; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/src/plugins/telemetry/public/services/telemetry_sender.test.ts b/src/plugins/telemetry/public/services/telemetry_sender.test.ts index 7f986af97331..b68ab85f0340 100644 --- a/src/plugins/telemetry/public/services/telemetry_sender.test.ts +++ b/src/plugins/telemetry/public/services/telemetry_sender.test.ts @@ -246,7 +246,7 @@ describe('TelemetrySender', () => { beforeEach(() => { window.fetch = mockFetch = jest.fn(); - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); consoleWarnMock = jest.spyOn(global.console, 'warn').mockImplementation(() => {}); }); @@ -380,7 +380,7 @@ describe('TelemetrySender', () => { }); describe('getRetryDelay', () => { - beforeEach(() => jest.useFakeTimers()); + beforeEach(() => jest.useFakeTimers('legacy')); afterAll(() => jest.useRealTimers()); it('sets a minimum retry delay of 60 seconds', () => { @@ -399,7 +399,7 @@ describe('TelemetrySender', () => { }); describe('startChecking', () => { - beforeEach(() => jest.useFakeTimers()); + beforeEach(() => jest.useFakeTimers('legacy')); afterAll(() => jest.useRealTimers()); it('calls sendIfDue every 60000 ms', () => { diff --git a/src/plugins/ui_actions/public/tests/execute_trigger_actions.test.ts b/src/plugins/ui_actions/public/tests/execute_trigger_actions.test.ts index 3c87857591e6..b303f8b4c3a1 100644 --- a/src/plugins/ui_actions/public/tests/execute_trigger_actions.test.ts +++ b/src/plugins/ui_actions/public/tests/execute_trigger_actions.test.ts @@ -44,7 +44,7 @@ const reset = () => { executeFn.mockReset(); openContextMenuSpy.mockReset(); - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }; beforeEach(reset); diff --git a/src/plugins/unified_search/public/dataview_picker/assets/adhoc.svg b/src/plugins/unified_search/public/dataview_picker/assets/adhoc.svg new file mode 100644 index 000000000000..04cf8bc8f22b --- /dev/null +++ b/src/plugins/unified_search/public/dataview_picker/assets/adhoc.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx b/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx index 1e71da3a0b20..3b987b8b1917 100644 --- a/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx +++ b/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx @@ -7,7 +7,7 @@ */ import { i18n } from '@kbn/i18n'; -import React, { useState, useEffect, useCallback, useRef } from 'react'; +import React, { useState, useEffect, useCallback, useRef, useMemo } from 'react'; import { css } from '@emotion/react'; import { EuiPopover, @@ -26,13 +26,13 @@ import { EuiToolTip, EuiSpacer, } from '@elastic/eui'; -import type { DataViewListItem } from '@kbn/data-views-plugin/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import type { IUnifiedSearchPluginServices } from '../types'; import type { DataViewPickerPropsExtended } from '.'; -import { DataViewsList } from './dataview_list'; +import { type DataViewListItemEnhanced, DataViewsList } from './dataview_list'; import type { TextBasedLanguagesListProps } from './text_languages_list'; import type { TextBasedLanguagesTransitionModalProps } from './text_languages_transition_modal'; +import adhoc from './assets/adhoc.svg'; import { changeDataViewStyles } from './change_dataview.styles'; // local storage key for the text based languages transition modal @@ -80,7 +80,7 @@ export function ChangeDataView({ const [noDataViewMatches, setNoDataViewMatches] = useState(false); const [dataViewSearchString, setDataViewSearchString] = useState(''); const [indexMatches, setIndexMatches] = useState(0); - const [dataViewsList, setDataViewsList] = useState([]); + const [dataViewsList, setDataViewsList] = useState([]); const [triggerLabel, setTriggerLabel] = useState(''); const [isTextBasedLangSelected, setIsTextBasedLangSelected] = useState( Boolean(textBasedLanguage) @@ -100,7 +100,7 @@ export function ChangeDataView({ useEffect(() => { const fetchDataViews = async () => { - const dataViewsRefs = await data.dataViews.getIdsWithTitle(); + const dataViewsRefs: DataViewListItemEnhanced[] = await data.dataViews.getIdsWithTitle(); if (adHocDataViews?.length) { adHocDataViews.forEach((adHocDataView) => { if (adHocDataView.id) { @@ -108,6 +108,7 @@ export function ChangeDataView({ title: adHocDataView.title, name: adHocDataView.name, id: adHocDataView.id, + isAdhoc: true, }); } }); @@ -151,6 +152,10 @@ export function ChangeDataView({ } }, [isTextBasedLangSelected, textBasedLanguage]); + const isAdHocSelected = useMemo(() => { + return adHocDataViews?.some((dataView) => dataView.id === currentDataViewId); + }, [adHocDataViews, currentDataViewId]); + const createTrigger = function () { const { label, title, 'data-test-subj': dataTestSubj, fullWidth, ...rest } = trigger; return ( @@ -168,7 +173,18 @@ export function ChangeDataView({ disabled={isDisabled} {...rest} > - {triggerLabel} + <> + {isAdHocSelected && ( + + )} + {triggerLabel} + ); }; diff --git a/src/plugins/unified_search/public/dataview_picker/dataview_list.tsx b/src/plugins/unified_search/public/dataview_picker/dataview_list.tsx index 1a4a1ddd4355..108441954b53 100644 --- a/src/plugins/unified_search/public/dataview_picker/dataview_list.tsx +++ b/src/plugins/unified_search/public/dataview_picker/dataview_list.tsx @@ -7,13 +7,17 @@ */ import React from 'react'; -import { EuiSelectable, EuiSelectableProps, EuiPanel } from '@elastic/eui'; +import { EuiSelectable, EuiSelectableProps, EuiPanel, EuiBadge } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { css } from '@emotion/react'; import { DataViewListItem } from '@kbn/data-views-plugin/public'; +export interface DataViewListItemEnhanced extends DataViewListItem { + isAdhoc?: boolean; +} + export interface DataViewsListProps { - dataViewsList: DataViewListItem[]; + dataViewsList: DataViewListItemEnhanced[]; onChangeDataView: (newId: string) => void; isTextBasedLangSelected?: boolean; currentDataViewId?: string; @@ -40,11 +44,18 @@ export function DataViewsList({ data-test-subj="indexPattern-switcher" searchable singleSelection="always" - options={dataViewsList?.map(({ title, id, name }) => ({ + options={dataViewsList?.map(({ title, id, name, isAdhoc }) => ({ key: id, label: name ? name : title, value: id, checked: id === currentDataViewId && !Boolean(isTextBasedLangSelected) ? 'on' : undefined, + append: isAdhoc ? ( + + {i18n.translate('unifiedSearch.query.queryBar.indexPattern.temporaryDataviewLabel', { + defaultMessage: 'Temporary', + })} + + ) : null, }))} onChange={(choices) => { const choice = choices.find(({ checked }) => checked) as unknown as { diff --git a/src/plugins/unified_search/public/dataview_picker/text_languages_list.tsx b/src/plugins/unified_search/public/dataview_picker/text_languages_list.tsx index 7b6ca242237c..cf070b7746bf 100644 --- a/src/plugins/unified_search/public/dataview_picker/text_languages_list.tsx +++ b/src/plugins/unified_search/public/dataview_picker/text_languages_list.tsx @@ -7,8 +7,8 @@ */ import React from 'react'; -import { EuiSelectable, EuiPanel, EuiBetaBadge } from '@elastic/eui'; -import { css } from '@emotion/react'; +import { i18n } from '@kbn/i18n'; +import { EuiSelectable, EuiPanel, EuiBadge } from '@elastic/eui'; import { TextBasedLanguages } from '.'; export interface TextBasedLanguagesListProps { @@ -39,15 +39,11 @@ export default function TextBasedLanguagesList({ value: lang, checked: lang === selectedOption ? 'on' : undefined, append: ( - + + {i18n.translate('unifiedSearch.query.queryBar.textBasedLanguagesTechPreviewLabel', { + defaultMessage: 'Technical preview', + })} + ), }))} onChange={(choices) => { diff --git a/src/plugins/unified_search/public/query_string_input/query_bar_top_row.tsx b/src/plugins/unified_search/public/query_string_input/query_bar_top_row.tsx index 26b5c1770ee8..8426275f10df 100644 --- a/src/plugins/unified_search/public/query_string_input/query_bar_top_row.tsx +++ b/src/plugins/unified_search/public/query_string_input/query_bar_top_row.tsx @@ -436,6 +436,7 @@ export const QueryBarTopRow = React.memo( size={shouldShowDatePickerAsBadge() ? 's' : 'm'} color={props.isDirty ? 'success' : 'primary'} fill={props.isDirty} + needsUpdate={props.isDirty} data-test-subj="querySubmitButton" // @ts-expect-error Need to fix expecting `children` in EUI toolTipProps={{ diff --git a/src/plugins/unified_search/public/query_string_input/query_string_input.test.tsx b/src/plugins/unified_search/public/query_string_input/query_string_input.test.tsx index dbf7af6afe12..f6f433de9fcf 100644 --- a/src/plugins/unified_search/public/query_string_input/query_string_input.test.tsx +++ b/src/plugins/unified_search/public/query_string_input/query_string_input.test.tsx @@ -28,7 +28,7 @@ import { QueryLanguageSwitcher } from './language_switcher'; import QueryStringInput from './query_string_input'; import { unifiedSearchPluginMock } from '../mocks'; -jest.useFakeTimers(); +jest.useFakeTimers('legacy'); const startMock = coreMock.createStart(); const noop = () => { diff --git a/src/plugins/vis_types/table/public/utils/use/use_ui_state.test.ts b/src/plugins/vis_types/table/public/utils/use/use_ui_state.test.ts index 5d6d0594e8e6..6be4eeca6bdc 100644 --- a/src/plugins/vis_types/table/public/utils/use/use_ui_state.test.ts +++ b/src/plugins/vis_types/table/public/utils/use/use_ui_state.test.ts @@ -95,7 +95,7 @@ describe('useUiState', () => { describe('updating uiState through callbacks', () => { beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); it('should update the uiState with new sort', async () => { diff --git a/src/plugins/vis_types/timeseries/public/application/components/series_editor.js b/src/plugins/vis_types/timeseries/public/application/components/series_editor.js index 7bd72b85edc1..531075b36244 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/series_editor.js +++ b/src/plugins/vis_types/timeseries/public/application/components/series_editor.js @@ -65,6 +65,10 @@ export class SeriesEditor extends Component { } }; + handleSeriesChange = (doc) => { + handleChange(this.props, doc); + }; + render() { const { limit, model, name, fields, colorPicker } = this.props; const list = model[name].filter((val, index) => index < (limit || Infinity)); @@ -89,7 +93,7 @@ export class SeriesEditor extends Component { disableDelete={model[name].length < 2} fields={fields} onAdd={() => handleAdd(this.props, newSeriesFn)} - onChange={(doc) => handleChange(this.props, doc)} + onChange={this.handleSeriesChange} onClone={() => this.handleClone(row)} onDelete={() => handleDelete(this.props, row)} model={row} diff --git a/src/plugins/vis_types/timeseries/public/application/components/vis_types/table/config.js b/src/plugins/vis_types/timeseries/public/application/components/vis_types/table/config.js index 48ba0e540366..506ce0dbdf2a 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/vis_types/table/config.js +++ b/src/plugins/vis_types/timeseries/public/application/components/vis_types/table/config.js @@ -35,7 +35,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { getDefaultQueryLanguage } from '../../lib/get_default_query_language'; import { checkIfNumericMetric } from '../../lib/check_if_numeric_metric'; import { QueryBarWrapper } from '../../query_bar_wrapper'; -import { DATA_FORMATTERS } from '../../../../../common/enums'; +import { DATA_FORMATTERS, BUCKET_TYPES } from '../../../../../common/enums'; import { isConfigurationFeatureEnabled } from '../../../../../common/check_ui_restrictions'; import { filterCannotBeAppliedErrorMessage } from '../../../../../common/errors'; import { tsvbEditorRowStyles } from '../../../styles/common.styles'; @@ -50,13 +50,20 @@ class TableSeriesConfigUi extends Component { } } + handleAggregateByChange = (selectedOptions) => { + this.props.onChange({ + aggregate_by: selectedOptions?.[0], + }); + }; + + handleSelectChange = createSelectHandler(this.props.onChange); + handleTextChange = createTextHandler(this.props.onChange); + changeModelFormatter = (formatter) => this.props.onChange({ formatter }); render() { const defaults = { offset_time: '', value_template: '{{value}}' }; const model = { ...defaults, ...this.props.model }; - const handleSelectChange = createSelectHandler(this.props.onChange); - const handleTextChange = createTextHandler(this.props.onChange); const htmlId = htmlIdGenerator(); const functionOptions = [ @@ -160,7 +167,7 @@ class TableSeriesConfigUi extends Component { fullWidth > - + @@ -222,11 +229,7 @@ class TableSeriesConfigUi extends Component { fields={this.props.fields} indexPattern={this.props.panel.index_pattern} value={model.aggregate_by} - onChange={(value) => - this.props.onChange({ - aggregate_by: value?.[0], - }) - } + onChange={this.handleAggregateByChange} fullWidth restrict={[ KBN_FIELD_TYPES.NUMBER, @@ -236,7 +239,7 @@ class TableSeriesConfigUi extends Component { KBN_FIELD_TYPES.STRING, ]} uiRestrictions={this.props.uiRestrictions} - type={'terms'} + type={BUCKET_TYPES.TERMS} /> @@ -251,9 +254,10 @@ class TableSeriesConfigUi extends Component { fullWidth > diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/gauge/index.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/gauge/index.test.ts index cbc899981717..9943cfa627e2 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/gauge/index.test.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/gauge/index.test.ts @@ -6,10 +6,11 @@ * Side Public License, v 1. */ +import { Vis } from '@kbn/visualizations-plugin/public'; import { METRIC_TYPES } from '@kbn/data-plugin/public'; import { stubLogstashDataView } from '@kbn/data-views-plugin/common/data_view.stub'; import { TSVB_METRIC_TYPES } from '../../../common/enums'; -import { Metric } from '../../../common/types'; +import { Panel, Metric } from '../../../common/types'; import { convertToLens } from '.'; import { createPanel, createSeries } from '../lib/__mocks__'; import { AvgColumn } from '../lib/convert'; @@ -58,6 +59,10 @@ describe('convertToLens', () => { series: [createSeries({ metrics: [metric] })], }); + const vis = { + params: model, + } as Vis; + const metricColumn: AvgColumn = { columnId: 'col-id', dataType: 'number', @@ -89,51 +94,55 @@ describe('convertToLens', () => { test('should return null for invalid metrics', async () => { mockIsValidMetrics.mockReturnValue(null); - const result = await convertToLens(model); + const result = await convertToLens(vis); expect(result).toBeNull(); expect(mockIsValidMetrics).toBeCalledTimes(1); }); test('should return null for invalid or unsupported metrics', async () => { mockGetMetricsColumns.mockReturnValue(null); - const result = await convertToLens(model); + const result = await convertToLens(vis); expect(result).toBeNull(); expect(mockGetMetricsColumns).toBeCalledTimes(1); }); test('should return null for invalid or unsupported buckets', async () => { mockGetBucketsColumns.mockReturnValue(null); - const result = await convertToLens(model); + const result = await convertToLens(vis); expect(result).toBeNull(); expect(mockGetBucketsColumns).toBeCalledTimes(1); }); test('should return null if metric is staticValue', async () => { const result = await convertToLens({ - ...model, - series: [ - { - ...model.series[0], - metrics: [...model.series[0].metrics, { type: TSVB_METRIC_TYPES.STATIC } as Metric], - }, - ], - }); + params: { + ...model, + series: [ + { + ...model.series[0], + metrics: [...model.series[0].metrics, { type: TSVB_METRIC_TYPES.STATIC } as Metric], + }, + ], + }, + } as Vis); expect(result).toBeNull(); expect(mockGetDataSourceInfo).toBeCalledTimes(0); }); test('should return null if only series agg is specified', async () => { const result = await convertToLens({ - ...model, - series: [ - { - ...model.series[0], - metrics: [ - { type: TSVB_METRIC_TYPES.SERIES_AGG, function: 'min', id: 'some-id' } as Metric, - ], - }, - ], - }); + params: { + ...model, + series: [ + { + ...model.series[0], + metrics: [ + { type: TSVB_METRIC_TYPES.SERIES_AGG, function: 'min', id: 'some-id' } as Metric, + ], + }, + ], + }, + } as Vis); expect(result).toBeNull(); }); @@ -142,7 +151,7 @@ describe('convertToLens', () => { mockGetSeriesAgg.mockReturnValue({ metrics: [metric] }); mockGetConfigurationForGauge.mockReturnValue(null); - const result = await convertToLens(model); + const result = await convertToLens(vis); expect(result).toBeNull(); }); @@ -151,8 +160,8 @@ describe('convertToLens', () => { mockGetSeriesAgg.mockReturnValue({ metrics: [metric] }); mockGetConfigurationForGauge.mockReturnValue({}); - const result = await convertToLens( - createPanel({ + const result = await convertToLens({ + params: createPanel({ series: [ createSeries({ metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], @@ -163,8 +172,8 @@ describe('convertToLens', () => { hidden: false, }), ], - }) - ); + }), + } as Vis); expect(result).toBeDefined(); expect(result?.type).toBe('lnsMetric'); }); diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/gauge/index.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/gauge/index.ts index b97f8d59e953..87d5333d4be5 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/gauge/index.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/gauge/index.ts @@ -45,7 +45,10 @@ const getMaxFormula = (metric: Metric, column?: Column) => { }))`; }; -export const convertToLens: ConvertTsvbToLensVisualization = async (model, timeRange) => { +export const convertToLens: ConvertTsvbToLensVisualization = async ( + { params: model }, + timeRange +) => { const dataViews = getDataViewsStart(); const series = model.series[0]; diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/index.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/index.test.ts index 309f066b18f2..a97395f64c11 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/index.test.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/index.test.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { Vis } from '@kbn/visualizations-plugin/public'; import type { Panel } from '../../common/types'; import { convertTSVBtoLensConfiguration } from '.'; @@ -42,7 +43,9 @@ describe('convertTSVBtoLensConfiguration', () => { ...model, type: 'markdown', } as Panel; - const triggerOptions = await convertTSVBtoLensConfiguration(metricModel); + const triggerOptions = await convertTSVBtoLensConfiguration({ + params: metricModel, + } as Vis); expect(triggerOptions).toBeNull(); }); @@ -51,7 +54,9 @@ describe('convertTSVBtoLensConfiguration', () => { ...model, use_kibana_indexes: false, }; - const triggerOptions = await convertTSVBtoLensConfiguration(stringIndexPatternModel); + const triggerOptions = await convertTSVBtoLensConfiguration({ + params: stringIndexPatternModel, + } as Vis); expect(triggerOptions).toBeNull(); }); }); diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/index.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/index.ts index a3d08e89e91a..3e1982aa0903 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/index.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/index.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { Vis } from '@kbn/visualizations-plugin/public'; import { TimeRange } from '@kbn/data-plugin/common'; import type { Panel } from '../../common/types'; import { PANEL_TYPES } from '../../common/enums'; @@ -29,6 +30,10 @@ const getConvertFnByType = (type: PANEL_TYPES) => { const { convertToLens } = await import('./gauge'); return convertToLens; }, + [PANEL_TYPES.TABLE]: async () => { + const { convertToLens } = await import('./table'); + return convertToLens; + }, }; return convertionFns[type]?.(); @@ -39,17 +44,17 @@ const getConvertFnByType = (type: PANEL_TYPES) => { * Returns the Lens model, only if it is supported. If not, it returns null. * In case of null, the menu item is disabled and the user can't navigate to Lens. */ -export const convertTSVBtoLensConfiguration = async (model: Panel, timeRange?: TimeRange) => { +export const convertTSVBtoLensConfiguration = async (vis: Vis, timeRange?: TimeRange) => { // Disables the option for not supported charts, for the string mode and for series with annotations - if (!model.use_kibana_indexes) { + if (!vis.params.use_kibana_indexes) { return null; } // Disables if model is invalid - if (model.isModelInvalid) { + if (vis.params.isModelInvalid) { return null; } - const convertFn = await getConvertFnByType(model.type); + const convertFn = await getConvertFnByType(vis.params.type); - return (await convertFn?.(model, timeRange)) ?? null; + return (await convertFn?.(vis, timeRange)) ?? null; }; diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/index.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/index.test.ts index 9cb0238f9d26..0fabef6eebdb 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/index.test.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/index.test.ts @@ -14,7 +14,7 @@ import { getConfigurationForMetric, getConfigurationForGauge } from '.'; const mockGetPalette = jest.fn(); -jest.mock('./palette', () => ({ +jest.mock('../palette', () => ({ getPalette: jest.fn(() => mockGetPalette()), })); diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/index.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/index.ts index 7b49d604b234..e6814b0797a1 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/index.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/index.ts @@ -10,7 +10,7 @@ import color from 'color'; import { MetricVisConfiguration } from '@kbn/visualizations-plugin/common'; import { Panel } from '../../../../../common/types'; import { Column, Layer } from '../../convert'; -import { getPalette } from './palette'; +import { getPalette } from '../palette'; import { findMetricColumn, getMetricWithCollapseFn } from '../../../utils'; export const getConfigurationForMetric = ( diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/palette.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/palette/index.test.ts similarity index 99% rename from src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/palette.test.ts rename to src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/palette/index.test.ts index b7356f094f91..059f0372c451 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/palette.test.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/palette/index.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { getPalette } from './palette'; +import { getPalette } from '.'; describe('getPalette', () => { const baseColor = '#fff'; diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/palette.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/palette/index.ts similarity index 83% rename from src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/palette.ts rename to src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/palette/index.ts index 4079ffb39664..159804a1ea29 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/palette.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/palette/index.ts @@ -8,7 +8,7 @@ import color from 'color'; import { ColorStop, CustomPaletteParams, PaletteOutput } from '@kbn/coloring'; import { uniqBy } from 'lodash'; -import { Panel } from '../../../../../common/types'; +import { Panel, Series } from '../../../../../common/types'; const Operators = { GTE: 'gte', @@ -24,9 +24,11 @@ type ColorStopsWithMinMax = Pick< type MetricColorRules = Exclude; type GaugeColorRules = Exclude; +type SeriesColorRules = Exclude; type MetricColorRule = MetricColorRules[number]; type GaugeColorRule = GaugeColorRules[number]; +type SeriesColorRule = SeriesColorRules[number]; type ValidMetricColorRule = Omit & ( @@ -44,31 +46,47 @@ type ValidGaugeColorRule = Omit & { gauge: Exclude; }; +type ValidSeriesColorRule = Omit & { + text: Exclude; +}; + const isValidColorRule = ( rule: MetricColorRule | GaugeColorRule -): rule is ValidMetricColorRule | ValidGaugeColorRule => { +): rule is ValidMetricColorRule | ValidGaugeColorRule | ValidSeriesColorRule => { const { background_color: bColor, color: textColor } = rule as MetricColorRule; const { gauge } = rule as GaugeColorRule; + const { text } = rule as SeriesColorRule; - return rule.operator && (bColor ?? textColor ?? gauge) && rule.value !== undefined ? true : false; + return Boolean( + rule.operator && (bColor ?? textColor ?? gauge ?? text) && rule.value !== undefined + ); }; const isMetricColorRule = ( - rule: ValidMetricColorRule | ValidGaugeColorRule + rule: ValidMetricColorRule | ValidGaugeColorRule | ValidSeriesColorRule ): rule is ValidMetricColorRule => { const metricRule = rule as ValidMetricColorRule; return metricRule.background_color ?? metricRule.color ? true : false; }; -const getColor = (rule: ValidMetricColorRule | ValidGaugeColorRule) => { +const isGaugeColorRule = ( + rule: ValidMetricColorRule | ValidGaugeColorRule | ValidSeriesColorRule +): rule is ValidGaugeColorRule => { + const metricRule = rule as ValidGaugeColorRule; + return Boolean(metricRule.gauge); +}; + +const getColor = (rule: ValidMetricColorRule | ValidGaugeColorRule | ValidSeriesColorRule) => { if (isMetricColorRule(rule)) { return rule.background_color ?? rule.color; + } else if (isGaugeColorRule(rule)) { + return rule.gauge; } - return rule.gauge; + return rule.text; }; const getColorStopsWithMinMaxForAllGteOrWithLte = ( - rules: Array, + rules: Array, tailOperator: string, baseColor?: string ): ColorStopsWithMinMax => { @@ -125,7 +143,7 @@ const getColorStopsWithMinMaxForAllGteOrWithLte = ( }; const getColorStopsWithMinMaxForLtWithLte = ( - rules: Array + rules: Array ): ColorStopsWithMinMax => { const lastRule = rules[rules.length - 1]; const colorStops = rules.reduce((colors, rule, index, rulesArr) => { @@ -166,7 +184,7 @@ const getColorStopsWithMinMaxForLtWithLte = ( }; const getColorStopWithMinMaxForLte = ( - rule: ValidMetricColorRule | ValidGaugeColorRule + rule: ValidMetricColorRule | ValidGaugeColorRule | ValidSeriesColorRule ): ColorStopsWithMinMax => { const colorStop = { color: color(getColor(rule)).hex(), @@ -183,7 +201,7 @@ const getColorStopWithMinMaxForLte = ( }; const getColorStopWithMinMaxForGte = ( - rule: ValidMetricColorRule | ValidGaugeColorRule, + rule: ValidMetricColorRule | ValidGaugeColorRule | ValidSeriesColorRule, baseColor?: string ): ColorStopsWithMinMax => { const colorStop = { @@ -224,12 +242,14 @@ const getCustomPalette = ( }; export const getPalette = ( - rules: MetricColorRules | GaugeColorRules, + rules: MetricColorRules | GaugeColorRules | SeriesColorRules, baseColor?: string ): PaletteOutput | null | undefined => { - const validRules = (rules as Array).filter< - ValidMetricColorRule | ValidGaugeColorRule - >((rule): rule is ValidMetricColorRule | ValidGaugeColorRule => isValidColorRule(rule)); + const validRules = (rules as Array).filter< + ValidMetricColorRule | ValidGaugeColorRule | ValidSeriesColorRule + >((rule): rule is ValidMetricColorRule | ValidGaugeColorRule | ValidSeriesColorRule => + isValidColorRule(rule) + ); validRules.sort((rule1, rule2) => { return rule1.value! - rule2.value!; diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/table/index.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/table/index.test.ts new file mode 100644 index 000000000000..6deeaaa39fb8 --- /dev/null +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/table/index.test.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { createSeries } from '../../__mocks__'; +import { getColumnState } from '.'; + +const mockGetPalette = jest.fn(); + +jest.mock('../palette', () => ({ + getPalette: jest.fn(() => mockGetPalette()), +})); + +describe('getColumnState', () => { + beforeEach(() => { + jest.clearAllMocks(); + mockGetPalette.mockReturnValue({ id: 'custom' }); + }); + + test('should return column state without palette if series is not provided', () => { + const config = getColumnState('test'); + expect(config).toEqual({ + columnId: 'test', + alignment: 'left', + colorMode: 'none', + }); + expect(mockGetPalette).toBeCalledTimes(0); + }); + + test('should return column state with palette if series is provided', () => { + const config = getColumnState('test', undefined, createSeries()); + expect(config).toEqual({ + columnId: 'test', + alignment: 'left', + colorMode: 'text', + palette: { id: 'custom' }, + }); + expect(mockGetPalette).toBeCalledTimes(1); + }); + + test('should return column state with collapseFn if collapseFn is provided', () => { + const config = getColumnState('test', 'max', createSeries()); + expect(config).toEqual({ + columnId: 'test', + alignment: 'left', + colorMode: 'text', + palette: { id: 'custom' }, + collapseFn: 'max', + }); + expect(mockGetPalette).toBeCalledTimes(1); + }); +}); diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/table/index.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/table/index.ts new file mode 100644 index 000000000000..7f152fa21884 --- /dev/null +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/table/index.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { Series } from '../../../../../common/types'; +import { getPalette } from '../palette'; + +export const getColumnState = (columnId: string, collapseFn?: string, series?: Series) => { + const palette = series ? getPalette(series.color_rules ?? []) : undefined; + return { + columnId, + alignment: 'left' as const, + colorMode: palette ? 'text' : 'none', + ...(palette ? { palette } : {}), + ...(collapseFn ? { collapseFn } : {}), + }; +}; diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/column.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/column.ts index c06cc3e72227..2be4e09bf889 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/column.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/column.ts @@ -32,7 +32,7 @@ interface ExtraColumnFields { const isSupportedFormat = (format: string) => ['bytes', 'number', 'percent'].includes(format); -export const getFormat = (series: Series): FormatParams => { +export const getFormat = (series: Pick): FormatParams => { let suffix; if (!series.formatter || series.formatter === 'default') { diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/date_histogram.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/date_histogram.ts index f2173cf56b46..0887ad1168dd 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/date_histogram.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/date_histogram.ts @@ -9,28 +9,36 @@ import type { DataView } from '@kbn/data-views-plugin/common'; import uuid from 'uuid'; import { DateHistogramParams, DataType } from '@kbn/visualizations-plugin/common/convert_to_lens'; -import { DateHistogramColumn } from './types'; -import type { Panel, Series } from '../../../../common/types'; +import { DateHistogramColumn, DateHistogramSeries } from './types'; +import type { Panel } from '../../../../common/types'; const getInterval = (interval?: string) => { return interval && !interval?.includes('=') ? interval : 'auto'; }; -export const convertToDateHistogramParams = (model: Panel, series: Series): DateHistogramParams => { +export const convertToDateHistogramParams = ( + model: Panel | undefined, + series: DateHistogramSeries, + includeEmptyRows: boolean = true +): DateHistogramParams => { return { - interval: getInterval(series.override_index_pattern ? series.series_interval : model.interval), + interval: getInterval(series.override_index_pattern ? series.series_interval : model?.interval), dropPartials: series.override_index_pattern ? series.series_drop_last_bucket > 0 - : model.drop_last_bucket > 0, - includeEmptyRows: true, + : (model?.drop_last_bucket ?? 0) > 0, + includeEmptyRows, }; }; export const convertToDateHistogramColumn = ( - model: Panel, - series: Series, + model: Panel | undefined, + series: DateHistogramSeries, dataView: DataView, - { fieldName, isSplit }: { fieldName: string; isSplit: boolean } + { + fieldName, + isSplit, + includeEmptyRows = true, + }: { fieldName: string; isSplit: boolean; includeEmptyRows?: boolean } ): DateHistogramColumn | null => { const dateField = dataView.getFieldByName(fieldName); @@ -38,7 +46,7 @@ export const convertToDateHistogramColumn = ( return null; } - const params = convertToDateHistogramParams(model, series); + const params = convertToDateHistogramParams(model, series, includeEmptyRows); return { columnId: uuid(), diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/filters.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/filters.ts index 9134504813b6..05d74337e848 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/filters.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/filters.ts @@ -8,10 +8,9 @@ import uuid from 'uuid'; import { FiltersParams } from '@kbn/visualizations-plugin/common/convert_to_lens'; -import { FiltersColumn } from './types'; -import type { Series } from '../../../../common/types'; +import { FiltersColumn, FiltersSeries } from './types'; -export const convertToFiltersParams = (series: Series): FiltersParams => { +export const convertToFiltersParams = (series: FiltersSeries): FiltersParams => { const splitFilters = []; if (series.split_mode === 'filter' && series.filter) { splitFilters.push({ filter: series.filter }); @@ -35,7 +34,10 @@ export const convertToFiltersParams = (series: Series): FiltersParams => { }; }; -export const convertToFiltersColumn = (series: Series, isSplit: boolean): FiltersColumn | null => { +export const convertToFiltersColumn = ( + series: FiltersSeries, + isSplit: boolean +): FiltersColumn | null => { const params = convertToFiltersParams(series); if (!params.filters.length) { return null; diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/terms.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/terms.ts index 977de1947d4f..c31d8dca68ce 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/terms.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/terms.ts @@ -9,16 +9,15 @@ import type { DataView } from '@kbn/data-views-plugin/common'; import { DataType, TermsParams } from '@kbn/visualizations-plugin/common'; import uuid from 'uuid'; -import { Series } from '../../../../common/types'; import { excludeMetaFromColumn, getFormat, isColumnWithMeta } from './column'; -import { Column, TermsColumn } from './types'; +import { Column, TermsColumn, TermsSeries } from './types'; interface OrderByWithAgg { orderAgg?: TermsParams['orderAgg']; orderBy: TermsParams['orderBy']; } -const getOrderByWithAgg = (series: Series, columns: Column[]): OrderByWithAgg | null => { +const getOrderByWithAgg = (series: TermsSeries, columns: Column[]): OrderByWithAgg | null => { if (series.terms_order_by === '_key') { return { orderBy: { type: 'alphabetical' } }; } @@ -56,7 +55,7 @@ const getOrderByWithAgg = (series: Series, columns: Column[]): OrderByWithAgg | }; export const convertToTermsParams = ( - series: Series, + series: TermsSeries, columns: Column[], secondaryFields: string[] ): TermsParams | null => { @@ -84,10 +83,11 @@ export const convertToTermsParams = ( export const convertToTermsColumn = ( termFields: [string, ...string[]], - series: Series, + series: TermsSeries, columns: Column[], dataView: DataView, - isSplit: boolean = false + isSplit: boolean = false, + label?: string ): TermsColumn | null => { const [baseField, ...secondaryFields] = termFields; const field = dataView.getFieldByName(baseField); @@ -108,6 +108,7 @@ export const convertToTermsColumn = ( sourceField: field.name, isBucketed: true, isSplit, + label, params: { ...params, ...getFormat(series) }, }; }; diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/types.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/types.ts index 4b3b6c582f91..943550aee006 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/types.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/types.ts @@ -117,4 +117,24 @@ export interface CommonColumnConverterArgs { dataView: DataView; } +export type TermsSeries = Pick< + Series, + | 'split_mode' + | 'terms_direction' + | 'terms_order_by' + | 'terms_size' + | 'terms_include' + | 'terms_exclude' + | 'terms_field' + | 'formatter' + | 'value_template' +>; + +export type FiltersSeries = Pick; + +export type DateHistogramSeries = Pick< + Series, + 'split_mode' | 'override_index_pattern' | 'series_interval' | 'series_drop_last_bucket' +>; + export { FiltersColumn, TermsColumn, DateHistogramColumn }; diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/metrics/supported_metrics.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/metrics/supported_metrics.ts index debe064940c8..a2130de36abd 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/metrics/supported_metrics.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/metrics/supported_metrics.ts @@ -68,6 +68,7 @@ const supportedPanelTypes: readonly PANEL_TYPES[] = [ PANEL_TYPES.TOP_N, PANEL_TYPES.METRIC, PANEL_TYPES.GAUGE, + PANEL_TYPES.TABLE, ]; const supportedTimeRangeModes: readonly TIME_RANGE_DATA_MODES[] = [ diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/series/buckets_columns.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/series/buckets_columns.ts index c0aa201de683..8ca184d443a6 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/series/buckets_columns.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/series/buckets_columns.ts @@ -7,18 +7,21 @@ */ import type { DataView } from '@kbn/data-views-plugin/common'; -import { Series, Panel } from '../../../../common/types'; +import { Panel } from '../../../../common/types'; import { getFieldsForTerms } from '../../../../common/fields_utils'; import { Column, convertToFiltersColumn, convertToDateHistogramColumn, convertToTermsColumn, + TermsSeries, + FiltersSeries, + DateHistogramSeries, } from '../convert'; import { getValidColumns } from './columns'; export const isSplitWithDateHistogram = ( - series: Series, + series: TermsSeries, splitFields: string[], dataView: DataView ) => { @@ -39,27 +42,49 @@ export const isSplitWithDateHistogram = ( return false; }; +const isFiltersSeries = ( + series: DateHistogramSeries | TermsSeries | FiltersSeries +): series is FiltersSeries => { + return series.split_mode === 'filters' || series.split_mode === 'filter'; +}; + +const isTermsSeries = ( + series: DateHistogramSeries | TermsSeries | FiltersSeries +): series is TermsSeries => { + return series.split_mode === 'terms'; +}; + +const isDateHistogramSeries = ( + series: DateHistogramSeries | TermsSeries | FiltersSeries, + isDateHistogram: boolean +): series is DateHistogramSeries => { + return isDateHistogram && series.split_mode === 'terms'; +}; + export const getBucketsColumns = ( - model: Panel, - series: Series, + model: Panel | undefined, + series: DateHistogramSeries | TermsSeries | FiltersSeries, columns: Column[], dataView: DataView, - isSplit: boolean = false + isSplit: boolean = false, + label?: string, + includeEmptyRowsForDateHistogram: boolean = true ) => { - if (series.split_mode === 'filters' || series.split_mode === 'filter') { + if (isFiltersSeries(series)) { const filterColumn = convertToFiltersColumn(series, true); return getValidColumns([filterColumn]); } - if (series.split_mode === 'terms') { + if (isTermsSeries(series)) { const splitFields = getFieldsForTerms(series.terms_field); const isDateHistogram = isSplitWithDateHistogram(series, splitFields, dataView); if (isDateHistogram === null) { return null; } - if (isDateHistogram) { + if (isDateHistogramSeries(series, isDateHistogram)) { const dateHistogramColumn = convertToDateHistogramColumn(model, series, dataView, { fieldName: splitFields[0], isSplit: true, + includeEmptyRows: includeEmptyRowsForDateHistogram, }); return getValidColumns(dateHistogramColumn); } @@ -73,7 +98,8 @@ export const getBucketsColumns = ( series, columns, dataView, - isSplit + isSplit, + label ); return getValidColumns(termsColumn); } diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/metric/index.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/metric/index.test.ts index 9407599573d9..6d62994be444 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/metric/index.test.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/metric/index.test.ts @@ -6,10 +6,12 @@ * Side Public License, v 1. */ +import { Vis } from '@kbn/visualizations-plugin/public'; import { METRIC_TYPES } from '@kbn/data-plugin/public'; import { stubLogstashDataView } from '@kbn/data-views-plugin/common/data_view.stub'; import { convertToLens } from '.'; import { createPanel, createSeries } from '../lib/__mocks__'; +import { Panel } from '../../../common/types'; const mockGetMetricsColumns = jest.fn(); const mockGetBucketsColumns = jest.fn(); @@ -54,6 +56,10 @@ describe('convertToLens', () => { ], }); + const vis = { + params: model, + } as Vis; + const bucket = { isBucketed: true, isSplit: true, @@ -135,27 +141,27 @@ describe('convertToLens', () => { test('should return null for invalid metrics', async () => { mockIsValidMetrics.mockReturnValue(null); - const result = await convertToLens(model); + const result = await convertToLens(vis); expect(result).toBeNull(); expect(mockIsValidMetrics).toBeCalledTimes(1); }); test('should return null for invalid or unsupported metrics', async () => { mockGetMetricsColumns.mockReturnValue(null); - const result = await convertToLens(model); + const result = await convertToLens(vis); expect(result).toBeNull(); expect(mockGetMetricsColumns).toBeCalledTimes(1); }); test('should return null for invalid or unsupported buckets', async () => { mockGetBucketsColumns.mockReturnValue(null); - const result = await convertToLens(model); + const result = await convertToLens(vis); expect(result).toBeNull(); expect(mockGetBucketsColumns).toBeCalledTimes(1); }); test('should return state for valid model', async () => { - const result = await convertToLens(model); + const result = await convertToLens(vis); expect(result).toBeDefined(); expect(result?.type).toBe('lnsMetric'); expect(mockGetBucketsColumns).toBeCalledTimes(model.series.length); @@ -163,16 +169,16 @@ describe('convertToLens', () => { }); test('should skip hidden series', async () => { - const result = await convertToLens( - createPanel({ + const result = await convertToLens({ + params: createPanel({ series: [ createSeries({ metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], hidden: true, }), ], - }) - ); + }), + } as Vis); expect(result).toBeDefined(); expect(result?.type).toBe('lnsMetric'); expect(mockIsValidMetrics).toBeCalledTimes(0); @@ -185,8 +191,8 @@ describe('convertToLens', () => { indexPattern: { id: 'test-index-pattern-1' }, }); - const result = await convertToLens( - createPanel({ + const result = await convertToLens({ + params: createPanel({ series: [ createSeries({ metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], @@ -197,8 +203,8 @@ describe('convertToLens', () => { hidden: false, }), ], - }) - ); + }), + } as Vis); expect(result).toBeNull(); }); @@ -207,8 +213,8 @@ describe('convertToLens', () => { mockGetBucketsColumns.mockReturnValueOnce([]); mockGetMetricsColumns.mockReturnValueOnce([metric]); - const result = await convertToLens( - createPanel({ + const result = await convertToLens({ + params: createPanel({ series: [ createSeries({ metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], @@ -219,8 +225,8 @@ describe('convertToLens', () => { hidden: false, }), ], - }) - ); + }), + } as Vis); expect(result).toBeNull(); }); @@ -229,8 +235,8 @@ describe('convertToLens', () => { mockGetBucketsColumns.mockReturnValueOnce([bucket2]); mockGetMetricsColumns.mockReturnValueOnce([metric]); - const result = await convertToLens( - createPanel({ + const result = await convertToLens({ + params: createPanel({ series: [ createSeries({ metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], @@ -241,8 +247,8 @@ describe('convertToLens', () => { hidden: false, }), ], - }) - ); + }), + } as Vis); expect(result).toBeNull(); }); @@ -251,8 +257,8 @@ describe('convertToLens', () => { mockGetBucketsColumns.mockReturnValueOnce([bucket]); mockGetMetricsColumns.mockReturnValueOnce([metric]); - const result = await convertToLens( - createPanel({ + const result = await convertToLens({ + params: createPanel({ series: [ createSeries({ metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], @@ -263,8 +269,8 @@ describe('convertToLens', () => { hidden: false, }), ], - }) - ); + }), + } as Vis); expect(result).toBeDefined(); expect(result?.type).toBe('lnsMetric'); expect(mockGetConfigurationForMetric).toBeCalledTimes(1); diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/metric/index.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/metric/index.ts index 149acc513b9f..fbf04a2dcf77 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/metric/index.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/metric/index.ts @@ -22,7 +22,10 @@ import { excludeMetaFromLayers, getUniqueBuckets } from '../utils'; const MAX_SERIES = 2; const MAX_BUCKETS = 2; -export const convertToLens: ConvertTsvbToLensVisualization = async (model, timeRange) => { +export const convertToLens: ConvertTsvbToLensVisualization = async ( + { params: model }, + timeRange +) => { const dataViews = getDataViewsStart(); const seriesNum = model.series.filter((series) => !series.hidden).length; diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/table/index.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/table/index.test.ts new file mode 100644 index 000000000000..37676b302fba --- /dev/null +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/table/index.test.ts @@ -0,0 +1,235 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { TableVisConfiguration } from '@kbn/visualizations-plugin/common'; +import { Vis } from '@kbn/visualizations-plugin/public'; +import { METRIC_TYPES } from '@kbn/data-plugin/public'; +import { stubLogstashDataView } from '@kbn/data-views-plugin/common/data_view.stub'; +import { convertToLens } from '.'; +import { createPanel, createSeries } from '../lib/__mocks__'; +import { Panel } from '../../../common/types'; +import { TSVB_METRIC_TYPES } from '../../../common/enums'; + +const mockConvertToDateHistogramColumn = jest.fn(); +const mockGetMetricsColumns = jest.fn(); +const mockGetBucketsColumns = jest.fn(); +const mockGetConfigurationForTimeseries = jest.fn(); +const mockIsValidMetrics = jest.fn(); +const mockGetDatasourceValue = jest + .fn() + .mockImplementation(() => Promise.resolve(stubLogstashDataView)); +const mockGetDataSourceInfo = jest.fn(); +const mockGetColumnState = jest.fn(); + +jest.mock('../../services', () => ({ + getDataViewsStart: jest.fn(() => mockGetDatasourceValue), +})); + +jest.mock('../lib/convert', () => ({ + excludeMetaFromColumn: jest.fn().mockReturnValue({}), +})); + +jest.mock('../lib/series', () => ({ + getMetricsColumns: jest.fn(() => mockGetMetricsColumns()), + getBucketsColumns: jest.fn(() => mockGetBucketsColumns()), +})); + +jest.mock('../lib/configurations/table', () => ({ + getColumnState: jest.fn(() => mockGetColumnState()), +})); + +jest.mock('../lib/metrics', () => ({ + isValidMetrics: jest.fn(() => mockIsValidMetrics()), + getReducedTimeRange: jest.fn().mockReturnValue('10'), +})); + +jest.mock('../lib/datasource', () => ({ + getDataSourceInfo: jest.fn(() => mockGetDataSourceInfo()), +})); + +describe('convertToLens', () => { + const model = createPanel({ + series: [ + createSeries({ + metrics: [ + { id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }, + { id: 'some-id-1', type: METRIC_TYPES.COUNT }, + ], + }), + ], + }); + + const vis = { + params: model, + uiState: { + get: () => ({}), + }, + } as Vis; + + beforeEach(() => { + mockIsValidMetrics.mockReturnValue(true); + mockGetDataSourceInfo.mockReturnValue({ + indexPatternId: 'test-index-pattern', + timeField: 'timeField', + indexPattern: { id: 'test-index-pattern' }, + }); + mockConvertToDateHistogramColumn.mockReturnValue({}); + mockGetMetricsColumns.mockReturnValue([{}]); + mockGetBucketsColumns.mockReturnValue([{}]); + mockGetConfigurationForTimeseries.mockReturnValue({ layers: [] }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + test('should return null for invalid metrics', async () => { + mockIsValidMetrics.mockReturnValue(null); + const result = await convertToLens(vis); + expect(result).toBeNull(); + expect(mockIsValidMetrics).toBeCalledTimes(1); + }); + + test('should return null for invalid or unsupported metrics', async () => { + mockGetMetricsColumns.mockReturnValue(null); + const result = await convertToLens(vis); + expect(result).toBeNull(); + expect(mockGetMetricsColumns).toBeCalledTimes(1); + }); + + test('should return null if several series have different “Field” + “Aggregate function”', async () => { + const result = await convertToLens({ + params: createPanel({ + series: [createSeries({ aggregate_by: 'new' }), createSeries({ aggregate_by: 'test' })], + }), + uiState: { + get: () => ({}), + }, + } as Vis); + expect(result).toBeNull(); + expect(mockGetBucketsColumns).toBeCalledTimes(1); + }); + + test('should return null if “Aggregate function” is not supported', async () => { + const result = await convertToLens({ + params: createPanel({ + series: [createSeries({ aggregate_by: 'new', aggregate_function: 'cumulative_sum' })], + }), + uiState: { + get: () => ({}), + }, + } as Vis); + expect(result).toBeNull(); + expect(mockGetBucketsColumns).toBeCalledTimes(1); + }); + + test('should return null if model have not visible metrics', async () => { + const result = await convertToLens({ + params: createPanel({ + series: [ + createSeries({ + metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], + hidden: true, + }), + ], + }), + uiState: { + get: () => ({}), + }, + } as Vis); + expect(result).toBeNull(); + }); + + test('should return null if only static value is visible metric', async () => { + mockGetMetricsColumns.mockReturnValue([ + { columnId: 'metric-column-1', operationType: 'static_value' }, + ]); + const result = await convertToLens({ + params: createPanel({ + series: [ + createSeries({ + metrics: [{ id: 'some-id', type: TSVB_METRIC_TYPES.STATIC }], + hidden: true, + }), + ], + }), + uiState: { + get: () => ({}), + }, + } as Vis); + expect(result).toBeNull(); + }); + + test('should return state for valid model', async () => { + const result = await convertToLens(vis); + expect(result).toBeDefined(); + expect(result?.type).toBe('lnsDatatable'); + expect(mockGetBucketsColumns).toBeCalledTimes(1); + // every series + group by + expect(mockGetColumnState).toBeCalledTimes(model.series.length + 1); + }); + + test('should return state for valid model with “Field” + “Aggregate function”', async () => { + const result = await convertToLens({ + params: createPanel({ + series: [createSeries({ aggregate_by: 'new', aggregate_function: 'sum' })], + }), + uiState: { + get: () => ({}), + }, + } as Vis); + expect(result).toBeDefined(); + expect(result?.type).toBe('lnsDatatable'); + expect(mockGetBucketsColumns).toBeCalledTimes(2); + // every series + group by + (“Field” + “Aggregate function”) + expect(mockGetColumnState).toBeCalledTimes(model.series.length + 2); + }); + + test('should return correct sorting config', async () => { + mockGetMetricsColumns.mockReturnValue([{ columnId: 'metric-column-1' }]); + const result = await convertToLens({ + params: createPanel({ + series: [createSeries({ id: 'test' })], + }), + uiState: { + get: () => ({ sort: { order: 'decs', column: 'test' } }), + }, + } as Vis); + expect(result).toBeDefined(); + expect(result?.type).toBe('lnsDatatable'); + expect((result?.configuration as TableVisConfiguration).sorting).toEqual({ + direction: 'decs', + columnId: 'metric-column-1', + }); + expect(mockGetBucketsColumns).toBeCalledTimes(1); + // every series + group by + expect(mockGetColumnState).toBeCalledTimes(model.series.length + 1); + }); + + test('should skip hidden series', async () => { + const result = await convertToLens({ + params: createPanel({ + series: [ + createSeries({ + metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], + hidden: true, + }), + createSeries({ + metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], + }), + ], + }), + uiState: { + get: () => ({}), + }, + } as Vis); + expect(result).toBeDefined(); + expect(result?.type).toBe('lnsDatatable'); + expect(mockIsValidMetrics).toBeCalledTimes(1); + }); +}); diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/table/index.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/table/index.ts new file mode 100644 index 000000000000..0219d1080724 --- /dev/null +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/table/index.ts @@ -0,0 +1,183 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import uuid from 'uuid'; +import { parseTimeShift } from '@kbn/data-plugin/common'; +import { getIndexPatternIds, Layer } from '@kbn/visualizations-plugin/common/convert_to_lens'; +import { PANEL_TYPES } from '../../../common/enums'; +import { getDataViewsStart } from '../../services'; +import { getColumnState } from '../lib/configurations/table'; +import { getDataSourceInfo } from '../lib/datasource'; +import { getMetricsColumns, getBucketsColumns } from '../lib/series'; +import { getReducedTimeRange, isValidMetrics } from '../lib/metrics'; +import { ConvertTsvbToLensVisualization } from '../types'; +import { Layer as ExtendedLayer, excludeMetaFromColumn, Column } from '../lib/convert'; + +const excludeMetaFromLayers = (layers: Record): Record => { + const newLayers: Record = {}; + Object.entries(layers).forEach(([layerId, layer]) => { + const columns = layer.columns.map(excludeMetaFromColumn); + newLayers[layerId] = { ...layer, columns }; + }); + + return newLayers; +}; + +export const convertToLens: ConvertTsvbToLensVisualization = async ( + { params: model, uiState }, + timeRange +) => { + const columnStates = []; + const dataViews = getDataViewsStart(); + const seriesNum = model.series.filter((series) => !series.hidden).length; + const sortConfig = uiState.get('table')?.sort ?? {}; + + const datasourceInfo = await getDataSourceInfo( + model.index_pattern, + model.time_field, + false, + undefined, + undefined, + dataViews + ); + + if (!datasourceInfo) { + return null; + } + + const { indexPatternId, indexPattern } = datasourceInfo; + + const commonBucketsColumns = getBucketsColumns( + undefined, + { + split_mode: 'terms', + terms_field: model.pivot_id, + terms_size: model.pivot_rows ? model.pivot_rows.toString() : undefined, + }, + [], + indexPattern!, + false, + model.pivot_label, + false + ); + + if (!commonBucketsColumns) { + return null; + } + + const sortConfiguration = { + columnId: commonBucketsColumns[0].columnId, + direction: sortConfig.order, + }; + + columnStates.push(getColumnState(commonBucketsColumns[0].columnId)); + + let bucketsColumns: Column[] | null = []; + + if ( + !model.series.every( + (s) => + ((!s.aggregate_by && !model.series[0].aggregate_by) || + s.aggregate_by === model.series[0].aggregate_by) && + ((!s.aggregate_function && !model.series[0].aggregate_function) || + s.aggregate_function === model.series[0].aggregate_function) + ) + ) { + return null; + } + + if (model.series[0].aggregate_by) { + if ( + !model.series[0].aggregate_function || + !['sum', 'mean', 'min', 'max'].includes(model.series[0].aggregate_function) + ) { + return null; + } + bucketsColumns = getBucketsColumns( + undefined, + { + split_mode: 'terms', + terms_field: model.series[0].aggregate_by, + }, + [], + indexPattern!, + false + ); + if (bucketsColumns === null) { + return null; + } + + columnStates.push( + getColumnState( + bucketsColumns[0].columnId, + model.series[0].aggregate_function === 'mean' ? 'avg' : model.series[0].aggregate_function + ) + ); + } + + const metrics = []; + + // handle multiple layers/series + for (const [_, series] of model.series.entries()) { + if (series.hidden) { + continue; + } + + // not valid time shift + if (series.offset_time && parseTimeShift(series.offset_time) === 'invalid') { + return null; + } + + if (!isValidMetrics(series.metrics, PANEL_TYPES.TABLE, series.time_range_mode)) { + return null; + } + + const reducedTimeRange = getReducedTimeRange(model, series, timeRange); + + // handle multiple metrics + const metricsColumns = getMetricsColumns(series, indexPattern!, seriesNum, { + reducedTimeRange, + }); + if (!metricsColumns) { + return null; + } + + columnStates.push(getColumnState(metricsColumns[0].columnId, undefined, series)); + + if (sortConfig.column === series.id) { + sortConfiguration.columnId = metricsColumns[0].columnId; + } + + metrics.push(...metricsColumns); + } + + if (!metrics.length || metrics.every((metric) => metric.operationType === 'static_value')) { + return null; + } + + const extendedLayer: ExtendedLayer = { + indexPatternId: indexPatternId as string, + layerId: uuid(), + columns: [...metrics, ...commonBucketsColumns, ...bucketsColumns], + columnOrder: [], + }; + + const layers = Object.values(excludeMetaFromLayers({ 0: extendedLayer })); + + return { + type: 'lnsDatatable', + layers, + configuration: { + columns: columnStates, + layerId: extendedLayer.layerId, + layerType: 'data', + sorting: sortConfiguration, + }, + indexPatternIds: getIndexPatternIds(layers), + }; +}; diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.test.ts index c81db38e0538..64fee3484b3b 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.test.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.test.ts @@ -6,10 +6,12 @@ * Side Public License, v 1. */ +import { Vis } from '@kbn/visualizations-plugin/public'; import { METRIC_TYPES } from '@kbn/data-plugin/public'; import { stubLogstashDataView } from '@kbn/data-views-plugin/common/data_view.stub'; import { convertToLens } from '.'; import { createPanel, createSeries } from '../lib/__mocks__'; +import { Panel } from '../../../common/types'; const mockConvertToDateHistogramColumn = jest.fn(); const mockGetMetricsColumns = jest.fn(); @@ -60,6 +62,10 @@ describe('convertToLens', () => { ], }); + const vis = { + params: model, + } as Vis; + beforeEach(() => { mockIsValidMetrics.mockReturnValue(true); mockGetDataSourceInfo.mockReturnValue({ @@ -79,35 +85,35 @@ describe('convertToLens', () => { test('should return null for invalid metrics', async () => { mockIsValidMetrics.mockReturnValue(null); - const result = await convertToLens(model); + const result = await convertToLens(vis); expect(result).toBeNull(); expect(mockIsValidMetrics).toBeCalledTimes(1); }); test('should return null for empty time field', async () => { mockGetDataSourceInfo.mockReturnValue({ timeField: null }); - const result = await convertToLens(model); + const result = await convertToLens(vis); expect(result).toBeNull(); expect(mockGetDataSourceInfo).toBeCalledTimes(1); }); test('should return null for invalid date histogram', async () => { mockConvertToDateHistogramColumn.mockReturnValue(null); - const result = await convertToLens(model); + const result = await convertToLens(vis); expect(result).toBeNull(); expect(mockConvertToDateHistogramColumn).toBeCalledTimes(1); }); test('should return null for invalid or unsupported metrics', async () => { mockGetMetricsColumns.mockReturnValue(null); - const result = await convertToLens(model); + const result = await convertToLens(vis); expect(result).toBeNull(); expect(mockGetMetricsColumns).toBeCalledTimes(1); }); test('should return null for invalid or unsupported buckets', async () => { mockGetBucketsColumns.mockReturnValue(null); - const result = await convertToLens(model); + const result = await convertToLens(vis); expect(result).toBeNull(); expect(mockGetBucketsColumns).toBeCalledTimes(1); }); @@ -119,14 +125,14 @@ describe('convertToLens', () => { operationType: 'static_value', }, ]); - const result = await convertToLens(model); + const result = await convertToLens(vis); expect(result).toBeNull(); expect(mockGetMetricsColumns).toBeCalledTimes(1); expect(mockGetBucketsColumns).toBeCalledTimes(1); }); test('should return state for valid model', async () => { - const result = await convertToLens(model); + const result = await convertToLens(vis); expect(result).toBeDefined(); expect(result?.type).toBe('lnsXY'); expect(mockGetBucketsColumns).toBeCalledTimes(model.series.length); @@ -134,16 +140,16 @@ describe('convertToLens', () => { }); test('should skip hidden series', async () => { - const result = await convertToLens( - createPanel({ + const result = await convertToLens({ + params: createPanel({ series: [ createSeries({ metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], hidden: true, }), ], - }) - ); + }), + } as Vis); expect(result).toBeDefined(); expect(result?.type).toBe('lnsXY'); expect(mockIsValidMetrics).toBeCalledTimes(0); diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.ts index ef678fcc2dab..a08b7113c4a7 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.ts @@ -14,7 +14,6 @@ import { } from '@kbn/visualizations-plugin/common/convert_to_lens'; import uuid from 'uuid'; import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; -import { Panel } from '../../../common/types'; import { PANEL_TYPES } from '../../../common/enums'; import { getDataViewsStart } from '../../services'; import { getDataSourceInfo } from '../lib/datasource'; @@ -41,7 +40,7 @@ const excludeMetaFromLayers = (layers: Record): Record { +export const convertToLens: ConvertTsvbToLensVisualization = async ({ params: model }) => { const dataViews: DataViewsPublicPluginStart = getDataViewsStart(); const extendedLayers: Record = {}; const seriesNum = model.series.filter((series) => !series.hidden).length; diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.test.ts index 7e4776f10ac9..646323a6691d 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.test.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.test.ts @@ -6,10 +6,12 @@ * Side Public License, v 1. */ +import { Vis } from '@kbn/visualizations-plugin/public'; import { METRIC_TYPES } from '@kbn/data-plugin/public'; import { stubLogstashDataView } from '@kbn/data-views-plugin/common/data_view.stub'; import { convertToLens } from '.'; import { createPanel, createSeries } from '../lib/__mocks__'; +import { Panel } from '../../../common/types'; const mockGetMetricsColumns = jest.fn(); const mockGetBucketsColumns = jest.fn(); @@ -59,6 +61,10 @@ describe('convertToLens', () => { ], }); + const vis = { + params: model, + } as Vis; + beforeEach(() => { mockIsValidMetrics.mockReturnValue(true); mockGetDataSourceInfo.mockReturnValue({ @@ -77,27 +83,27 @@ describe('convertToLens', () => { test('should return null for invalid metrics', async () => { mockIsValidMetrics.mockReturnValue(null); - const result = await convertToLens(model); + const result = await convertToLens(vis); expect(result).toBeNull(); expect(mockIsValidMetrics).toBeCalledTimes(1); }); test('should return null for invalid or unsupported metrics', async () => { mockGetMetricsColumns.mockReturnValue(null); - const result = await convertToLens(model); + const result = await convertToLens(vis); expect(result).toBeNull(); expect(mockGetMetricsColumns).toBeCalledTimes(1); }); test('should return null for invalid or unsupported buckets', async () => { mockGetBucketsColumns.mockReturnValue(null); - const result = await convertToLens(model); + const result = await convertToLens(vis); expect(result).toBeNull(); expect(mockGetBucketsColumns).toBeCalledTimes(1); }); test('should return state for valid model', async () => { - const result = await convertToLens(model); + const result = await convertToLens(vis); expect(result).toBeDefined(); expect(result?.type).toBe('lnsXY'); expect(mockGetBucketsColumns).toBeCalledTimes(model.series.length); @@ -105,16 +111,16 @@ describe('convertToLens', () => { }); test('should skip hidden series', async () => { - const result = await convertToLens( - createPanel({ + const result = await convertToLens({ + params: createPanel({ series: [ createSeries({ metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], hidden: true, }), ], - }) - ); + }), + } as Vis); expect(result).toBeDefined(); expect(result?.type).toBe('lnsXY'); expect(mockIsValidMetrics).toBeCalledTimes(0); diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.ts index 130646f72f12..9505b7f5f0c7 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.ts @@ -28,7 +28,10 @@ const excludeMetaFromLayers = (layers: Record): Record { +export const convertToLens: ConvertTsvbToLensVisualization = async ( + { params: model }, + timeRange +) => { const dataViews = getDataViewsStart(); const extendedLayers: Record = {}; const seriesNum = model.series.filter((series) => !series.hidden).length; diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/types.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/types.ts index 69a90c7864eb..9f00a669ea5c 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/types.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/types.ts @@ -6,18 +6,22 @@ * Side Public License, v 1. */ +import { Vis } from '@kbn/visualizations-plugin/public'; import { MetricVisConfiguration, NavigateToLensContext, XYConfiguration, + TableVisConfiguration, } from '@kbn/visualizations-plugin/common'; import { TimeRange } from '@kbn/data-plugin/common'; import type { Panel } from '../../common/types'; export type ConvertTsvbToLensVisualization = ( - model: Panel, + vis: Vis, timeRange?: TimeRange -) => Promise | null>; +) => Promise | null>; export interface Filter { kql?: string | { [key: string]: any } | undefined; diff --git a/src/plugins/vis_types/timeseries/public/metrics_type.ts b/src/plugins/vis_types/timeseries/public/metrics_type.ts index 3bf9f9b90bf1..43be3ee3004f 100644 --- a/src/plugins/vis_types/timeseries/public/metrics_type.ts +++ b/src/plugins/vis_types/timeseries/public/metrics_type.ts @@ -171,15 +171,13 @@ export const metricsVisDefinition: VisTypeDefinition< return { canNavigateToLens: Boolean( vis?.params - ? await convertTSVBtoLensConfiguration(vis.params as Panel, timeFilter?.getAbsoluteTime()) + ? await convertTSVBtoLensConfiguration(vis, timeFilter?.getAbsoluteTime()) : null ), }; }, navigateToLens: async (vis, timeFilter) => - vis?.params - ? await convertTSVBtoLensConfiguration(vis?.params as Panel, timeFilter?.getAbsoluteTime()) - : null, + vis?.params ? await convertTSVBtoLensConfiguration(vis, timeFilter?.getAbsoluteTime()) : null, inspectorAdapters: () => ({ requests: new RequestAdapter(), diff --git a/src/plugins/vis_types/vislib/public/vis_controller.tsx b/src/plugins/vis_types/vislib/public/vis_controller.tsx index af9dda7ccf01..40a518a8c0c9 100644 --- a/src/plugins/vis_types/vislib/public/vis_controller.tsx +++ b/src/plugins/vis_types/vislib/public/vis_controller.tsx @@ -9,13 +9,10 @@ import $ from 'jquery'; import React, { RefObject } from 'react'; -import { METRIC_TYPE } from '@kbn/analytics'; import { toMountPoint } from '@kbn/kibana-react-plugin/public'; import { ChartsPluginSetup } from '@kbn/charts-plugin/public'; import type { PersistedState } from '@kbn/visualizations-plugin/public'; import { IInterpreterRenderHandlers } from '@kbn/expressions-plugin/public'; -import { KibanaExecutionContext } from '@kbn/core-execution-context-common'; -import { getUsageCollectionStart } from './services'; import { VisTypeVislibCoreSetup } from './plugin'; import { VisLegend, CUSTOM_LEGEND_VIS_TYPES } from './vislib/components/legend'; import { BasicVislibParams } from './types'; @@ -30,38 +27,6 @@ const legendClassName = { export type VislibVisController = InstanceType>; -/** @internal **/ -const extractContainerType = (context?: KibanaExecutionContext): string | undefined => { - if (context) { - const recursiveGet = (item: KibanaExecutionContext): KibanaExecutionContext | undefined => { - if (item.type) { - return item; - } else if (item.child) { - return recursiveGet(item.child); - } - }; - return recursiveGet(context)?.type; - } -}; - -const renderComplete = ( - visParams: BasicVislibParams | PieVisParams, - handlers: IInterpreterRenderHandlers -) => { - const usageCollection = getUsageCollectionStart(); - const containerType = extractContainerType(handlers.getExecutionContext()); - - if (usageCollection && containerType) { - usageCollection.reportUiCounter( - containerType, - METRIC_TYPE.COUNT, - `render_agg_based_${visParams.type}` - ); - } - - handlers.done(); -}; - export const createVislibVisController = ( core: VisTypeVislibCoreSetup, charts: ChartsPluginSetup @@ -99,7 +64,8 @@ export const createVislibVisController = ( async render( esResponse: any, visParams: BasicVislibParams | PieVisParams, - handlers: IInterpreterRenderHandlers + handlers: IInterpreterRenderHandlers, + renderComplete: (() => void) | undefined ): Promise { if (this.vislibVis) { this.destroy(false); @@ -109,7 +75,7 @@ export const createVislibVisController = ( this.chartEl.dataset.vislibChartType = visParams.type; if (this.el.clientWidth === 0 || this.el.clientHeight === 0) { - renderComplete(visParams, handlers); + renderComplete?.(); return; } @@ -133,7 +99,7 @@ export const createVislibVisController = ( this.mountLegend(esResponse, visParams, fireEvent, uiState as PersistedState); } - renderComplete(visParams, handlers); + renderComplete?.(); }); this.removeListeners = () => { diff --git a/src/plugins/vis_types/vislib/public/vis_wrapper.tsx b/src/plugins/vis_types/vislib/public/vis_wrapper.tsx index a800abd3d11f..5a68e74b5a48 100644 --- a/src/plugins/vis_types/vislib/public/vis_wrapper.tsx +++ b/src/plugins/vis_types/vislib/public/vis_wrapper.tsx @@ -6,20 +6,23 @@ * Side Public License, v 1. */ -import React, { useEffect, useMemo, useRef } from 'react'; -import { EuiResizeObserver } from '@elastic/eui'; +import React, { useEffect, useMemo, useRef, useCallback } from 'react'; +import { EuiResizeObserver, EuiResizeObserverProps } from '@elastic/eui'; import { debounce } from 'lodash'; import { IInterpreterRenderHandlers } from '@kbn/expressions-plugin/public'; import type { PersistedState } from '@kbn/visualizations-plugin/public'; import { ChartsPluginSetup } from '@kbn/charts-plugin/public'; +import { KibanaExecutionContext } from '@kbn/core-execution-context-common'; +import { METRIC_TYPE } from '@kbn/analytics'; import { VislibRenderValue } from './vis_type_vislib_vis_fn'; import { createVislibVisController, VislibVisController } from './vis_controller'; import { VisTypeVislibCoreSetup } from './plugin'; import { PieRenderValue } from './pie_fn'; import './index.scss'; +import { getUsageCollectionStart } from './services'; type VislibWrapperProps = (VislibRenderValue | PieRenderValue) & { core: VisTypeVislibCoreSetup; @@ -27,20 +30,67 @@ type VislibWrapperProps = (VislibRenderValue | PieRenderValue) & { handlers: IInterpreterRenderHandlers; }; +/** @internal **/ +const extractContainerType = (context?: KibanaExecutionContext): string | undefined => { + if (context) { + const recursiveGet = (item: KibanaExecutionContext): KibanaExecutionContext | undefined => { + if (item.type) { + return item; + } else if (item.child) { + return recursiveGet(item.child); + } + }; + return recursiveGet(context)?.type; + } +}; + const VislibWrapper = ({ core, charts, visData, visConfig, handlers }: VislibWrapperProps) => { const chartDiv = useRef(null); const visController = useRef(null); + const skipRenderComplete = useRef(true); + + const renderComplete = useMemo( + () => () => { + const usageCollection = getUsageCollectionStart(); + const containerType = extractContainerType(handlers.getExecutionContext()); + + if (usageCollection && containerType) { + usageCollection.reportUiCounter( + containerType, + METRIC_TYPE.COUNT, + `render_agg_based_${visConfig!.type}` + ); + } + handlers.done(); + }, + [handlers, visConfig] + ); - const updateChart = useMemo( + const renderChart = useMemo( () => debounce(() => { if (visController.current) { - visController.current.render(visData, visConfig, handlers); + visController.current.render( + visData, + visConfig, + handlers, + skipRenderComplete.current ? undefined : renderComplete + ); } + skipRenderComplete.current = true; }, 100), - [visConfig, visData, handlers] + [handlers, renderComplete, skipRenderComplete, visConfig, visData] ); + const onResize: EuiResizeObserverProps['onResize'] = useCallback(() => { + renderChart(); + }, [renderChart]); + + useEffect(() => { + skipRenderComplete.current = false; + renderChart(); + }, [renderChart]); + useEffect(() => { if (chartDiv.current) { const Controller = createVislibVisController(core, charts); @@ -52,22 +102,20 @@ const VislibWrapper = ({ core, charts, visData, visConfig, handlers }: VislibWra }; }, [core, charts]); - useEffect(updateChart, [updateChart]); - useEffect(() => { if (handlers.uiState) { const uiState = handlers.uiState as PersistedState; - uiState.on('change', updateChart); + uiState.on('change', renderChart); return () => { - uiState?.off('change', updateChart); + uiState?.off('change', renderChart); }; } - }, [handlers.uiState, updateChart]); + }, [handlers.uiState, renderChart]); return ( - + {(resizeRef) => (
diff --git a/src/plugins/visualizations/common/convert_to_lens/types/configurations.ts b/src/plugins/visualizations/common/convert_to_lens/types/configurations.ts index 4f7a5ad71521..f62f61f0c50a 100644 --- a/src/plugins/visualizations/common/convert_to_lens/types/configurations.ts +++ b/src/plugins/visualizations/common/convert_to_lens/types/configurations.ts @@ -183,6 +183,7 @@ export interface ColumnState { summaryRow?: 'none' | 'sum' | 'avg' | 'count' | 'min' | 'max'; alignment?: 'left' | 'right' | 'center'; collapseFn?: CollapseFunction; + palette?: PaletteOutput; } export interface TableVisConfiguration { diff --git a/src/plugins/visualizations/public/visualize_app/components/visualize_top_nav.tsx b/src/plugins/visualizations/public/visualize_app/components/visualize_top_nav.tsx index e7512c6dd647..0111c9026397 100644 --- a/src/plugins/visualizations/public/visualize_app/components/visualize_top_nav.tsx +++ b/src/plugins/visualizations/public/visualize_app/components/visualize_top_nav.tsx @@ -114,6 +114,7 @@ const TopNav = ({ vis.type, vis.params, uiStateJSON?.vis, + uiStateJSON?.table, vis.data.indexPattern, ]); diff --git a/test/analytics/tests/instrumented_events/from_the_browser/core_context_providers.ts b/test/analytics/tests/instrumented_events/from_the_browser/core_context_providers.ts index 3d0b018156ce..79e43f0df086 100644 --- a/test/analytics/tests/instrumented_events/from_the_browser/core_context_providers.ts +++ b/test/analytics/tests/instrumented_events/from_the_browser/core_context_providers.ts @@ -91,5 +91,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(event.context).not.to.have.property('cloudId'); } }); + + it('should have the properties provided by the "viewport_size" context provider', async () => { + expect(event.context).to.have.property('viewport_width'); + expect(event.context.viewport_width).to.be.a('number'); + expect(event.context).to.have.property('viewport_height'); + expect(event.context.viewport_height).to.be.a('number'); + }); }); } diff --git a/test/analytics/tests/instrumented_events/from_the_browser/index.ts b/test/analytics/tests/instrumented_events/from_the_browser/index.ts index 7c6ee6ff1e2a..c69f091cc454 100644 --- a/test/analytics/tests/instrumented_events/from_the_browser/index.ts +++ b/test/analytics/tests/instrumented_events/from_the_browser/index.ts @@ -15,5 +15,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./loaded_kibana')); loadTestFile(require.resolve('./loaded_dashboard')); loadTestFile(require.resolve('./core_context_providers')); + loadTestFile(require.resolve('./viewport_resize')); }); } diff --git a/test/analytics/tests/instrumented_events/from_the_browser/viewport_resize.ts b/test/analytics/tests/instrumented_events/from_the_browser/viewport_resize.ts new file mode 100644 index 000000000000..f5e084810a37 --- /dev/null +++ b/test/analytics/tests/instrumented_events/from_the_browser/viewport_resize.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../services'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const ebtUIHelper = getService('kibana_ebt_ui'); + const browser = getService('browser'); + const { common } = getPageObjects(['common']); + + describe('Event "viewport_resize"', () => { + beforeEach(async () => { + // Navigating to `home` with the Welcome prompt because some runs were flaky + // as we handle the Welcome screen only if the login prompt pops up. + // Otherwise, it stays in the Welcome screen :/ + await common.navigateToApp('home'); + }); + + it('should emit a "viewport_resize" event when the browser is resized', async () => { + const events = await ebtUIHelper.getEvents(1, { + eventTypes: ['viewport_resize'], + withTimeoutMs: 100, + }); + expect(events.length).to.be(0); + // Resize the window + await browser.setWindowSize(500, 500); + const { height, width } = await browser.getWindowSize(); + expect(height).to.eql(500); + expect(width).to.eql(500); + + const actualInnerHeight = await browser.execute(() => window.innerHeight); + expect(actualInnerHeight <= height).to.be(true); // The address bar takes some space when not running on HEADLESS + + const [event] = await ebtUIHelper.getEvents(1, { eventTypes: ['viewport_resize'] }); + expect(event.event_type).to.eql('viewport_resize'); + expect(event.properties).to.eql({ + viewport_width: 500, + viewport_height: actualInnerHeight, + }); + + // Validating that the context is also updated + expect(event.context.viewport_width).to.be(500); + expect(event.context.viewport_height).to.be(actualInnerHeight); + }); + }); +} diff --git a/test/functional/apps/dashboard_elements/controls/control_group_chaining.ts b/test/functional/apps/dashboard_elements/controls/control_group_chaining.ts index a27a1a4814cf..89a435430f9e 100644 --- a/test/functional/apps/dashboard_elements/controls/control_group_chaining.ts +++ b/test/functional/apps/dashboard_elements/controls/control_group_chaining.ts @@ -123,6 +123,27 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ]); }); + it('Excluding selections in the first control will validate the second and third controls', async () => { + await dashboardControls.optionsListOpenPopover(controlIds[0]); + await dashboardControls.optionsListPopoverSetIncludeSelections(false); + await dashboardControls.optionsListEnsurePopoverIsClosed(controlIds[0]); + + await ensureAvailableOptionsEql(controlIds[1], ['Tiger', 'sylvester']); + await ensureAvailableOptionsEql(controlIds[2], ['meow', 'hiss']); + }); + + it('Excluding all options of first control removes all options in second and third controls', async () => { + await dashboardControls.optionsListOpenPopover(controlIds[0]); + await dashboardControls.optionsListPopoverSelectOption('cat'); + await dashboardControls.optionsListEnsurePopoverIsClosed(controlIds[0]); + + await dashboardControls.optionsListOpenPopover(controlIds[1]); + expect(await dashboardControls.optionsListPopoverGetAvailableOptionsCount()).to.be(0); + await dashboardControls.optionsListOpenPopover(controlIds[2]); + expect(await dashboardControls.optionsListPopoverGetAvailableOptionsCount()).to.be(0); + await dashboardControls.optionsListEnsurePopoverIsClosed(controlIds[2]); + }); + describe('Hierarchical chaining off', async () => { before(async () => { await dashboardControls.updateChainingSystem('NONE'); @@ -130,6 +151,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('Selecting an option in the first Options List will not filter the second or third controls', async () => { await dashboardControls.optionsListOpenPopover(controlIds[0]); + await dashboardControls.optionsListPopoverSetIncludeSelections(true); await dashboardControls.optionsListPopoverSelectOption('cat'); await dashboardControls.optionsListEnsurePopoverIsClosed(controlIds[0]); diff --git a/test/functional/apps/dashboard_elements/controls/options_list.ts b/test/functional/apps/dashboard_elements/controls/options_list.ts index 0c8dea528d9e..091f893eec2c 100644 --- a/test/functional/apps/dashboard_elements/controls/options_list.ts +++ b/test/functional/apps/dashboard_elements/controls/options_list.ts @@ -385,6 +385,27 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(await pieChart.getPieSliceCount()).to.be(2); }); + it('excluding selections has expected results', async () => { + await dashboard.clickQuickSave(); + await dashboard.waitForRenderComplete(); + + await dashboardControls.optionsListOpenPopover(controlId); + await dashboardControls.optionsListPopoverSetIncludeSelections(false); + await dashboard.waitForRenderComplete(); + + expect(await pieChart.getPieSliceCount()).to.be(5); + await dashboard.clearUnsavedChanges(); + }); + + it('including selections has expected results', async () => { + await dashboardControls.optionsListOpenPopover(controlId); + await dashboardControls.optionsListPopoverSetIncludeSelections(true); + await dashboard.waitForRenderComplete(); + + expect(await pieChart.getPieSliceCount()).to.be(2); + await dashboard.clearUnsavedChanges(); + }); + it('Can mark multiple selections invalid with Filter', async () => { await filterBar.addFilter('sound.keyword', 'is', ['hiss']); await dashboard.waitForRenderComplete(); diff --git a/test/functional/apps/discover/group1/_discover.ts b/test/functional/apps/discover/group1/_discover.ts index d6035d0a28a6..60f2a54dd01f 100644 --- a/test/functional/apps/discover/group1/_discover.ts +++ b/test/functional/apps/discover/group1/_discover.ts @@ -369,14 +369,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.timePicker.setDefaultAbsoluteRange(); await PageObjects.header.waitUntilLoadingHasFinished(); const dataViewId = await PageObjects.discover.getCurrentDataViewId(); - const originalUrl = await browser.getCurrentUrl(); const newUrl = originalUrl.replace(dataViewId, 'invalid-data-view-id'); await browser.get(newUrl); - await PageObjects.header.waitUntilLoadingHasFinished(); - expect(await browser.getCurrentUrl()).to.be(originalUrl); - expect(await testSubjects.exists('dscDataViewNotFoundShowDefaultWarning')).to.be(true); + await retry.try(async () => { + expect(await browser.getCurrentUrl()).to.be(originalUrl); + expect(await testSubjects.exists('dscDataViewNotFoundShowDefaultWarning')).to.be(true); + }); }); it('should show a warning and fall back to the current data view if the URL is updated to an invalid data view ID', async () => { @@ -384,14 +384,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.timePicker.setDefaultAbsoluteRange(); const originalHash = await browser.execute<[], string>('return window.location.hash'); const dataViewId = await PageObjects.discover.getCurrentDataViewId(); - const newHash = originalHash.replace(dataViewId, 'invalid-data-view-id'); await browser.execute(`window.location.hash = "${newHash}"`); await PageObjects.header.waitUntilLoadingHasFinished(); - - const currentHash = await browser.execute<[], string>('return window.location.hash'); - expect(currentHash).to.be(originalHash); - expect(await testSubjects.exists('dscDataViewNotFoundShowSavedWarning')).to.be(true); + await retry.try(async () => { + const currentHash = await browser.execute<[], string>('return window.location.hash'); + expect(currentHash).to.be(originalHash); + expect(await testSubjects.exists('dscDataViewNotFoundShowSavedWarning')).to.be(true); + }); }); }); }); diff --git a/test/functional/apps/management/_index_pattern_filter.ts b/test/functional/apps/management/_index_pattern_filter.ts index afa64c474d39..e1e39e93cc6c 100644 --- a/test/functional/apps/management/_index_pattern_filter.ts +++ b/test/functional/apps/management/_index_pattern_filter.ts @@ -15,7 +15,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['settings']); const esArchiver = getService('esArchiver'); - describe('index pattern filter', function describeIndexTests() { + // Failing: See https://github.com/elastic/kibana/issues/143109 + describe.skip('index pattern filter', function describeIndexTests() { before(async function () { await esArchiver.emptyKibanaIndex(); await kibanaServer.uiSettings.replace({}); diff --git a/test/functional/page_objects/dashboard_page_controls.ts b/test/functional/page_objects/dashboard_page_controls.ts index 15510a07dccb..1e04ebb467d8 100644 --- a/test/functional/page_objects/dashboard_page_controls.ts +++ b/test/functional/page_objects/dashboard_page_controls.ts @@ -376,6 +376,19 @@ export class DashboardPageControls extends FtrService { await this.testSubjects.click(`optionsList-control-clear-all-selections`); } + public async optionsListPopoverSetIncludeSelections(include: boolean) { + this.log.debug(`exclude selections`); + await this.optionsListPopoverAssertOpen(); + + const buttonGroup = await this.testSubjects.find('optionsList__includeExcludeButtonGroup'); + await ( + await this.find.descendantDisplayedByCssSelector( + include ? '[data-text="Include"]' : '[data-text="Exclude"]', + buttonGroup + ) + ).click(); + } + /* ----------------------------------------------------------- Control editor flyout ----------------------------------------------------------- */ diff --git a/test/functional/page_objects/discover_page.ts b/test/functional/page_objects/discover_page.ts index 85c93c0fc284..44a29441d270 100644 --- a/test/functional/page_objects/discover_page.ts +++ b/test/functional/page_objects/discover_page.ts @@ -243,6 +243,10 @@ export class DiscoverPageObject extends FtrService { return await this.testSubjects.getVisibleText('unifiedHistogramQueryHits'); } + public async getHitCountInt() { + return parseInt(await this.getHitCount(), 10); + } + public async getDocHeader() { const table = await this.getDocTable(); const docHeader = await table.getHeaders(); diff --git a/test/functional/page_objects/visual_builder_page.ts b/test/functional/page_objects/visual_builder_page.ts index edbd53e80d8c..1aacde4127f3 100644 --- a/test/functional/page_objects/visual_builder_page.ts +++ b/test/functional/page_objects/visual_builder_page.ts @@ -659,6 +659,28 @@ export class VisualBuilderPageObject extends FtrService { await this.comboBox.setElement(fieldEl, field); } + public async setFieldForAggregateBy(field: string): Promise { + const aggregateBy = await this.testSubjects.find('tsvbAggregateBySelect'); + + await this.retry.try(async () => { + await this.comboBox.setElement(aggregateBy, field); + if (!(await this.comboBox.isOptionSelected(aggregateBy, field))) { + throw new Error(`aggregate by field - ${field} is not selected`); + } + }); + } + + public async setFunctionForAggregateFunction(func: string): Promise { + const aggregateFunction = await this.testSubjects.find('tsvbAggregateFunctionCombobox'); + + await this.retry.try(async () => { + await this.comboBox.setElement(aggregateFunction, func); + if (!(await this.comboBox.isOptionSelected(aggregateFunction, func))) { + throw new Error(`aggregate function - ${func} is not selected`); + } + }); + } + public async checkFieldForAggregationValidity(aggNth: number = 0): Promise { const fieldEl = await this.getFieldForAggregation(aggNth); diff --git a/versions.json b/versions.json index 30514180e5c1..3265499716cd 100644 --- a/versions.json +++ b/versions.json @@ -20,7 +20,7 @@ "previousMinor": true }, { - "version": "7.17.7", + "version": "7.17.8", "branch": "7.17", "previousMajor": true } diff --git a/x-pack/examples/files_example/public/components/app.tsx b/x-pack/examples/files_example/public/components/app.tsx index d3dfbdeb7187..afdf8be1f4f6 100644 --- a/x-pack/examples/files_example/public/components/app.tsx +++ b/x-pack/examples/files_example/public/components/app.tsx @@ -36,8 +36,10 @@ interface FilesExampleAppDeps { type ListResponse = FilesClientResponses['list']; export const FilesExampleApp = ({ files, notifications }: FilesExampleAppDeps) => { - const { data, isLoading, error, refetch } = useQuery(['files'], () => - files.example.list() + const { data, isLoading, error, refetch } = useQuery( + ['files'], + () => files.example.list(), + { refetchOnWindowFocus: false } ); const [showUploadModal, setShowUploadModal] = useState(false); const [showFilePickerModal, setShowFilePickerModal] = useState(false); diff --git a/x-pack/examples/files_example/public/components/file_picker.tsx b/x-pack/examples/files_example/public/components/file_picker.tsx index 2bf5530655ba..3c2178b299ea 100644 --- a/x-pack/examples/files_example/public/components/file_picker.tsx +++ b/x-pack/examples/files_example/public/components/file_picker.tsx @@ -18,5 +18,5 @@ interface Props { } export const MyFilePicker: FunctionComponent = ({ onClose, onDone }) => { - return ; + return ; }; diff --git a/x-pack/packages/ml/agg_utils/src/fetch_agg_intervals.ts b/x-pack/packages/ml/agg_utils/src/fetch_agg_intervals.ts index 338f55ad754c..7986747d34dd 100644 --- a/x-pack/packages/ml/agg_utils/src/fetch_agg_intervals.ts +++ b/x-pack/packages/ml/agg_utils/src/fetch_agg_intervals.ts @@ -29,7 +29,8 @@ export const fetchAggIntervals = async ( query: estypes.QueryDslQueryContainer, fields: HistogramField[], samplerShardSize: number, - runtimeMappings?: estypes.MappingRuntimeFields + runtimeMappings?: estypes.MappingRuntimeFields, + abortSignal?: AbortSignal ): Promise => { const numericColumns = fields.filter((field) => { return field.type === KBN_FIELD_TYPES.NUMBER || field.type === KBN_FIELD_TYPES.DATE; @@ -49,16 +50,19 @@ export const fetchAggIntervals = async ( return aggs; }, {} as Record); - const body = await client.search({ - index: indexPattern, - size: 0, - body: { - query, - aggs: buildSamplerAggregation(minMaxAggs, samplerShardSize), + const body = await client.search( + { + index: indexPattern, size: 0, - ...(isPopulatedObject(runtimeMappings) ? { runtime_mappings: runtimeMappings } : {}), + body: { + query, + aggs: buildSamplerAggregation(minMaxAggs, samplerShardSize), + size: 0, + ...(isPopulatedObject(runtimeMappings) ? { runtime_mappings: runtimeMappings } : {}), + }, }, - }); + { signal: abortSignal, maxRetries: 0 } + ); const aggsPath = getSamplerAggregationsResponsePath(samplerShardSize); const aggregations = aggsPath.length > 0 ? get(body.aggregations, aggsPath) : body.aggregations; diff --git a/x-pack/packages/ml/agg_utils/src/fetch_histograms_for_fields.ts b/x-pack/packages/ml/agg_utils/src/fetch_histograms_for_fields.ts index a921eaeae370..70d5f6360155 100644 --- a/x-pack/packages/ml/agg_utils/src/fetch_histograms_for_fields.ts +++ b/x-pack/packages/ml/agg_utils/src/fetch_histograms_for_fields.ts @@ -146,7 +146,8 @@ export const fetchHistogramsForFields = async ( query: any, fields: FieldsForHistograms, samplerShardSize: number, - runtimeMappings?: estypes.MappingRuntimeFields + runtimeMappings?: estypes.MappingRuntimeFields, + abortSignal?: AbortSignal ) => { const aggIntervals = { ...(await fetchAggIntervals( @@ -155,7 +156,8 @@ export const fetchHistogramsForFields = async ( query, fields.filter((f) => !isNumericHistogramFieldWithColumnStats(f)), samplerShardSize, - runtimeMappings + runtimeMappings, + abortSignal )), ...fields.filter(isNumericHistogramFieldWithColumnStats).reduce((p, field) => { const { interval, min, max, fieldName } = field; @@ -209,7 +211,7 @@ export const fetchHistogramsForFields = async ( ...(isPopulatedObject(runtimeMappings) ? { runtime_mappings: runtimeMappings } : {}), }, }, - { maxRetries: 0 } + { signal: abortSignal, maxRetries: 0 } ); const aggsPath = getSamplerAggregationsResponsePath(samplerShardSize); diff --git a/x-pack/plugins/actions/server/create_execute_function.ts b/x-pack/plugins/actions/server/create_execute_function.ts index 8f4c4eee61e8..19447bf8e79e 100644 --- a/x-pack/plugins/actions/server/create_execute_function.ts +++ b/x-pack/plugins/actions/server/create_execute_function.ts @@ -16,7 +16,6 @@ import { import { ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE } from './constants/saved_objects'; import { ExecuteOptions as ActionExecutorOptions } from './lib/action_executor'; import { extractSavedObjectReferences, isSavedObjectExecutionSource } from './lib'; -import { RelatedSavedObjects } from './lib/related_saved_objects'; interface CreateExecuteFunctionOptions { taskManager: TaskManagerStartContract; @@ -25,21 +24,18 @@ interface CreateExecuteFunctionOptions { preconfiguredActions: PreConfiguredAction[]; } -export interface ExecuteOptions extends Pick { +export interface ExecuteOptions + extends Pick { id: string; spaceId: string; apiKey: string | null; executionId: string; - consumer?: string; - relatedSavedObjects?: RelatedSavedObjects; } -export interface ActionTaskParams extends Pick { - actionId: string; +interface ActionTaskParams + extends Pick { apiKey: string | null; executionId: string; - consumer?: string; - relatedSavedObjects?: RelatedSavedObjects; } export interface GetConnectorsResult { @@ -176,43 +172,40 @@ export function createBulkExecutionEnqueuerFunction({ connectorIsPreconfigured[id] = isPreconfigured; }); - const actions = await Promise.all( - actionsToExecute.map(async (actionToExecute) => { - // Get saved object references from action ID and relatedSavedObjects - const { references, relatedSavedObjectWithRefs } = extractSavedObjectReferences( - actionToExecute.id, - connectorIsPreconfigured[actionToExecute.id], - actionToExecute.relatedSavedObjects - ); - const executionSourceReference = executionSourceAsSavedObjectReferences( - actionToExecute.source - ); - - const taskReferences = []; - if (executionSourceReference.references) { - taskReferences.push(...executionSourceReference.references); - } - if (references) { - taskReferences.push(...references); - } - - spaceIds[actionToExecute.id] = actionToExecute.spaceId; - - return { - type: ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE, - attributes: { - actionId: actionToExecute.id, - params: actionToExecute.params, - apiKey: actionToExecute.apiKey, - executionId: actionToExecute.executionId, - consumer: actionToExecute.consumer, - relatedSavedObjects: relatedSavedObjectWithRefs, - }, - references: taskReferences, - }; - }) - ); + const actions = actionsToExecute.map((actionToExecute) => { + // Get saved object references from action ID and relatedSavedObjects + const { references, relatedSavedObjectWithRefs } = extractSavedObjectReferences( + actionToExecute.id, + connectorIsPreconfigured[actionToExecute.id], + actionToExecute.relatedSavedObjects + ); + const executionSourceReference = executionSourceAsSavedObjectReferences( + actionToExecute.source + ); + + const taskReferences = []; + if (executionSourceReference.references) { + taskReferences.push(...executionSourceReference.references); + } + if (references) { + taskReferences.push(...references); + } + spaceIds[actionToExecute.id] = actionToExecute.spaceId; + + return { + type: ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE, + attributes: { + actionId: actionToExecute.id, + params: actionToExecute.params, + apiKey: actionToExecute.apiKey, + executionId: actionToExecute.executionId, + consumer: actionToExecute.consumer, + relatedSavedObjects: relatedSavedObjectWithRefs, + }, + references: taskReferences, + }; + }); const actionTaskParamsRecords: SavedObjectsBulkResponse = await unsecuredSavedObjectsClient.bulkCreate(actions); const taskInstances = actionTaskParamsRecords.saved_objects.map((so) => { diff --git a/x-pack/plugins/actions/server/create_unsecured_execute_function.test.ts b/x-pack/plugins/actions/server/create_unsecured_execute_function.test.ts new file mode 100644 index 000000000000..ea8407b95627 --- /dev/null +++ b/x-pack/plugins/actions/server/create_unsecured_execute_function.test.ts @@ -0,0 +1,481 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import uuid from 'uuid'; +import { savedObjectsRepositoryMock } from '@kbn/core/server/mocks'; +import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; +import { createBulkUnsecuredExecutionEnqueuerFunction } from './create_unsecured_execute_function'; +import { actionTypeRegistryMock } from './action_type_registry.mock'; +import { asSavedObjectExecutionSource } from './lib/action_execution_source'; + +const mockTaskManager = taskManagerMock.createStart(); +const internalSavedObjectsRepository = savedObjectsRepositoryMock.create(); + +beforeEach(() => jest.resetAllMocks()); + +describe('bulkExecute()', () => { + test('schedules the actions with all given parameters with a preconfigured connector', async () => { + const executeFn = createBulkUnsecuredExecutionEnqueuerFunction({ + taskManager: mockTaskManager, + connectorTypeRegistry: actionTypeRegistryMock.create(), + preconfiguredConnectors: [ + { + id: '123', + actionTypeId: '.email', + config: {}, + isPreconfigured: true, + isDeprecated: false, + name: 'x', + secrets: {}, + }, + ], + }); + + internalSavedObjectsRepository.bulkCreate.mockResolvedValueOnce({ + saved_objects: [ + { + id: '234', + type: 'action_task_params', + attributes: { + actionId: '123', + }, + references: [], + }, + { + id: '345', + type: 'action_task_params', + attributes: { + actionId: '123', + }, + references: [], + }, + ], + }); + await executeFn(internalSavedObjectsRepository, [ + { + id: '123', + params: { baz: false }, + }, + { + id: '123', + params: { baz: true }, + }, + ]); + expect(mockTaskManager.bulkSchedule).toHaveBeenCalledTimes(1); + expect(mockTaskManager.bulkSchedule.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "params": Object { + "actionTaskParamsId": "234", + "spaceId": "default", + }, + "scope": Array [ + "actions", + ], + "state": Object {}, + "taskType": "actions:.email", + }, + Object { + "params": Object { + "actionTaskParamsId": "345", + "spaceId": "default", + }, + "scope": Array [ + "actions", + ], + "state": Object {}, + "taskType": "actions:.email", + }, + ], + ] + `); + + expect(internalSavedObjectsRepository.bulkCreate).toHaveBeenCalledWith([ + { + type: 'action_task_params', + attributes: { + actionId: '123', + params: { baz: false }, + apiKey: null, + }, + references: [], + }, + { + type: 'action_task_params', + attributes: { + actionId: '123', + params: { baz: true }, + apiKey: null, + }, + references: [], + }, + ]); + }); + + test('schedules the actions with all given parameters with a preconfigured connector and source specified', async () => { + const sourceUuid = uuid.v4(); + const source = { type: 'alert', id: sourceUuid }; + const executeFn = createBulkUnsecuredExecutionEnqueuerFunction({ + taskManager: mockTaskManager, + connectorTypeRegistry: actionTypeRegistryMock.create(), + preconfiguredConnectors: [ + { + id: '123', + actionTypeId: '.email', + config: {}, + isPreconfigured: true, + isDeprecated: false, + name: 'x', + secrets: {}, + }, + ], + }); + + internalSavedObjectsRepository.bulkCreate.mockResolvedValueOnce({ + saved_objects: [ + { + id: '234', + type: 'action_task_params', + attributes: { + actionId: '123', + }, + references: [ + { + id: sourceUuid, + name: 'source', + type: 'alert', + }, + ], + }, + { + id: '345', + type: 'action_task_params', + attributes: { + actionId: '123', + }, + references: [], + }, + ], + }); + await executeFn(internalSavedObjectsRepository, [ + { + id: '123', + params: { baz: false }, + source: asSavedObjectExecutionSource(source), + }, + { + id: '123', + params: { baz: true }, + }, + ]); + expect(mockTaskManager.bulkSchedule).toHaveBeenCalledTimes(1); + expect(mockTaskManager.bulkSchedule.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "params": Object { + "actionTaskParamsId": "234", + "spaceId": "default", + }, + "scope": Array [ + "actions", + ], + "state": Object {}, + "taskType": "actions:.email", + }, + Object { + "params": Object { + "actionTaskParamsId": "345", + "spaceId": "default", + }, + "scope": Array [ + "actions", + ], + "state": Object {}, + "taskType": "actions:.email", + }, + ], + ] + `); + + expect(internalSavedObjectsRepository.bulkCreate).toHaveBeenCalledWith([ + { + type: 'action_task_params', + attributes: { + actionId: '123', + params: { baz: false }, + apiKey: null, + }, + references: [ + { + id: sourceUuid, + name: 'source', + type: 'alert', + }, + ], + }, + { + type: 'action_task_params', + attributes: { + actionId: '123', + params: { baz: true }, + apiKey: null, + }, + references: [], + }, + ]); + }); + + test('schedules the actions with all given parameters with a preconfigured connector and relatedSavedObjects specified', async () => { + const sourceUuid = uuid.v4(); + const source = { type: 'alert', id: sourceUuid }; + const executeFn = createBulkUnsecuredExecutionEnqueuerFunction({ + taskManager: mockTaskManager, + connectorTypeRegistry: actionTypeRegistryMock.create(), + preconfiguredConnectors: [ + { + id: '123', + actionTypeId: '.email', + config: {}, + isPreconfigured: true, + isDeprecated: false, + name: 'x', + secrets: {}, + }, + ], + }); + + internalSavedObjectsRepository.bulkCreate.mockResolvedValueOnce({ + saved_objects: [ + { + id: '234', + type: 'action_task_params', + attributes: { + actionId: '123', + }, + references: [ + { + id: sourceUuid, + name: 'source', + type: 'alert', + }, + ], + }, + { + id: '345', + type: 'action_task_params', + attributes: { + actionId: '123', + }, + references: [ + { + id: 'some-id', + name: 'related_some-type_0', + type: 'some-type', + }, + ], + }, + ], + }); + await executeFn(internalSavedObjectsRepository, [ + { + id: '123', + params: { baz: false }, + source: asSavedObjectExecutionSource(source), + }, + { + id: '123', + params: { baz: true }, + relatedSavedObjects: [ + { + id: 'some-id', + namespace: 'some-namespace', + type: 'some-type', + }, + ], + }, + ]); + expect(mockTaskManager.bulkSchedule).toHaveBeenCalledTimes(1); + expect(mockTaskManager.bulkSchedule.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "params": Object { + "actionTaskParamsId": "234", + "spaceId": "default", + }, + "scope": Array [ + "actions", + ], + "state": Object {}, + "taskType": "actions:.email", + }, + Object { + "params": Object { + "actionTaskParamsId": "345", + "spaceId": "default", + }, + "scope": Array [ + "actions", + ], + "state": Object {}, + "taskType": "actions:.email", + }, + ], + ] + `); + + expect(internalSavedObjectsRepository.bulkCreate).toHaveBeenCalledWith([ + { + type: 'action_task_params', + attributes: { + actionId: '123', + params: { baz: false }, + apiKey: null, + }, + references: [ + { + id: sourceUuid, + name: 'source', + type: 'alert', + }, + ], + }, + { + type: 'action_task_params', + attributes: { + actionId: '123', + params: { baz: true }, + apiKey: null, + relatedSavedObjects: [ + { + id: 'related_some-type_0', + namespace: 'some-namespace', + type: 'some-type', + }, + ], + }, + references: [ + { + id: 'some-id', + name: 'related_some-type_0', + type: 'some-type', + }, + ], + }, + ]); + }); + + test('throws when scheduling action using non preconfigured connector', async () => { + const executeFn = createBulkUnsecuredExecutionEnqueuerFunction({ + taskManager: mockTaskManager, + connectorTypeRegistry: actionTypeRegistryMock.create(), + preconfiguredConnectors: [ + { + id: '123', + actionTypeId: '.email', + config: {}, + isPreconfigured: true, + isDeprecated: false, + name: 'x', + secrets: {}, + }, + ], + }); + await expect( + executeFn(internalSavedObjectsRepository, [ + { + id: '123', + params: { baz: false }, + }, + { + id: 'not-preconfigured', + params: { baz: true }, + }, + ]) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"not-preconfigured are not preconfigured connectors and can't be scheduled for unsecured actions execution"` + ); + }); + + test('throws when connector type is not enabled', async () => { + const mockedConnectorTypeRegistry = actionTypeRegistryMock.create(); + const executeFn = createBulkUnsecuredExecutionEnqueuerFunction({ + taskManager: mockTaskManager, + connectorTypeRegistry: mockedConnectorTypeRegistry, + preconfiguredConnectors: [ + { + id: '123', + actionTypeId: '.email', + config: {}, + isPreconfigured: true, + isDeprecated: false, + name: 'x', + secrets: {}, + }, + ], + }); + mockedConnectorTypeRegistry.ensureActionTypeEnabled.mockImplementation(() => { + throw new Error('Fail'); + }); + + await expect( + executeFn(internalSavedObjectsRepository, [ + { + id: '123', + params: { baz: false }, + }, + { + id: '123', + params: { baz: true }, + }, + ]) + ).rejects.toThrowErrorMatchingInlineSnapshot(`"Fail"`); + }); + + test('throws when scheduling action using non allow-listed preconfigured connector', async () => { + const executeFn = createBulkUnsecuredExecutionEnqueuerFunction({ + taskManager: mockTaskManager, + connectorTypeRegistry: actionTypeRegistryMock.create(), + preconfiguredConnectors: [ + { + id: '123', + actionTypeId: '.email', + config: {}, + isPreconfigured: true, + isDeprecated: false, + name: 'x', + secrets: {}, + }, + { + id: '456', + actionTypeId: 'not-in-allowlist', + config: {}, + isPreconfigured: true, + isDeprecated: false, + name: 'x', + secrets: {}, + }, + ], + }); + await expect( + executeFn(internalSavedObjectsRepository, [ + { + id: '123', + params: { baz: false }, + }, + { + id: '456', + params: { baz: true }, + }, + ]) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"not-in-allowlist actions cannot be scheduled for unsecured actions execution"` + ); + }); +}); diff --git a/x-pack/plugins/actions/server/create_unsecured_execute_function.ts b/x-pack/plugins/actions/server/create_unsecured_execute_function.ts new file mode 100644 index 000000000000..4670601ecff8 --- /dev/null +++ b/x-pack/plugins/actions/server/create_unsecured_execute_function.ts @@ -0,0 +1,148 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ISavedObjectsRepository, SavedObjectsBulkResponse } from '@kbn/core/server'; +import { TaskManagerStartContract } from '@kbn/task-manager-plugin/server'; +import { + ActionTypeRegistryContract as ConnectorTypeRegistryContract, + PreConfiguredAction as PreconfiguredConnector, +} from './types'; +import { ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE } from './constants/saved_objects'; +import { ExecuteOptions as ActionExecutorOptions } from './lib/action_executor'; +import { extractSavedObjectReferences, isSavedObjectExecutionSource } from './lib'; + +// This allowlist should only contain connector types that don't require API keys for +// execution. +const ALLOWED_CONNECTOR_TYPE_IDS = ['.email']; +interface CreateBulkUnsecuredExecuteFunctionOptions { + taskManager: TaskManagerStartContract; + connectorTypeRegistry: ConnectorTypeRegistryContract; + preconfiguredConnectors: PreconfiguredConnector[]; +} + +export interface ExecuteOptions + extends Pick { + id: string; +} + +interface ActionTaskParams + extends Pick { + apiKey: string | null; +} + +export type BulkUnsecuredExecutionEnqueuer = ( + internalSavedObjectsRepository: ISavedObjectsRepository, + actionsToExectute: ExecuteOptions[] +) => Promise; + +export function createBulkUnsecuredExecutionEnqueuerFunction({ + taskManager, + connectorTypeRegistry, + preconfiguredConnectors, +}: CreateBulkUnsecuredExecuteFunctionOptions): BulkUnsecuredExecutionEnqueuer { + return async function execute( + internalSavedObjectsRepository: ISavedObjectsRepository, + actionsToExecute: ExecuteOptions[] + ) { + const connectorTypeIds: Record = {}; + const connectorIds = [...new Set(actionsToExecute.map((action) => action.id))]; + + const notPreconfiguredConnectors = connectorIds.filter( + (connectorId) => + preconfiguredConnectors.find((connector) => connector.id === connectorId) == null + ); + + if (notPreconfiguredConnectors.length > 0) { + throw new Error( + `${notPreconfiguredConnectors.join( + ',' + )} are not preconfigured connectors and can't be scheduled for unsecured actions execution` + ); + } + + const connectors: PreconfiguredConnector[] = connectorIds + .map((connectorId) => + preconfiguredConnectors.find((pConnector) => pConnector.id === connectorId) + ) + .filter(Boolean) as PreconfiguredConnector[]; + + connectors.forEach((connector) => { + const { id, actionTypeId } = connector; + if (!connectorTypeRegistry.isActionExecutable(id, actionTypeId, { notifyUsage: true })) { + connectorTypeRegistry.ensureActionTypeEnabled(actionTypeId); + } + + if (!ALLOWED_CONNECTOR_TYPE_IDS.includes(actionTypeId)) { + throw new Error( + `${actionTypeId} actions cannot be scheduled for unsecured actions execution` + ); + } + + connectorTypeIds[id] = actionTypeId; + }); + + const actions = actionsToExecute.map((actionToExecute) => { + // Get saved object references from action ID and relatedSavedObjects + const { references, relatedSavedObjectWithRefs } = extractSavedObjectReferences( + actionToExecute.id, + true, + actionToExecute.relatedSavedObjects + ); + const executionSourceReference = executionSourceAsSavedObjectReferences( + actionToExecute.source + ); + + const taskReferences = []; + if (executionSourceReference.references) { + taskReferences.push(...executionSourceReference.references); + } + if (references) { + taskReferences.push(...references); + } + + return { + type: ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE, + attributes: { + actionId: actionToExecute.id, + params: actionToExecute.params, + apiKey: null, + relatedSavedObjects: relatedSavedObjectWithRefs, + }, + references: taskReferences, + }; + }); + const actionTaskParamsRecords: SavedObjectsBulkResponse = + await internalSavedObjectsRepository.bulkCreate(actions); + + const taskInstances = actionTaskParamsRecords.saved_objects.map((so) => { + const actionId = so.attributes.actionId; + return { + taskType: `actions:${connectorTypeIds[actionId]}`, + params: { + spaceId: 'default', + actionTaskParamsId: so.id, + }, + state: {}, + scope: ['actions'], + }; + }); + await taskManager.bulkSchedule(taskInstances); + }; +} + +function executionSourceAsSavedObjectReferences(executionSource: ActionExecutorOptions['source']) { + return isSavedObjectExecutionSource(executionSource) + ? { + references: [ + { + name: 'source', + ...executionSource.source, + }, + ], + } + : {}; +} diff --git a/x-pack/plugins/actions/server/index.ts b/x-pack/plugins/actions/server/index.ts index 1c7a66978ffb..2713ee17463e 100644 --- a/x-pack/plugins/actions/server/index.ts +++ b/x-pack/plugins/actions/server/index.ts @@ -12,6 +12,8 @@ import { configSchema, ActionsConfig, CustomHostSettings } from './config'; import { ActionsClient as ActionsClientClass } from './actions_client'; import { ActionsAuthorization as ActionsAuthorizationClass } from './authorization/actions_authorization'; +export type { IUnsecuredActionsClient } from './unsecured_actions_client/unsecured_actions_client'; +export { UnsecuredActionsClient } from './unsecured_actions_client/unsecured_actions_client'; export type ActionsClient = PublicMethodsOf; export type ActionsAuthorization = PublicMethodsOf; diff --git a/x-pack/plugins/actions/server/lib/action_executor.test.ts b/x-pack/plugins/actions/server/lib/action_executor.test.ts index 849cd2ff44ba..4fde645fb367 100644 --- a/x-pack/plugins/actions/server/lib/action_executor.test.ts +++ b/x-pack/plugins/actions/server/lib/action_executor.test.ts @@ -47,7 +47,21 @@ actionExecutor.initialize({ actionTypeRegistry, encryptedSavedObjectsClient, eventLogger, - preconfiguredActions: [], + preconfiguredActions: [ + { + id: 'preconfigured', + name: 'Preconfigured', + actionTypeId: 'test', + config: { + bar: 'preconfigured', + }, + secrets: { + apiKey: 'abc', + }, + isPreconfigured: true, + isDeprecated: false, + }, + ], }); beforeEach(() => { @@ -183,6 +197,107 @@ test('successfully executes', async () => { `); }); +test('successfully executes with preconfigured connector', async () => { + const actionType: jest.Mocked = { + id: 'test', + name: 'Test', + minimumLicenseRequired: 'basic', + supportedFeatureIds: ['alerting'], + executor: jest.fn(), + }; + + actionTypeRegistry.get.mockReturnValueOnce(actionType); + await actionExecutor.execute({ ...executeParams, actionId: 'preconfigured' }); + + expect(actionsClient.get).not.toHaveBeenCalled(); + expect(encryptedSavedObjectsClient.getDecryptedAsInternalUser).not.toHaveBeenCalled(); + + expect(actionTypeRegistry.get).toHaveBeenCalledWith('test'); + expect(actionTypeRegistry.isActionExecutable).toHaveBeenCalledWith('preconfigured', 'test', { + notifyUsage: true, + }); + + expect(actionType.executor).toHaveBeenCalledWith({ + actionId: 'preconfigured', + services: expect.anything(), + config: { + bar: 'preconfigured', + }, + secrets: { + apiKey: 'abc', + }, + params: { foo: true }, + logger: loggerMock, + }); + + expect(loggerMock.debug).toBeCalledWith('executing action test:preconfigured: Preconfigured'); + expect(eventLogger.logEvent.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "event": Object { + "action": "execute-start", + "kind": "action", + }, + "kibana": Object { + "alert": Object { + "rule": Object { + "execution": Object { + "uuid": "123abc", + }, + }, + }, + "saved_objects": Array [ + Object { + "id": "preconfigured", + "namespace": "some-namespace", + "rel": "primary", + "type": "action", + "type_id": "test", + }, + ], + "space_ids": Array [ + "some-namespace", + ], + }, + "message": "action started: test:preconfigured: Preconfigured", + }, + ], + Array [ + Object { + "event": Object { + "action": "execute", + "kind": "action", + "outcome": "success", + }, + "kibana": Object { + "alert": Object { + "rule": Object { + "execution": Object { + "uuid": "123abc", + }, + }, + }, + "saved_objects": Array [ + Object { + "id": "preconfigured", + "namespace": "some-namespace", + "rel": "primary", + "type": "action", + "type_id": "test", + }, + ], + "space_ids": Array [ + "some-namespace", + ], + }, + "message": "action executed: test:preconfigured: Preconfigured", + }, + ], + ] + `); +}); + test('successfully executes as a task', async () => { const actionType: jest.Mocked = { id: 'test', @@ -509,6 +624,132 @@ test('throws an error when passing isESOCanEncrypt with value of false', async ( ); }); +test('should not throw error if action is preconfigured and isESOCanEncrypt is false', async () => { + const customActionExecutor = new ActionExecutor({ isESOCanEncrypt: false }); + customActionExecutor.initialize({ + logger: loggingSystemMock.create().get(), + spaces: spacesMock, + getActionsClientWithRequest, + getServices: () => services, + actionTypeRegistry, + encryptedSavedObjectsClient, + eventLogger: eventLoggerMock.create(), + preconfiguredActions: [ + { + id: 'preconfigured', + name: 'Preconfigured', + actionTypeId: 'test', + config: { + bar: 'preconfigured', + }, + secrets: { + apiKey: 'abc', + }, + isPreconfigured: true, + isDeprecated: false, + }, + ], + }); + const actionType: jest.Mocked = { + id: 'test', + name: 'Test', + minimumLicenseRequired: 'basic', + supportedFeatureIds: ['alerting'], + executor: jest.fn(), + }; + + actionTypeRegistry.get.mockReturnValueOnce(actionType); + await actionExecutor.execute({ ...executeParams, actionId: 'preconfigured' }); + + expect(actionsClient.get).not.toHaveBeenCalled(); + expect(encryptedSavedObjectsClient.getDecryptedAsInternalUser).not.toHaveBeenCalled(); + + expect(actionTypeRegistry.get).toHaveBeenCalledWith('test'); + expect(actionTypeRegistry.isActionExecutable).toHaveBeenCalledWith('preconfigured', 'test', { + notifyUsage: true, + }); + + expect(actionType.executor).toHaveBeenCalledWith({ + actionId: 'preconfigured', + services: expect.anything(), + config: { + bar: 'preconfigured', + }, + secrets: { + apiKey: 'abc', + }, + params: { foo: true }, + logger: loggerMock, + }); + + expect(loggerMock.debug).toBeCalledWith('executing action test:preconfigured: Preconfigured'); + expect(eventLogger.logEvent.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "event": Object { + "action": "execute-start", + "kind": "action", + }, + "kibana": Object { + "alert": Object { + "rule": Object { + "execution": Object { + "uuid": "123abc", + }, + }, + }, + "saved_objects": Array [ + Object { + "id": "preconfigured", + "namespace": "some-namespace", + "rel": "primary", + "type": "action", + "type_id": "test", + }, + ], + "space_ids": Array [ + "some-namespace", + ], + }, + "message": "action started: test:preconfigured: Preconfigured", + }, + ], + Array [ + Object { + "event": Object { + "action": "execute", + "kind": "action", + "outcome": "success", + }, + "kibana": Object { + "alert": Object { + "rule": Object { + "execution": Object { + "uuid": "123abc", + }, + }, + }, + "saved_objects": Array [ + Object { + "id": "preconfigured", + "namespace": "some-namespace", + "rel": "primary", + "type": "action", + "type_id": "test", + }, + ], + "space_ids": Array [ + "some-namespace", + ], + }, + "message": "action executed: test:preconfigured: Preconfigured", + }, + ], + ] + `); +}); + test('does not log warning when alert executor succeeds', async () => { const executorMock = setupActionExecutorMock(); executorMock.mockResolvedValue({ diff --git a/x-pack/plugins/actions/server/lib/action_executor.ts b/x-pack/plugins/actions/server/lib/action_executor.ts index 90603499cd4a..42e5c8c8e1a9 100644 --- a/x-pack/plugins/actions/server/lib/action_executor.ts +++ b/x-pack/plugins/actions/server/lib/action_executor.ts @@ -105,12 +105,6 @@ export class ActionExecutor { throw new Error('ActionExecutor not initialized'); } - if (!this.isESOCanEncrypt) { - throw new Error( - `Unable to execute action because the Encrypted Saved Objects plugin is missing encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.` - ); - } - return withSpan( { name: `execute_action`, @@ -135,11 +129,14 @@ export class ActionExecutor { const namespace = spaceId && spaceId !== 'default' ? { namespace: spaceId } : {}; const actionInfo = await getActionInfoInternal( - await getActionsClientWithRequest(request, source), + getActionsClientWithRequest, + request, + this.isESOCanEncrypt, encryptedSavedObjectsClient, preconfiguredActions, actionId, - namespace.namespace + namespace.namespace, + source ); const { actionTypeId, name, config, secrets } = actionInfo; @@ -321,11 +318,14 @@ export class ActionExecutor { const namespace = spaceId && spaceId !== 'default' ? { namespace: spaceId } : {}; if (!this.actionInfo || this.actionInfo.actionId !== actionId) { this.actionInfo = await getActionInfoInternal( - await getActionsClientWithRequest(request, source), + getActionsClientWithRequest, + request, + this.isESOCanEncrypt, encryptedSavedObjectsClient, preconfiguredActions, actionId, - namespace.namespace + namespace.namespace, + source ); } const task = taskInfo @@ -371,12 +371,18 @@ interface ActionInfo { actionId: string; } -async function getActionInfoInternal( - actionsClient: PublicMethodsOf, +async function getActionInfoInternal( + getActionsClientWithRequest: ( + request: KibanaRequest, + authorizationContext?: ActionExecutionSource + ) => Promise>, + request: KibanaRequest, + isESOCanEncrypt: boolean, encryptedSavedObjectsClient: EncryptedSavedObjectsClient, preconfiguredActions: PreConfiguredAction[], actionId: string, - namespace: string | undefined + namespace: string | undefined, + source?: ActionExecutionSource ): Promise { // check to see if it's a pre-configured action first const pcAction = preconfiguredActions.find( @@ -392,6 +398,14 @@ async function getActionInfoInternal( }; } + if (!isESOCanEncrypt) { + throw new Error( + `Unable to execute action because the Encrypted Saved Objects plugin is missing encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.` + ); + } + + const actionsClient = await getActionsClientWithRequest(request, source); + // if not pre-configured action, should be a saved object // ensure user can read the action before processing const { actionTypeId, config, name } = await actionsClient.get({ id: actionId }); diff --git a/x-pack/plugins/actions/server/lib/create_action_event_log_record_object.test.ts b/x-pack/plugins/actions/server/lib/create_action_event_log_record_object.test.ts index 72cbda1312b9..69eca915cc72 100644 --- a/x-pack/plugins/actions/server/lib/create_action_event_log_record_object.test.ts +++ b/x-pack/plugins/actions/server/lib/create_action_event_log_record_object.test.ts @@ -109,6 +109,43 @@ describe('createActionEventLogRecordObject', () => { }); }); + test('created action event "execute" with no kibana.alert.rule fields', async () => { + expect( + createActionEventLogRecordObject({ + actionId: '1', + name: 'test name', + action: 'execute', + message: 'action execution start', + namespace: 'default', + savedObjects: [ + { + id: '2', + type: 'action', + typeId: '.email', + relation: 'primary', + }, + ], + }) + ).toStrictEqual({ + event: { + action: 'execute', + kind: 'action', + }, + kibana: { + saved_objects: [ + { + id: '2', + namespace: 'default', + rel: 'primary', + type: 'action', + type_id: '.email', + }, + ], + }, + message: 'action execution start', + }); + }); + test('created action event "execute-timeout"', async () => { expect( createActionEventLogRecordObject({ diff --git a/x-pack/plugins/actions/server/lib/create_action_event_log_record_object.ts b/x-pack/plugins/actions/server/lib/create_action_event_log_record_object.ts index 5d556398dc66..2632ead26a47 100644 --- a/x-pack/plugins/actions/server/lib/create_action_event_log_record_object.ts +++ b/x-pack/plugins/actions/server/lib/create_action_event_log_record_object.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { set } from 'lodash'; +import { isEmpty, set } from 'lodash'; import { IEvent, SAVED_OBJECT_REL_PRIMARY } from '@kbn/event-log-plugin/server'; import { RelatedSavedObjects } from './related_saved_objects'; @@ -38,6 +38,17 @@ export function createActionEventLogRecordObject(params: CreateActionEventLogRec const { action, message, task, namespace, executionId, spaceId, consumer, relatedSavedObjects } = params; + const kibanaAlertRule = { + ...(consumer ? { consumer } : {}), + ...(executionId + ? { + execution: { + uuid: executionId, + }, + } + : {}), + }; + const event: Event = { ...(params.timestamp ? { '@timestamp': params.timestamp } : {}), event: { @@ -45,18 +56,7 @@ export function createActionEventLogRecordObject(params: CreateActionEventLogRec kind: 'action', }, kibana: { - alert: { - rule: { - ...(consumer ? { consumer } : {}), - ...(executionId - ? { - execution: { - uuid: executionId, - }, - } - : {}), - }, - }, + ...(!isEmpty(kibanaAlertRule) ? { alert: { rule: kibanaAlertRule } } : {}), saved_objects: params.savedObjects.map((so) => ({ ...(so.relation ? { rel: so.relation } : {}), type: so.type, diff --git a/x-pack/plugins/actions/server/lib/get_custom_agents.test.ts b/x-pack/plugins/actions/server/lib/get_custom_agents.test.ts index 9bf1e26f1d37..7d61e4b4c6d4 100644 --- a/x-pack/plugins/actions/server/lib/get_custom_agents.test.ts +++ b/x-pack/plugins/actions/server/lib/get_custom_agents.test.ts @@ -6,7 +6,7 @@ */ import { Agent as HttpsAgent } from 'https'; -import HttpProxyAgent from 'http-proxy-agent'; +import { HttpProxyAgent } from 'http-proxy-agent'; import { HttpsProxyAgent } from 'https-proxy-agent'; import { Logger } from '@kbn/core/server'; import { getCustomAgents } from './get_custom_agents'; diff --git a/x-pack/plugins/actions/server/lib/get_custom_agents.ts b/x-pack/plugins/actions/server/lib/get_custom_agents.ts index 49eca54afcde..220abeb6cc92 100644 --- a/x-pack/plugins/actions/server/lib/get_custom_agents.ts +++ b/x-pack/plugins/actions/server/lib/get_custom_agents.ts @@ -7,7 +7,7 @@ import { Agent as HttpAgent } from 'http'; import { Agent as HttpsAgent, AgentOptions } from 'https'; -import HttpProxyAgent from 'http-proxy-agent'; +import { HttpProxyAgent } from 'http-proxy-agent'; import { HttpsProxyAgent } from 'https-proxy-agent'; import { Logger } from '@kbn/core/server'; import { ActionsConfigurationUtilities } from '../actions_config'; diff --git a/x-pack/plugins/actions/server/mocks.ts b/x-pack/plugins/actions/server/mocks.ts index 3b8155818452..4d5846de9528 100644 --- a/x-pack/plugins/actions/server/mocks.ts +++ b/x-pack/plugins/actions/server/mocks.ts @@ -17,6 +17,7 @@ import { PluginSetupContract, PluginStartContract, renderActionParameterTemplate import { Services } from './types'; import { actionsAuthorizationMock } from './authorization/actions_authorization.mock'; import { ConnectorTokenClient } from './lib/connector_token_client'; +import { unsecuredActionsClientMock } from './unsecured_actions_client/unsecured_actions_client.mock'; export { actionsAuthorizationMock }; export { actionsClientMock }; const logger = loggingSystemMock.create().get() as jest.Mocked; @@ -38,6 +39,7 @@ const createStartMock = () => { isActionTypeEnabled: jest.fn(), isActionExecutable: jest.fn(), getActionsClientWithRequest: jest.fn().mockResolvedValue(actionsClientMock.create()), + getUnsecuredActionsClient: jest.fn().mockResolvedValue(unsecuredActionsClientMock.create()), getActionsAuthorizationWithRequest: jest .fn() .mockReturnValue(actionsAuthorizationMock.create()), diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts index 749fc7a21ebd..e24ac8247bfd 100644 --- a/x-pack/plugins/actions/server/plugin.ts +++ b/x-pack/plugins/actions/server/plugin.ts @@ -101,6 +101,11 @@ import { createSubActionConnectorFramework } from './sub_action_framework'; import { IServiceAbstract, SubActionConnectorType } from './sub_action_framework/types'; import { SubActionConnector } from './sub_action_framework/sub_action_connector'; import { CaseConnector } from './sub_action_framework/case'; +import { + IUnsecuredActionsClient, + UnsecuredActionsClient, +} from './unsecured_actions_client/unsecured_actions_client'; +import { createBulkUnsecuredExecutionEnqueuerFunction } from './create_unsecured_execute_function'; export interface PluginSetupContract { registerType< @@ -138,6 +143,8 @@ export interface PluginStartContract { preconfiguredActions: PreConfiguredAction[]; + getUnsecuredActionsClient(): IUnsecuredActionsClient; + renderActionParameterTemplates( actionTypeId: string, actionId: string, @@ -453,6 +460,21 @@ export class ActionsPlugin implements Plugin { + const internalSavedObjectsRepository = core.savedObjects.createInternalRepository([ + ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE, + ]); + + return new UnsecuredActionsClient({ + internalSavedObjectsRepository, + executionEnqueuer: createBulkUnsecuredExecutionEnqueuerFunction({ + taskManager: plugins.taskManager, + connectorTypeRegistry: actionTypeRegistry!, + preconfiguredConnectors: preconfiguredActions, + }), + }); + }; + // Ensure the public API cannot be used to circumvent authorization // using our legacy exemption mechanism by passing in a legacy SO // as authorizationContext which would then set a Legacy AuthorizationMode @@ -533,6 +555,7 @@ export class ActionsPlugin implements Plugin renderActionParameterTemplates(actionTypeRegistry, ...args), diff --git a/x-pack/plugins/actions/server/unsecured_actions_client/unsecured_actions_client.mock.ts b/x-pack/plugins/actions/server/unsecured_actions_client/unsecured_actions_client.mock.ts new file mode 100644 index 000000000000..eb8d4de53e7f --- /dev/null +++ b/x-pack/plugins/actions/server/unsecured_actions_client/unsecured_actions_client.mock.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { IUnsecuredActionsClient } from './unsecured_actions_client'; + +export type UnsecuredActionsClientMock = jest.Mocked; + +const createUnsecuredActionsClientMock = () => { + const mocked: UnsecuredActionsClientMock = { + bulkEnqueueExecution: jest.fn(), + }; + return mocked; +}; + +export const unsecuredActionsClientMock = { + create: createUnsecuredActionsClientMock, +}; diff --git a/x-pack/plugins/actions/server/unsecured_actions_client/unsecured_actions_client.test.ts b/x-pack/plugins/actions/server/unsecured_actions_client/unsecured_actions_client.test.ts new file mode 100644 index 000000000000..c863e943b8dc --- /dev/null +++ b/x-pack/plugins/actions/server/unsecured_actions_client/unsecured_actions_client.test.ts @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { UnsecuredActionsClient } from './unsecured_actions_client'; +import { savedObjectsRepositoryMock } from '@kbn/core/server/mocks'; + +const internalSavedObjectsRepository = savedObjectsRepositoryMock.create(); +const executionEnqueuer = jest.fn(); + +let unsecuredActionsClient: UnsecuredActionsClient; + +beforeEach(() => { + jest.resetAllMocks(); + unsecuredActionsClient = new UnsecuredActionsClient({ + internalSavedObjectsRepository, + executionEnqueuer, + }); +}); + +describe('bulkEnqueueExecution()', () => { + test('throws error when enqueuing execution with not allowed requester id', async () => { + const opts = [ + { + id: 'preconfigured1', + params: {}, + executionId: '123abc', + }, + { + id: 'preconfigured2', + params: {}, + executionId: '456def', + }, + ]; + await expect( + unsecuredActionsClient.bulkEnqueueExecution('badId', opts) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"\\"badId\\" feature is not allow-listed for UnsecuredActionsClient access."` + ); + }); + + test('calls the executionEnqueuer with the appropriate parameters', async () => { + const opts = [ + { + id: 'preconfigured1', + params: {}, + executionId: '123abc', + }, + { + id: 'preconfigured2', + params: {}, + executionId: '456def', + }, + ]; + await expect( + unsecuredActionsClient.bulkEnqueueExecution('notifications', opts) + ).resolves.toMatchInlineSnapshot(`undefined`); + + expect(executionEnqueuer).toHaveBeenCalledWith(internalSavedObjectsRepository, opts); + }); +}); diff --git a/x-pack/plugins/actions/server/unsecured_actions_client/unsecured_actions_client.ts b/x-pack/plugins/actions/server/unsecured_actions_client/unsecured_actions_client.ts new file mode 100644 index 000000000000..333490389013 --- /dev/null +++ b/x-pack/plugins/actions/server/unsecured_actions_client/unsecured_actions_client.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ISavedObjectsRepository } from '@kbn/core/server'; +import { + BulkUnsecuredExecutionEnqueuer, + ExecuteOptions, +} from '../create_unsecured_execute_function'; + +// allowlist for features wanting access to the unsecured actions client +// which allows actions to be enqueued for execution without a user request +const ALLOWED_REQUESTER_IDS = [ + 'notifications', + // For functional testing + 'functional_tester', +]; + +export interface UnsecuredActionsClientOpts { + internalSavedObjectsRepository: ISavedObjectsRepository; + executionEnqueuer: BulkUnsecuredExecutionEnqueuer; +} + +export interface IUnsecuredActionsClient { + bulkEnqueueExecution: (requesterId: string, actionsToExecute: ExecuteOptions[]) => Promise; +} + +export class UnsecuredActionsClient { + private readonly internalSavedObjectsRepository: ISavedObjectsRepository; + private readonly executionEnqueuer: BulkUnsecuredExecutionEnqueuer; + + constructor(params: UnsecuredActionsClientOpts) { + this.executionEnqueuer = params.executionEnqueuer; + this.internalSavedObjectsRepository = params.internalSavedObjectsRepository; + } + + public async bulkEnqueueExecution( + requesterId: string, + actionsToExecute: ExecuteOptions[] + ): Promise { + // Check that requesterId is allowed + if (!ALLOWED_REQUESTER_IDS.includes(requesterId)) { + throw new Error( + `"${requesterId}" feature is not allow-listed for UnsecuredActionsClient access.` + ); + } + return this.executionEnqueuer(this.internalSavedObjectsRepository, actionsToExecute); + } +} diff --git a/x-pack/plugins/aiops/server/lib/is_request_aborted_error.test.ts b/x-pack/plugins/aiops/server/lib/is_request_aborted_error.test.ts new file mode 100644 index 000000000000..ee9725dedda4 --- /dev/null +++ b/x-pack/plugins/aiops/server/lib/is_request_aborted_error.test.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isRequestAbortedError } from './is_request_aborted_error'; + +describe('isRequestAbortedError', () => { + it('returns false for a string', () => { + expect(isRequestAbortedError('the-error')).toBe(false); + }); + it('returns false for a an object without a name field', () => { + expect(isRequestAbortedError({ error: 'the-error' })).toBe(false); + }); + it(`returns false for a an object with a name field other than 'RequestAbortedError'`, () => { + expect(isRequestAbortedError({ name: 'the-error' })).toBe(false); + }); + it(`returns true for a an object with a name field that contains 'RequestAbortedError'`, () => { + expect(isRequestAbortedError({ name: 'RequestAbortedError' })).toBe(true); + }); +}); diff --git a/x-pack/plugins/aiops/server/lib/is_request_aborted_error.ts b/x-pack/plugins/aiops/server/lib/is_request_aborted_error.ts new file mode 100644 index 000000000000..b80256dcd863 --- /dev/null +++ b/x-pack/plugins/aiops/server/lib/is_request_aborted_error.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isPopulatedObject } from '@kbn/ml-is-populated-object'; + +interface RequestAbortedError extends Error { + name: 'RequestAbortedError'; +} + +export function isRequestAbortedError(arg: unknown): arg is RequestAbortedError { + return isPopulatedObject(arg, ['name']) && arg.name === 'RequestAbortedError'; +} diff --git a/x-pack/plugins/aiops/server/routes/explain_log_rate_spikes.ts b/x-pack/plugins/aiops/server/routes/explain_log_rate_spikes.ts index c61e1915f68f..2468df9df823 100644 --- a/x-pack/plugins/aiops/server/routes/explain_log_rate_spikes.ts +++ b/x-pack/plugins/aiops/server/routes/explain_log_rate_spikes.ts @@ -34,6 +34,7 @@ import { } from '../../common/api/explain_log_rate_spikes'; import { API_ENDPOINT } from '../../common/api'; +import { isRequestAbortedError } from '../lib/is_request_aborted_error'; import type { AiopsLicense } from '../types'; import { fetchChangePointPValues } from './queries/fetch_change_point_p_values'; @@ -92,6 +93,7 @@ export const defineExplainLogRateSpikesRoute = ( const client = (await context.core).elasticsearch.client.asCurrentUser; const controller = new AbortController(); + const abortSignal = controller.signal; let isRunning = false; let loaded = 0; @@ -129,9 +131,13 @@ export const defineExplainLogRateSpikesRoute = ( } function end() { - isRunning = false; - logDebugMessage('Ending analysis.'); - streamEnd(); + if (isRunning) { + isRunning = false; + logDebugMessage('Ending analysis.'); + streamEnd(); + } else { + logDebugMessage('end() was called again with isRunning already being false.'); + } } function endWithUpdatedLoadingState() { @@ -178,10 +184,12 @@ export const defineExplainLogRateSpikesRoute = ( let fieldCandidates: Awaited>; try { - fieldCandidates = await fetchFieldCandidates(client, request.body); + fieldCandidates = await fetchFieldCandidates(client, request.body, abortSignal); } catch (e) { - logger.error(`Failed to fetch field candidates, got: \n${e.toString()}`); - pushError(`Failed to fetch field candidates.`); + if (!isRequestAbortedError(e)) { + logger.error(`Failed to fetch field candidates, got: \n${e.toString()}`); + pushError(`Failed to fetch field candidates.`); + } end(); return; } @@ -208,16 +216,20 @@ export const defineExplainLogRateSpikesRoute = ( if (fieldCandidates.length === 0) { endWithUpdatedLoadingState(); } else if (shouldStop) { + logDebugMessage('shouldStop after fetching field candidates.'); end(); return; } const changePoints: ChangePoint[] = []; const fieldsToSample = new Set(); - const chunkSize = 10; + + // Don't use more than 10 here otherwise Kibana will emit an error + // regarding a limit of abort signal listeners of more than 10. + const CHUNK_SIZE = 10; let chunkCount = 0; - const fieldCandidatesChunks = chunk(fieldCandidates, chunkSize); + const fieldCandidatesChunks = chunk(fieldCandidates, CHUNK_SIZE); logDebugMessage('Fetch p-values.'); @@ -233,16 +245,18 @@ export const defineExplainLogRateSpikesRoute = ( request.body, fieldCandidatesChunk, logger, - pushError + pushError, + abortSignal ); } catch (e) { - logger.error( - `Failed to fetch p-values for ${JSON.stringify( - fieldCandidatesChunk - )}, got: \n${e.toString()}` - ); - pushError(`Failed to fetch p-values for ${JSON.stringify(fieldCandidatesChunk)}.`); - // Still continue the analysis even if chunks of p-value queries fail. + if (!isRequestAbortedError(e)) { + logger.error( + `Failed to fetch p-values for ${JSON.stringify( + fieldCandidatesChunk + )}, got: \n${e.toString()}` + ); + pushError(`Failed to fetch p-values for ${JSON.stringify(fieldCandidatesChunk)}.`); + } // Still continue the analysis even if chunks of p-value queries fail. continue; } @@ -267,7 +281,7 @@ export const defineExplainLogRateSpikesRoute = ( defaultMessage: 'Identified {fieldValuePairsCount, plural, one {# significant field/value pair} other {# significant field/value pairs}}.', values: { - fieldValuePairsCount: changePoints?.length ?? 0, + fieldValuePairsCount: changePoints.length, }, } ), @@ -276,13 +290,12 @@ export const defineExplainLogRateSpikesRoute = ( if (shouldStop) { logDebugMessage('shouldStop fetching p-values.'); - end(); return; } } - if (changePoints?.length === 0) { + if (changePoints.length === 0) { logDebugMessage('Stopping analysis, did not find change points.'); endWithUpdatedLoadingState(); return; @@ -305,12 +318,15 @@ export const defineExplainLogRateSpikesRoute = ( histogramFields, // samplerShardSize -1, - undefined + undefined, + abortSignal )) as [NumericChartData] )[0]; } catch (e) { - logger.error(`Failed to fetch the overall histogram data, got: \n${e.toString()}`); - pushError(`Failed to fetch overall histogram data.`); + if (!isRequestAbortedError(e)) { + logger.error(`Failed to fetch the overall histogram data, got: \n${e.toString()}`); + pushError(`Failed to fetch overall histogram data.`); + } // Still continue the analysis even if loading the overall histogram fails. } @@ -329,6 +345,12 @@ export const defineExplainLogRateSpikesRoute = ( ); } + if (shouldStop) { + logDebugMessage('shouldStop after fetching overall histogram.'); + end(); + return; + } + if (groupingEnabled) { logDebugMessage('Group results.'); @@ -374,9 +396,16 @@ export const defineExplainLogRateSpikesRoute = ( request.body.deviationMin, request.body.deviationMax, logger, - pushError + pushError, + abortSignal ); + if (shouldStop) { + logDebugMessage('shouldStop after fetching frequent_items.'); + end(); + return; + } + if (fields.length > 0 && df.length > 0) { // The way the `frequent_items` aggregations works could return item sets that include // field/value pairs that are not part of the original list of significant change points. @@ -517,172 +546,208 @@ export const defineExplainLogRateSpikesRoute = ( pushHistogramDataLoadingState(); - logDebugMessage('Fetch group histograms.'); + if (shouldStop) { + logDebugMessage('shouldStop after grouping.'); + end(); + return; + } - await asyncForEach(changePointGroups, async (cpg) => { - if (overallTimeSeries !== undefined) { - const histogramQuery = { - bool: { - filter: cpg.group.map((d) => ({ - term: { [d.fieldName]: d.fieldValue }, - })), - }, - }; + logDebugMessage(`Fetch ${changePointGroups.length} group histograms.`); - let cpgTimeSeries: NumericChartData; - try { - cpgTimeSeries = ( - (await fetchHistogramsForFields( - client, - request.body.index, - histogramQuery, - // fields - [ - { - fieldName: request.body.timeFieldName, - type: KBN_FIELD_TYPES.DATE, - interval: overallTimeSeries.interval, - min: overallTimeSeries.stats[0], - max: overallTimeSeries.stats[1], - }, - ], - // samplerShardSize - -1, - undefined - )) as [NumericChartData] - )[0]; - } catch (e) { - logger.error( - `Failed to fetch the histogram data for group #${ - cpg.id - }, got: \n${e.toString()}` - ); - pushError(`Failed to fetch the histogram data for group #${cpg.id}.`); - return; - } - const histogram = - overallTimeSeries.data.map((o, i) => { - const current = cpgTimeSeries.data.find( - (d1) => d1.key_as_string === o.key_as_string - ) ?? { - doc_count: 0, - }; - return { - key: o.key, - key_as_string: o.key_as_string ?? '', - doc_count_change_point: current.doc_count, - doc_count_overall: Math.max(0, o.doc_count - current.doc_count), - }; - }) ?? []; + const changePointGroupsChunks = chunk(changePointGroups, CHUNK_SIZE); - push( - addChangePointsGroupHistogramAction([ - { - id: cpg.id, - histogram, - }, - ]) - ); + for (const changePointGroupsChunk of changePointGroupsChunks) { + if (shouldStop) { + logDebugMessage('shouldStop abort fetching group histograms.'); + end(); + return; } - }); + + await asyncForEach(changePointGroupsChunk, async (cpg) => { + if (overallTimeSeries !== undefined) { + const histogramQuery = { + bool: { + filter: cpg.group.map((d) => ({ + term: { [d.fieldName]: d.fieldValue }, + })), + }, + }; + + let cpgTimeSeries: NumericChartData; + try { + cpgTimeSeries = ( + (await fetchHistogramsForFields( + client, + request.body.index, + histogramQuery, + // fields + [ + { + fieldName: request.body.timeFieldName, + type: KBN_FIELD_TYPES.DATE, + interval: overallTimeSeries.interval, + min: overallTimeSeries.stats[0], + max: overallTimeSeries.stats[1], + }, + ], + // samplerShardSize + -1, + undefined, + abortSignal + )) as [NumericChartData] + )[0]; + } catch (e) { + if (!isRequestAbortedError(e)) { + logger.error( + `Failed to fetch the histogram data for group #${ + cpg.id + }, got: \n${e.toString()}` + ); + pushError(`Failed to fetch the histogram data for group #${cpg.id}.`); + } + return; + } + const histogram = + overallTimeSeries.data.map((o, i) => { + const current = cpgTimeSeries.data.find( + (d1) => d1.key_as_string === o.key_as_string + ) ?? { + doc_count: 0, + }; + return { + key: o.key, + key_as_string: o.key_as_string ?? '', + doc_count_change_point: current.doc_count, + doc_count_overall: Math.max(0, o.doc_count - current.doc_count), + }; + }) ?? []; + + push( + addChangePointsGroupHistogramAction([ + { + id: cpg.id, + histogram, + }, + ]) + ); + } + }); + } } } catch (e) { - logger.error( - `Failed to transform field/value pairs into groups, got: \n${e.toString()}` - ); - pushError(`Failed to transform field/value pairs into groups.`); + if (!isRequestAbortedError(e)) { + logger.error( + `Failed to transform field/value pairs into groups, got: \n${e.toString()}` + ); + pushError(`Failed to transform field/value pairs into groups.`); + } } } loaded += PROGRESS_STEP_HISTOGRAMS_GROUPS; - logDebugMessage('Fetch field/value histograms.'); + logDebugMessage(`Fetch ${changePoints.length} field/value histograms.`); // time series filtered by fields - if (changePoints && overallTimeSeries !== undefined) { - await asyncForEach(changePoints, async (cp) => { - if (overallTimeSeries !== undefined) { - const histogramQuery = { - bool: { - filter: [ - { - term: { [cp.fieldName]: cp.fieldValue }, - }, - ], - }, - }; - - let cpTimeSeries: NumericChartData; - - try { - cpTimeSeries = ( - (await fetchHistogramsForFields( - client, - request.body.index, - histogramQuery, - // fields - [ + if (changePoints.length > 0 && overallTimeSeries !== undefined) { + const changePointsChunks = chunk(changePoints, CHUNK_SIZE); + + for (const changePointsChunk of changePointsChunks) { + if (shouldStop) { + logDebugMessage('shouldStop abort fetching field/value histograms.'); + end(); + return; + } + + await asyncForEach(changePointsChunk, async (cp) => { + if (overallTimeSeries !== undefined) { + const histogramQuery = { + bool: { + filter: [ { - fieldName: request.body.timeFieldName, - type: KBN_FIELD_TYPES.DATE, - interval: overallTimeSeries.interval, - min: overallTimeSeries.stats[0], - max: overallTimeSeries.stats[1], + term: { [cp.fieldName]: cp.fieldValue }, }, ], - // samplerShardSize - -1, - undefined - )) as [NumericChartData] - )[0]; - } catch (e) { - logger.error( - `Failed to fetch the histogram data for field/value pair "${cp.fieldName}:${ - cp.fieldValue - }", got: \n${e.toString()}` - ); - pushError( - `Failed to fetch the histogram data for field/value pair "${cp.fieldName}:${cp.fieldValue}".` + }, + }; + + let cpTimeSeries: NumericChartData; + + try { + cpTimeSeries = ( + (await fetchHistogramsForFields( + client, + request.body.index, + histogramQuery, + // fields + [ + { + fieldName: request.body.timeFieldName, + type: KBN_FIELD_TYPES.DATE, + interval: overallTimeSeries.interval, + min: overallTimeSeries.stats[0], + max: overallTimeSeries.stats[1], + }, + ], + // samplerShardSize + -1, + undefined, + abortSignal + )) as [NumericChartData] + )[0]; + } catch (e) { + logger.error( + `Failed to fetch the histogram data for field/value pair "${cp.fieldName}:${ + cp.fieldValue + }", got: \n${e.toString()}` + ); + pushError( + `Failed to fetch the histogram data for field/value pair "${cp.fieldName}:${cp.fieldValue}".` + ); + return; + } + + const histogram = + overallTimeSeries.data.map((o, i) => { + const current = cpTimeSeries.data.find( + (d1) => d1.key_as_string === o.key_as_string + ) ?? { + doc_count: 0, + }; + return { + key: o.key, + key_as_string: o.key_as_string ?? '', + doc_count_change_point: current.doc_count, + doc_count_overall: Math.max(0, o.doc_count - current.doc_count), + }; + }) ?? []; + + const { fieldName, fieldValue } = cp; + + loaded += (1 / changePoints.length) * PROGRESS_STEP_HISTOGRAMS; + pushHistogramDataLoadingState(); + push( + addChangePointsHistogramAction([ + { + fieldName, + fieldValue, + histogram, + }, + ]) ); - return; } - - const histogram = - overallTimeSeries.data.map((o, i) => { - const current = cpTimeSeries.data.find( - (d1) => d1.key_as_string === o.key_as_string - ) ?? { - doc_count: 0, - }; - return { - key: o.key, - key_as_string: o.key_as_string ?? '', - doc_count_change_point: current.doc_count, - doc_count_overall: Math.max(0, o.doc_count - current.doc_count), - }; - }) ?? []; - - const { fieldName, fieldValue } = cp; - - loaded += (1 / changePoints.length) * PROGRESS_STEP_HISTOGRAMS; - pushHistogramDataLoadingState(); - push( - addChangePointsHistogramAction([ - { - fieldName, - fieldValue, - histogram, - }, - ]) - ); - } - }); + }); + } } endWithUpdatedLoadingState(); } catch (e) { - logger.error(`Explain log rate spikes analysis failed to finish, got: \n${e.toString()}`); - pushError(`Explain log rate spikes analysis failed to finish.`); + if (!isRequestAbortedError(e)) { + logger.error( + `Explain log rate spikes analysis failed to finish, got: \n${e.toString()}` + ); + pushError(`Explain log rate spikes analysis failed to finish.`); + } end(); } } diff --git a/x-pack/plugins/aiops/server/routes/queries/fetch_change_point_p_values.ts b/x-pack/plugins/aiops/server/routes/queries/fetch_change_point_p_values.ts index 0fb7f90c89c1..08165db08467 100644 --- a/x-pack/plugins/aiops/server/routes/queries/fetch_change_point_p_values.ts +++ b/x-pack/plugins/aiops/server/routes/queries/fetch_change_point_p_values.ts @@ -13,6 +13,8 @@ import { ChangePoint } from '@kbn/ml-agg-utils'; import { SPIKE_ANALYSIS_THRESHOLD } from '../../../common/constants'; import type { AiopsExplainLogRateSpikesSchema } from '../../../common/api/explain_log_rate_spikes'; +import { isRequestAbortedError } from '../../lib/is_request_aborted_error'; + import { getQueryWithParams } from './get_query_with_params'; import { getRequestBase } from './get_request_base'; @@ -95,23 +97,49 @@ export const fetchChangePointPValues = async ( params: AiopsExplainLogRateSpikesSchema, fieldNames: string[], logger: Logger, - emitError: (m: string) => void + emitError: (m: string) => void, + abortSignal?: AbortSignal ): Promise => { const result: ChangePoint[] = []; - for (const fieldName of fieldNames) { - const request = getChangePointRequest(params, fieldName); - const resp = await esClient.search(request); - - if (resp.aggregations === undefined) { + const settledPromises = await Promise.allSettled( + fieldNames.map((fieldName) => + esClient.search( + getChangePointRequest(params, fieldName), + { + signal: abortSignal, + maxRetries: 0, + } + ) + ) + ); + + function reportError(fieldName: string, error: unknown) { + if (!isRequestAbortedError(error)) { logger.error( `Failed to fetch p-value aggregation for fieldName "${fieldName}", got: \n${JSON.stringify( - resp, + error, null, 2 )}` ); emitError(`Failed to fetch p-value aggregation for fieldName "${fieldName}".`); + } + } + + for (const [index, settledPromise] of settledPromises.entries()) { + const fieldName = fieldNames[index]; + + if (settledPromise.status === 'rejected') { + reportError(fieldName, settledPromise.reason); + // Still continue the analysis even if individual p-value queries fail. + continue; + } + + const resp = settledPromise.value; + + if (resp.aggregations === undefined) { + reportError(fieldName, resp); // Still continue the analysis even if individual p-value queries fail. continue; } diff --git a/x-pack/plugins/aiops/server/routes/queries/fetch_field_candidates.ts b/x-pack/plugins/aiops/server/routes/queries/fetch_field_candidates.ts index 7a761d91c0da..036d8c0f51fc 100644 --- a/x-pack/plugins/aiops/server/routes/queries/fetch_field_candidates.ts +++ b/x-pack/plugins/aiops/server/routes/queries/fetch_field_candidates.ts @@ -47,14 +47,18 @@ export const getRandomDocsRequest = ( export const fetchFieldCandidates = async ( esClient: ElasticsearchClient, - params: AiopsExplainLogRateSpikesSchema + params: AiopsExplainLogRateSpikesSchema, + abortSignal?: AbortSignal ): Promise => { const { index } = params; // Get all supported fields - const respMapping = await esClient.fieldCaps({ - index, - fields: '*', - }); + const respMapping = await esClient.fieldCaps( + { + index, + fields: '*', + }, + { signal: abortSignal, maxRetries: 0 } + ); const finalFieldCandidates: Set = new Set([]); const acceptableFields: Set = new Set(); @@ -69,7 +73,10 @@ export const fetchFieldCandidates = async ( } }); - const resp = await esClient.search(getRandomDocsRequest(params)); + const resp = await esClient.search(getRandomDocsRequest(params), { + signal: abortSignal, + maxRetries: 0, + }); const sampledDocs = resp.hits.hits.map((d) => d.fields ?? {}); // Get all field names for each returned doc and flatten it diff --git a/x-pack/plugins/aiops/server/routes/queries/fetch_frequent_items.ts b/x-pack/plugins/aiops/server/routes/queries/fetch_frequent_items.ts index c9444aaca22a..aaf9af283c3e 100644 --- a/x-pack/plugins/aiops/server/routes/queries/fetch_frequent_items.ts +++ b/x-pack/plugins/aiops/server/routes/queries/fetch_frequent_items.ts @@ -56,7 +56,8 @@ export async function fetchFrequentItems( deviationMin: number, deviationMax: number, logger: Logger, - emitError: (m: string) => void + emitError: (m: string) => void, + abortSignal?: AbortSignal ) { // get unique fields from change points const fields = [...new Set(changePoints.map((t) => t.fieldName))]; @@ -127,7 +128,7 @@ export async function fetchFrequentItems( track_total_hits: true, }, }, - { maxRetries: 0 } + { signal: abortSignal, maxRetries: 0 } ); if (body.aggregations === undefined) { diff --git a/x-pack/plugins/alerting/server/authorization/alerting_authorization.ts b/x-pack/plugins/alerting/server/authorization/alerting_authorization.ts index 7138aabe9d26..2b403684c8d5 100644 --- a/x-pack/plugins/alerting/server/authorization/alerting_authorization.ts +++ b/x-pack/plugins/alerting/server/authorization/alerting_authorization.ts @@ -51,6 +51,7 @@ export enum WriteOperations { UnmuteAlert = 'unmuteAlert', Snooze = 'snooze', BulkEdit = 'bulkEdit', + BulkDelete = 'bulkDelete', Unsnooze = 'unsnooze', } diff --git a/x-pack/plugins/alerting/server/lib/wrap_scoped_cluster_client.test.ts b/x-pack/plugins/alerting/server/lib/wrap_scoped_cluster_client.test.ts index e86cd98f4a49..b36dfed980b0 100644 --- a/x-pack/plugins/alerting/server/lib/wrap_scoped_cluster_client.test.ts +++ b/x-pack/plugins/alerting/server/lib/wrap_scoped_cluster_client.test.ts @@ -25,7 +25,7 @@ const rule = { describe('wrapScopedClusterClient', () => { beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/alerting/server/lib/wrap_search_source_client.test.ts b/x-pack/plugins/alerting/server/lib/wrap_search_source_client.test.ts index 9c10e619e3eb..55dc0a5d6216 100644 --- a/x-pack/plugins/alerting/server/lib/wrap_search_source_client.test.ts +++ b/x-pack/plugins/alerting/server/lib/wrap_search_source_client.test.ts @@ -35,7 +35,7 @@ const createSearchSourceClientMock = () => { describe('wrapSearchSourceClient', () => { beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/alerting/server/routes/bulk_delete_rules.test.ts b/x-pack/plugins/alerting/server/routes/bulk_delete_rules.test.ts new file mode 100644 index 000000000000..8fc27cfbc005 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/bulk_delete_rules.test.ts @@ -0,0 +1,126 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { httpServiceMock } from '@kbn/core/server/mocks'; + +import { bulkDeleteRulesRoute } from './bulk_delete_rules'; +import { licenseStateMock } from '../lib/license_state.mock'; +import { mockHandlerArguments } from './_mock_handler_arguments'; +import { rulesClientMock } from '../rules_client.mock'; +import { RuleTypeDisabledError } from '../lib/errors/rule_type_disabled'; +import { verifyApiAccess } from '../lib/license_api_access'; + +const rulesClient = rulesClientMock.create(); + +jest.mock('../lib/license_api_access', () => ({ + verifyApiAccess: jest.fn(), +})); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('bulkDeleteRulesRoute', () => { + const bulkDeleteRequest = { filter: '' }; + const bulkDeleteResult = { errors: [], total: 1, taskIdsFailedToBeDeleted: [] }; + + it('should delete rules with proper parameters', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + bulkDeleteRulesRoute({ router, licenseState }); + + const [config, handler] = router.patch.mock.calls[0]; + + expect(config.path).toBe('/internal/alerting/rules/_bulk_delete'); + + rulesClient.bulkDeleteRules.mockResolvedValueOnce(bulkDeleteResult); + + const [context, req, res] = mockHandlerArguments( + { rulesClient }, + { + body: bulkDeleteRequest, + }, + ['ok'] + ); + + expect(await handler(context, req, res)).toEqual({ + body: bulkDeleteResult, + }); + + expect(rulesClient.bulkDeleteRules).toHaveBeenCalledTimes(1); + expect(rulesClient.bulkDeleteRules.mock.calls[0]).toEqual([bulkDeleteRequest]); + + expect(res.ok).toHaveBeenCalled(); + }); + + it('ensures the license allows bulk deleting rules', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + rulesClient.bulkDeleteRules.mockResolvedValueOnce(bulkDeleteResult); + + bulkDeleteRulesRoute({ router, licenseState }); + + const [, handler] = router.patch.mock.calls[0]; + + const [context, req, res] = mockHandlerArguments( + { rulesClient }, + { + body: bulkDeleteRequest, + } + ); + + await handler(context, req, res); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); + + it('ensures the license check prevents bulk deleting rules', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + (verifyApiAccess as jest.Mock).mockImplementation(() => { + throw new Error('Failure'); + }); + + bulkDeleteRulesRoute({ router, licenseState }); + + const [, handler] = router.patch.mock.calls[0]; + + const [context, req, res] = mockHandlerArguments( + { rulesClient }, + { + body: bulkDeleteRequest, + } + ); + + expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); + }); + + it('ensures the rule type gets validated for the license', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + bulkDeleteRulesRoute({ router, licenseState }); + + const [, handler] = router.patch.mock.calls[0]; + + rulesClient.bulkDeleteRules.mockRejectedValue( + new RuleTypeDisabledError('Fail', 'license_invalid') + ); + + const [context, req, res] = mockHandlerArguments({ rulesClient }, { params: {}, body: {} }, [ + 'ok', + 'forbidden', + ]); + + await handler(context, req, res); + + expect(res.forbidden).toHaveBeenCalledWith({ body: { message: 'Fail' } }); + }); +}); diff --git a/x-pack/plugins/alerting/server/routes/bulk_delete_rules.ts b/x-pack/plugins/alerting/server/routes/bulk_delete_rules.ts new file mode 100644 index 000000000000..1eca663de21a --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/bulk_delete_rules.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; +import { IRouter } from '@kbn/core/server'; +import { verifyAccessAndContext, handleDisabledApiKeysError } from './lib'; +import { ILicenseState, RuleTypeDisabledError } from '../lib'; +import { AlertingRequestHandlerContext, INTERNAL_BASE_ALERTING_API_PATH } from '../types'; + +export const bulkDeleteRulesRoute = ({ + router, + licenseState, +}: { + router: IRouter; + licenseState: ILicenseState; +}) => { + router.patch( + { + path: `${INTERNAL_BASE_ALERTING_API_PATH}/rules/_bulk_delete`, + validate: { + body: schema.object({ + filter: schema.maybe(schema.string()), + ids: schema.maybe(schema.arrayOf(schema.string(), { minSize: 1, maxSize: 1000 })), + }), + }, + }, + handleDisabledApiKeysError( + router.handleLegacyErrors( + verifyAccessAndContext(licenseState, async (context, req, res) => { + const rulesClient = (await context.alerting).getRulesClient(); + const { filter, ids } = req.body; + + try { + const result = await rulesClient.bulkDeleteRules({ filter, ids }); + return res.ok({ body: result }); + } catch (e) { + if (e instanceof RuleTypeDisabledError) { + return e.sendResponse(res); + } + throw e; + } + }) + ) + ) + ); +}; diff --git a/x-pack/plugins/alerting/server/routes/index.ts b/x-pack/plugins/alerting/server/routes/index.ts index de9e2f112d9e..dfb9c290af3d 100644 --- a/x-pack/plugins/alerting/server/routes/index.ts +++ b/x-pack/plugins/alerting/server/routes/index.ts @@ -38,6 +38,7 @@ import { bulkEditInternalRulesRoute } from './bulk_edit_rules'; import { snoozeRuleRoute } from './snooze_rule'; import { unsnoozeRuleRoute } from './unsnooze_rule'; import { runSoonRoute } from './run_soon'; +import { bulkDeleteRulesRoute } from './bulk_delete_rules'; export interface RouteOptions { router: IRouter; @@ -76,6 +77,7 @@ export function defineRoutes(opts: RouteOptions) { unmuteAlertRoute(router, licenseState); updateRuleApiKeyRoute(router, licenseState); bulkEditInternalRulesRoute(router, licenseState); + bulkDeleteRulesRoute({ router, licenseState }); snoozeRuleRoute(router, licenseState); unsnoozeRuleRoute(router, licenseState); runSoonRoute(router, licenseState); diff --git a/x-pack/plugins/alerting/server/rules_client.mock.ts b/x-pack/plugins/alerting/server/rules_client.mock.ts index 2092b98e48c0..aa29e64d2f46 100644 --- a/x-pack/plugins/alerting/server/rules_client.mock.ts +++ b/x-pack/plugins/alerting/server/rules_client.mock.ts @@ -36,6 +36,7 @@ const createRulesClientMock = () => { getActionErrorLog: jest.fn(), getSpaceId: jest.fn(), bulkEdit: jest.fn(), + bulkDeleteRules: jest.fn(), snooze: jest.fn(), unsnooze: jest.fn(), calculateIsSnoozedUntil: jest.fn(), diff --git a/x-pack/plugins/alerting/server/rules_client/lib/index.ts b/x-pack/plugins/alerting/server/rules_client/lib/index.ts index f3f11589d296..a221327d938e 100644 --- a/x-pack/plugins/alerting/server/rules_client/lib/index.ts +++ b/x-pack/plugins/alerting/server/rules_client/lib/index.ts @@ -8,5 +8,6 @@ export { mapSortField } from './map_sort_field'; export { validateOperationOnAttributes } from './validate_attributes'; export { retryIfBulkEditConflicts } from './retry_if_bulk_edit_conflicts'; +export { retryIfBulkDeleteConflicts } from './retry_if_bulk_delete_conflicts'; export { applyBulkEditOperation } from './apply_bulk_edit_operation'; export { buildKueryNodeFilter } from './build_kuery_node_filter'; diff --git a/x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_delete_conflicts.test.ts b/x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_delete_conflicts.test.ts new file mode 100644 index 000000000000..32a18ea7f098 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_delete_conflicts.test.ts @@ -0,0 +1,100 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { KueryNode } from '@kbn/es-query'; +import { loggingSystemMock } from '@kbn/core/server/mocks'; + +import { retryIfBulkDeleteConflicts } from './retry_if_bulk_delete_conflicts'; +import { RETRY_IF_CONFLICTS_ATTEMPTS } from './wait_before_next_retry'; + +const mockFilter: KueryNode = { + type: 'function', + value: 'mock', +}; + +const mockLogger = loggingSystemMock.create().get(); + +const mockSuccessfulResult = { + apiKeysToInvalidate: ['apiKey1'], + errors: [], + taskIdsToDelete: ['taskId1'], +}; + +const error409 = { + message: 'some fake message', + status: 409, + rule: { + id: 'fake_rule_id', + name: 'fake rule name', + }, +}; + +const getOperationConflictsTimes = (times: number) => { + return async () => { + conflictOperationMock(); + times--; + if (times >= 0) { + return { + apiKeysToInvalidate: [], + taskIdsToDelete: [], + errors: [error409], + }; + } + return mockSuccessfulResult; + }; +}; + +const OperationSuccessful = async () => mockSuccessfulResult; +const conflictOperationMock = jest.fn(); + +describe('retryIfBulkDeleteConflicts', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + + test('should work when operation is successful', async () => { + const result = await retryIfBulkDeleteConflicts(mockLogger, OperationSuccessful, mockFilter); + + expect(result).toEqual(mockSuccessfulResult); + }); + + test('should throw error when operation fails', async () => { + await expect( + retryIfBulkDeleteConflicts( + mockLogger, + async () => { + throw Error('Test failure'); + }, + mockFilter + ) + ).rejects.toThrowError('Test failure'); + }); + + test(`should return conflict errors when number of retries exceeds ${RETRY_IF_CONFLICTS_ATTEMPTS}`, async () => { + const result = await retryIfBulkDeleteConflicts( + mockLogger, + getOperationConflictsTimes(RETRY_IF_CONFLICTS_ATTEMPTS + 1), + mockFilter + ); + + expect(result.errors).toEqual([error409]); + expect(mockLogger.warn).toBeCalledWith('Bulk delete rules conflicts, exceeded retries'); + }); + + for (let i = 1; i <= RETRY_IF_CONFLICTS_ATTEMPTS; i++) { + test(`should work when operation conflicts ${i} times`, async () => { + const result = await retryIfBulkDeleteConflicts( + mockLogger, + getOperationConflictsTimes(i), + mockFilter + ); + + expect(conflictOperationMock.mock.calls.length).toBe(i + 1); + expect(result).toStrictEqual(mockSuccessfulResult); + }); + } +}); diff --git a/x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_delete_conflicts.ts b/x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_delete_conflicts.ts new file mode 100644 index 000000000000..529055b85e44 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_delete_conflicts.ts @@ -0,0 +1,130 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import pMap from 'p-map'; +import { chunk } from 'lodash'; +import { KueryNode } from '@kbn/es-query'; +import { Logger } from '@kbn/core/server'; +import { convertRuleIdsToKueryNode } from '../../lib'; +import { BulkDeleteError } from '../rules_client'; +import { waitBeforeNextRetry, RETRY_IF_CONFLICTS_ATTEMPTS } from './wait_before_next_retry'; + +const MAX_RULES_IDS_IN_RETRY = 1000; + +export type BulkDeleteOperation = (filter: KueryNode | null) => Promise<{ + apiKeysToInvalidate: string[]; + errors: BulkDeleteError[]; + taskIdsToDelete: string[]; +}>; + +interface ReturnRetry { + apiKeysToInvalidate: string[]; + errors: BulkDeleteError[]; + taskIdsToDelete: string[]; +} + +/** + * Retries BulkDelete requests + * If in response are presents conflicted savedObjects(409 statusCode), this util constructs filter with failed SO ids and retries bulkDelete operation until + * all SO updated or number of retries exceeded + * @param logger + * @param bulkEditOperation + * @param filter - KueryNode filter + * @param retries - number of retries left + * @param accApiKeysToInvalidate - accumulated apiKeys that need to be invalidated + * @param accErrors - accumulated conflict errors + * @param accTaskIdsToDelete - accumulated task ids + * @returns Promise + */ +export const retryIfBulkDeleteConflicts = async ( + logger: Logger, + bulkDeleteOperation: BulkDeleteOperation, + filter: KueryNode | null, + retries: number = RETRY_IF_CONFLICTS_ATTEMPTS, + accApiKeysToInvalidate: string[] = [], + accErrors: BulkDeleteError[] = [], + accTaskIdsToDelete: string[] = [] +): Promise => { + try { + const { + apiKeysToInvalidate: currentApiKeysToInvalidate, + errors: currentErrors, + taskIdsToDelete: currentTaskIdsToDelete, + } = await bulkDeleteOperation(filter); + + const apiKeysToInvalidate = [...accApiKeysToInvalidate, ...currentApiKeysToInvalidate]; + const taskIdsToDelete = [...accTaskIdsToDelete, ...currentTaskIdsToDelete]; + const errors = + retries <= 0 + ? [...accErrors, ...currentErrors] + : [...accErrors, ...currentErrors.filter((error) => error.status !== 409)]; + + const ruleIdsWithConflictError = currentErrors.reduce((acc, error) => { + if (error.status === 409) { + return [...acc, error.rule.id]; + } + return acc; + }, []); + + if (ruleIdsWithConflictError.length === 0) { + return { + apiKeysToInvalidate, + errors, + taskIdsToDelete, + }; + } + + if (retries <= 0) { + logger.warn('Bulk delete rules conflicts, exceeded retries'); + + return { + apiKeysToInvalidate, + errors, + taskIdsToDelete, + }; + } + + logger.debug( + `Bulk delete rules conflicts, retrying ..., ${ruleIdsWithConflictError.length} saved objects conflicted` + ); + + await waitBeforeNextRetry(retries); + + // here, we construct filter query with ids. But, due to a fact that number of conflicted saved objects can exceed few thousands we can encounter following error: + // "all shards failed: search_phase_execution_exception: [query_shard_exception] Reason: failed to create query: maxClauseCount is set to 2621" + // That's why we chunk processing ids into pieces by size equals to MAX_RULES_IDS_IN_RETRY + return ( + await pMap( + chunk(ruleIdsWithConflictError, MAX_RULES_IDS_IN_RETRY), + async (queryIds) => + retryIfBulkDeleteConflicts( + logger, + bulkDeleteOperation, + convertRuleIdsToKueryNode(queryIds), + retries - 1, + apiKeysToInvalidate, + errors, + taskIdsToDelete + ), + { + concurrency: 1, + } + ) + ).reduce( + (acc, item) => { + return { + apiKeysToInvalidate: [...acc.apiKeysToInvalidate, ...item.apiKeysToInvalidate], + errors: [...acc.errors, ...item.errors], + taskIdsToDelete: [...acc.taskIdsToDelete, ...item.taskIdsToDelete], + }; + }, + { apiKeysToInvalidate: [], errors: [], taskIdsToDelete: [] } + ); + } catch (err) { + throw err; + } +}; diff --git a/x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_edit_conflicts.test.ts b/x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_edit_conflicts.test.ts index ae2a83614ac2..5053b367b938 100644 --- a/x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_edit_conflicts.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_edit_conflicts.test.ts @@ -6,10 +6,8 @@ */ import { KueryNode } from '@kbn/es-query'; -import { - retryIfBulkEditConflicts, - RetryForConflictsAttempts, -} from './retry_if_bulk_edit_conflicts'; +import { retryIfBulkEditConflicts } from './retry_if_bulk_edit_conflicts'; +import { RETRY_IF_CONFLICTS_ATTEMPTS } from './wait_before_next_retry'; import { loggingSystemMock } from '@kbn/core/server/mocks'; const mockFilter: KueryNode = { @@ -112,11 +110,11 @@ describe('retryIfBulkEditConflicts', () => { ).rejects.toThrowError('Test failure'); }); - test(`should return conflict errors when number of retries exceeds ${RetryForConflictsAttempts}`, async () => { + test(`should return conflict errors when number of retries exceeds ${RETRY_IF_CONFLICTS_ATTEMPTS}`, async () => { const result = await retryIfBulkEditConflicts( mockLogger, mockOperationName, - getOperationConflictsTimes(RetryForConflictsAttempts + 1), + getOperationConflictsTimes(RETRY_IF_CONFLICTS_ATTEMPTS + 1), mockFilter ); @@ -132,7 +130,7 @@ describe('retryIfBulkEditConflicts', () => { expect(mockLogger.warn).toBeCalledWith(`${mockOperationName} conflicts, exceeded retries`); }); - for (let i = 1; i <= RetryForConflictsAttempts; i++) { + for (let i = 1; i <= RETRY_IF_CONFLICTS_ATTEMPTS; i++) { test(`should work when operation conflicts ${i} times`, async () => { const result = await retryIfBulkEditConflicts( mockLogger, diff --git a/x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_edit_conflicts.ts b/x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_edit_conflicts.ts index 9e1e60acb768..550e13a6bffe 100644 --- a/x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_edit_conflicts.ts +++ b/x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_edit_conflicts.ts @@ -12,15 +12,7 @@ import { Logger, SavedObjectsBulkUpdateObject, SavedObjectsUpdateResponse } from import { convertRuleIdsToKueryNode } from '../../lib'; import { BulkEditError } from '../rules_client'; import { RawRule } from '../../types'; - -// number of times to retry when conflicts occur -export const RetryForConflictsAttempts = 2; - -// milliseconds to wait before retrying when conflicts occur -// note: we considered making this random, to help avoid a stampede, but -// with 1 retry it probably doesn't matter, and adding randomness could -// make it harder to diagnose issues -const RetryForConflictsDelay = 250; +import { waitBeforeNextRetry, RETRY_IF_CONFLICTS_ATTEMPTS } from './wait_before_next_retry'; // max number of failed SO ids in one retry filter const MaxIdsNumberInRetryFilter = 1000; @@ -57,7 +49,7 @@ export const retryIfBulkEditConflicts = async ( name: string, bulkEditOperation: BulkEditOperation, filter: KueryNode | null, - retries: number = RetryForConflictsAttempts, + retries: number = RETRY_IF_CONFLICTS_ATTEMPTS, accApiKeysToInvalidate: string[] = [], accResults: Array> = [], accErrors: BulkEditError[] = [] @@ -154,13 +146,3 @@ export const retryIfBulkEditConflicts = async ( throw err; } }; - -// exponential delay before retry with adding random delay -async function waitBeforeNextRetry(retries: number): Promise { - const exponentialDelayMultiplier = 1 + (RetryForConflictsAttempts - retries) ** 2; - const randomDelayMs = Math.floor(Math.random() * 100); - - await new Promise((resolve) => - setTimeout(resolve, RetryForConflictsDelay * exponentialDelayMultiplier + randomDelayMs) - ); -} diff --git a/x-pack/plugins/alerting/server/rules_client/lib/wait_before_next_retry.test.ts b/x-pack/plugins/alerting/server/rules_client/lib/wait_before_next_retry.test.ts new file mode 100644 index 000000000000..2fd74a7e08a7 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/lib/wait_before_next_retry.test.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + getExponentialDelayMultiplier, + randomDelayMs, + RETRY_IF_CONFLICTS_DELAY, + RETRY_IF_CONFLICTS_ATTEMPTS, + waitBeforeNextRetry, +} from './wait_before_next_retry'; + +describe('waitBeforeNextRetry', () => { + const randomDelayPart = 0.1; + + beforeEach(() => { + jest.spyOn(global.Math, 'random').mockReturnValue(randomDelayPart); + jest.spyOn(window, 'setTimeout'); + }); + afterEach(() => { + jest.clearAllMocks(); + }); + + for (let i = 1; i <= RETRY_IF_CONFLICTS_ATTEMPTS; i++) { + it(`should set timout for ${i} tries`, async () => { + await waitBeforeNextRetry(i); + expect(setTimeout).toBeCalledTimes(1); + expect(setTimeout).toHaveBeenCalledWith( + expect.any(Function), + RETRY_IF_CONFLICTS_DELAY * getExponentialDelayMultiplier(i) + randomDelayMs + ); + }); + } +}); diff --git a/x-pack/plugins/alerting/server/rules_client/lib/wait_before_next_retry.ts b/x-pack/plugins/alerting/server/rules_client/lib/wait_before_next_retry.ts new file mode 100644 index 000000000000..f836de26c418 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/lib/wait_before_next_retry.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const RETRY_IF_CONFLICTS_ATTEMPTS = 2; + +// milliseconds to wait before retrying when conflicts occur +// note: we considered making this random, to help avoid a stampede, but +// with 1 retry it probably doesn't matter, and adding randomness could +// make it harder to diagnose issues +export const RETRY_IF_CONFLICTS_DELAY = 250; + +export const randomDelayMs = Math.floor(Math.random() * 100); +export const getExponentialDelayMultiplier = (retries: number) => + 1 + (RETRY_IF_CONFLICTS_ATTEMPTS - retries) ** 2; + +/** + * exponential delay before retry with adding random delay + */ +export const waitBeforeNextRetry = async (retries: number): Promise => { + const exponentialDelayMultiplier = getExponentialDelayMultiplier(retries); + + await new Promise((resolve) => + setTimeout(resolve, RETRY_IF_CONFLICTS_DELAY * exponentialDelayMultiplier + randomDelayMs) + ); +}; diff --git a/x-pack/plugins/alerting/server/rules_client/rules_client.ts b/x-pack/plugins/alerting/server/rules_client/rules_client.ts index d08f12f054a5..777cf340b53e 100644 --- a/x-pack/plugins/alerting/server/rules_client/rules_client.ts +++ b/x-pack/plugins/alerting/server/rules_client/rules_client.ts @@ -33,6 +33,7 @@ import { SavedObjectsUtils, SavedObjectAttributes, SavedObjectsBulkUpdateObject, + SavedObjectsBulkDeleteObject, SavedObjectsUpdateResponse, } from '@kbn/core/server'; import { ActionsClient, ActionsAuthorization } from '@kbn/actions-plugin/server'; @@ -104,6 +105,7 @@ import { mapSortField, validateOperationOnAttributes, retryIfBulkEditConflicts, + retryIfBulkDeleteConflicts, applyBulkEditOperation, buildKueryNodeFilter, } from './lib'; @@ -178,7 +180,7 @@ export interface RuleAggregation { }; } -export interface RuleBulkEditAggregation { +export interface RuleBulkOperationAggregation { alertTypeId: { buckets: Array<{ key: string[]; @@ -297,6 +299,16 @@ export type BulkEditOptions = | BulkEditOptionsFilter | BulkEditOptionsIds; +export interface BulkDeleteOptionsFilter { + filter?: string | KueryNode; +} + +export interface BulkDeleteOptionsIds { + ids?: string[]; +} + +export type BulkDeleteOptions = BulkDeleteOptionsFilter | BulkDeleteOptionsIds; + export interface BulkEditError { message: string; rule: { @@ -305,6 +317,15 @@ export interface BulkEditError { }; } +export interface BulkDeleteError { + message: string; + status: number; + rule: { + id: string; + name: string; + }; +} + export interface AggregateOptions extends IndexType { search?: string; defaultSearchOperator?: 'AND' | 'OR'; @@ -433,7 +454,7 @@ const extractedSavedObjectParamReferenceNamePrefix = 'param:'; // NOTE: Changing this prefix will require a migration to update the prefix in all existing `rule` saved objects const preconfiguredConnectorActionRefPrefix = 'preconfigured:'; -const MAX_RULES_NUMBER_FOR_BULK_EDIT = 10000; +const MAX_RULES_NUMBER_FOR_BULK_OPERATION = 10000; const API_KEY_GENERATE_CONCURRENCY = 50; const RULE_TYPE_CHECKS_CONCURRENCY = 50; @@ -1695,6 +1716,212 @@ export class RulesClient { ); } + private getAuthorizationFilter = async () => { + try { + const authorizationTuple = await this.authorization.getFindAuthorizationFilter( + AlertingAuthorizationEntity.Rule, + alertingAuthorizationFilterOpts + ); + return authorizationTuple.filter; + } catch (error) { + this.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.DELETE, + error, + }) + ); + throw error; + } + }; + + public bulkDeleteRules = async (options: BulkDeleteOptions) => { + const filter = (options as BulkDeleteOptionsFilter).filter; + const ids = (options as BulkDeleteOptionsIds).ids; + + if (!ids && !filter) { + throw Boom.badRequest( + "Either 'ids' or 'filter' property in method's arguments should be provided" + ); + } + + if (ids?.length === 0) { + throw Boom.badRequest("'ids' property should not be an empty array"); + } + + if (ids && filter) { + throw Boom.badRequest( + "Both 'filter' and 'ids' are supplied. Define either 'ids' or 'filter' properties in method's arguments" + ); + } + + const kueryNodeFilter = ids ? convertRuleIdsToKueryNode(ids) : buildKueryNodeFilter(filter); + const authorizationFilter = await this.getAuthorizationFilter(); + + const kueryNodeFilterWithAuth = + authorizationFilter && kueryNodeFilter + ? nodeBuilder.and([kueryNodeFilter, authorizationFilter as KueryNode]) + : kueryNodeFilter; + + const { aggregations, total } = await this.unsecuredSavedObjectsClient.find< + RawRule, + RuleBulkOperationAggregation + >({ + filter: kueryNodeFilterWithAuth, + page: 1, + perPage: 0, + type: 'alert', + aggs: { + alertTypeId: { + multi_terms: { + terms: [ + { field: 'alert.attributes.alertTypeId' }, + { field: 'alert.attributes.consumer' }, + ], + }, + }, + }, + }); + + if (total > MAX_RULES_NUMBER_FOR_BULK_OPERATION) { + throw Boom.badRequest( + `More than ${MAX_RULES_NUMBER_FOR_BULK_OPERATION} rules matched for bulk delete` + ); + } + + const buckets = aggregations?.alertTypeId.buckets; + + if (buckets === undefined || buckets?.length === 0) { + throw Boom.badRequest('No rules found for bulk delete'); + } + + await pMap( + buckets, + async ({ key: [ruleType, consumer] }) => { + this.ruleTypeRegistry.ensureRuleTypeEnabled(ruleType); + try { + await this.authorization.ensureAuthorized({ + ruleTypeId: ruleType, + consumer, + operation: WriteOperations.BulkDelete, + entity: AlertingAuthorizationEntity.Rule, + }); + } catch (error) { + this.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.DELETE, + error, + }) + ); + throw error; + } + }, + { concurrency: RULE_TYPE_CHECKS_CONCURRENCY } + ); + + const { apiKeysToInvalidate, errors, taskIdsToDelete } = await retryIfBulkDeleteConflicts( + this.logger, + (filterKueryNode: KueryNode | null) => this.bulkDeleteWithOCC({ filter: filterKueryNode }), + kueryNodeFilterWithAuth + ); + + const taskIdsFailedToBeDeleted: string[] = []; + if (taskIdsToDelete.length > 0) { + try { + const resultFromDeletingTasks = await this.taskManager.bulkRemoveIfExist(taskIdsToDelete); + resultFromDeletingTasks?.statuses.forEach((status) => { + if (!status.success) { + taskIdsFailedToBeDeleted.push(status.id); + } + }); + this.logger.debug( + `Successfully deleted schedules for underlying tasks: ${taskIdsToDelete + .filter((id) => taskIdsFailedToBeDeleted.includes(id)) + .join(', ')}` + ); + } catch (error) { + this.logger.error( + `Failure to delete schedules for underlying tasks: ${taskIdsToDelete.join( + ', ' + )}. TaskManager bulkRemoveIfExist failed with Error: ${error.message}` + ); + } + } + + await bulkMarkApiKeysForInvalidation( + { apiKeys: apiKeysToInvalidate }, + this.logger, + this.unsecuredSavedObjectsClient + ); + + return { errors, total, taskIdsFailedToBeDeleted }; + }; + + private bulkDeleteWithOCC = async ({ filter }: { filter: KueryNode | null }) => { + const rulesFinder = + await this.encryptedSavedObjectsClient.createPointInTimeFinderDecryptedAsInternalUser( + { + filter, + type: 'alert', + perPage: 100, + ...(this.namespace ? { namespaces: [this.namespace] } : undefined), + } + ); + + const rules: SavedObjectsBulkDeleteObject[] = []; + const apiKeysToInvalidate: string[] = []; + const taskIdsToDelete: string[] = []; + const errors: BulkDeleteError[] = []; + const apiKeyToRuleIdMapping: Record = {}; + const taskIdToRuleIdMapping: Record = {}; + const ruleNameToRuleIdMapping: Record = {}; + + for await (const response of rulesFinder.find()) { + for (const rule of response.saved_objects) { + if (rule.attributes.apiKey) { + apiKeyToRuleIdMapping[rule.id] = rule.attributes.apiKey; + } + if (rule.attributes.name) { + ruleNameToRuleIdMapping[rule.id] = rule.attributes.name; + } + if (rule.attributes.scheduledTaskId) { + taskIdToRuleIdMapping[rule.id] = rule.attributes.scheduledTaskId; + } + rules.push(rule); + + this.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.DELETE, + outcome: 'unknown', + savedObject: { type: 'alert', id: rule.id }, + }) + ); + } + } + + const result = await this.unsecuredSavedObjectsClient.bulkDelete(rules); + + result.statuses.forEach((status) => { + if (status.error === undefined) { + if (apiKeyToRuleIdMapping[status.id]) { + apiKeysToInvalidate.push(apiKeyToRuleIdMapping[status.id]); + } + if (taskIdToRuleIdMapping[status.id]) { + taskIdsToDelete.push(taskIdToRuleIdMapping[status.id]); + } + } else { + errors.push({ + message: status.error.message ?? 'n/a', + status: status.error.statusCode, + rule: { + id: status.id, + name: ruleNameToRuleIdMapping[status.id] ?? 'n/a', + }, + }); + } + }); + return { apiKeysToInvalidate, errors, taskIdsToDelete }; + }; + public async bulkEdit( options: BulkEditOptions ): Promise<{ @@ -1737,7 +1964,7 @@ export class RulesClient { const { aggregations, total } = await this.unsecuredSavedObjectsClient.find< RawRule, - RuleBulkEditAggregation + RuleBulkOperationAggregation >({ filter: qNodeFilterWithAuth, page: 1, @@ -1755,9 +1982,9 @@ export class RulesClient { }, }); - if (total > MAX_RULES_NUMBER_FOR_BULK_EDIT) { + if (total > MAX_RULES_NUMBER_FOR_BULK_OPERATION) { throw Boom.badRequest( - `More than ${MAX_RULES_NUMBER_FOR_BULK_EDIT} rules matched for bulk edit` + `More than ${MAX_RULES_NUMBER_FOR_BULK_OPERATION} rules matched for bulk edit` ); } const buckets = aggregations?.alertTypeId.buckets; diff --git a/x-pack/plugins/alerting/server/rules_client/tests/bulk_delete.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/bulk_delete.test.ts new file mode 100644 index 000000000000..5e0a71edcae5 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/tests/bulk_delete.test.ts @@ -0,0 +1,492 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { RulesClient, ConstructorOptions } from '../rules_client'; +import { savedObjectsClientMock } from '@kbn/core/server/mocks'; +import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; +import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; +import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; +import { RecoveredActionGroup } from '../../../common'; +import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks'; +import { actionsAuthorizationMock } from '@kbn/actions-plugin/server/mocks'; +import { AlertingAuthorization } from '../../authorization/alerting_authorization'; +import { ActionsAuthorization } from '@kbn/actions-plugin/server'; +import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks'; +import { getBeforeSetup, setGlobalDate } from './lib'; +import { bulkMarkApiKeysForInvalidation } from '../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation'; +import { loggerMock } from '@kbn/logging-mocks'; + +jest.mock('../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation', () => ({ + bulkMarkApiKeysForInvalidation: jest.fn(), +})); + +const taskManager = taskManagerMock.createStart(); +const ruleTypeRegistry = ruleTypeRegistryMock.create(); +const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); +const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); +const authorization = alertingAuthorizationMock.create(); +const actionsAuthorization = actionsAuthorizationMock.create(); +const auditLogger = auditLoggerMock.create(); +const logger = loggerMock.create(); + +const kibanaVersion = 'v8.2.0'; +const createAPIKeyMock = jest.fn(); +const rulesClientParams: jest.Mocked = { + taskManager, + ruleTypeRegistry, + unsecuredSavedObjectsClient, + authorization: authorization as unknown as AlertingAuthorization, + actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization, + spaceId: 'default', + namespace: 'default', + getUserName: jest.fn(), + createAPIKey: createAPIKeyMock, + logger, + encryptedSavedObjectsClient: encryptedSavedObjects, + getActionsClient: jest.fn(), + getEventLogClient: jest.fn(), + kibanaVersion, + auditLogger, + minimumScheduleInterval: { value: '1m', enforce: false }, +}; + +beforeEach(() => { + getBeforeSetup(rulesClientParams, taskManager, ruleTypeRegistry); + (auditLogger.log as jest.Mock).mockClear(); +}); + +setGlobalDate(); + +describe('bulkDelete', () => { + let rulesClient: RulesClient; + const existingRule = { + id: 'id1', + type: 'alert', + attributes: {}, + references: [], + version: '123', + }; + const existingDecryptedRule1 = { + ...existingRule, + attributes: { + ...existingRule.attributes, + scheduledTaskId: 'taskId1', + apiKey: Buffer.from('123:abc').toString('base64'), + }, + }; + const existingDecryptedRule2 = { + ...existingRule, + id: 'id2', + attributes: { + ...existingRule.attributes, + scheduledTaskId: 'taskId2', + apiKey: Buffer.from('321:abc').toString('base64'), + }, + }; + + const mockCreatePointInTimeFinderAsInternalUser = ( + response = { saved_objects: [existingDecryptedRule1, existingDecryptedRule2] } + ) => { + encryptedSavedObjects.createPointInTimeFinderDecryptedAsInternalUser = jest + .fn() + .mockResolvedValue({ + close: jest.fn(), + find: function* asyncGenerator() { + yield response; + }, + }); + }; + + beforeEach(async () => { + rulesClient = new RulesClient(rulesClientParams); + authorization.getFindAuthorizationFilter.mockResolvedValue({ + ensureRuleTypeIsAuthorized() {}, + }); + + unsecuredSavedObjectsClient.find.mockResolvedValue({ + aggregations: { + alertTypeId: { + buckets: [{ key: ['myType', 'myApp'], key_as_string: 'myType|myApp', doc_count: 2 }], + }, + }, + saved_objects: [], + per_page: 0, + page: 0, + total: 2, + }); + + ruleTypeRegistry.get.mockReturnValue({ + id: 'myType', + name: 'Test', + actionGroups: [ + { id: 'default', name: 'Default' }, + { id: 'custom', name: 'Not the Default' }, + ], + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + isExportable: true, + recoveryActionGroup: RecoveredActionGroup, + async executor() {}, + producer: 'alerts', + }); + }); + + test('should try to delete rules, one successful and one with 500 error', async () => { + mockCreatePointInTimeFinderAsInternalUser(); + unsecuredSavedObjectsClient.bulkDelete.mockResolvedValue({ + statuses: [ + { id: 'id1', type: 'alert', success: true }, + { + id: 'id2', + type: 'alert', + success: false, + error: { + error: '', + message: 'UPS', + statusCode: 500, + }, + }, + ], + }); + + const result = await rulesClient.bulkDeleteRules({ filter: 'fake_filter' }); + + expect(unsecuredSavedObjectsClient.bulkDelete).toHaveBeenCalledTimes(1); + expect(unsecuredSavedObjectsClient.bulkDelete).toHaveBeenCalledWith([ + existingDecryptedRule1, + existingDecryptedRule2, + ]); + expect(taskManager.bulkRemoveIfExist).toHaveBeenCalledTimes(1); + expect(taskManager.bulkRemoveIfExist).toHaveBeenCalledWith(['taskId1']); + expect(bulkMarkApiKeysForInvalidation).toHaveBeenCalledTimes(1); + expect(bulkMarkApiKeysForInvalidation).toHaveBeenCalledWith( + { apiKeys: ['MTIzOmFiYw=='] }, + expect.anything(), + expect.anything() + ); + expect(result).toStrictEqual({ + errors: [{ message: 'UPS', rule: { id: 'id2', name: 'n/a' }, status: 500 }], + total: 2, + taskIdsFailedToBeDeleted: [], + }); + }); + + test('should try to delete rules, one successful and one with 409 error, which will not be deleted with retry', async () => { + unsecuredSavedObjectsClient.bulkDelete + .mockResolvedValueOnce({ + statuses: [ + { id: 'id1', type: 'alert', success: true }, + { + id: 'id2', + type: 'alert', + success: false, + error: { + error: '', + message: 'UPS', + statusCode: 409, + }, + }, + ], + }) + .mockResolvedValueOnce({ + statuses: [ + { + id: 'id2', + type: 'alert', + success: false, + error: { + error: '', + message: 'UPS', + statusCode: 409, + }, + }, + ], + }) + .mockResolvedValueOnce({ + statuses: [ + { + id: 'id2', + type: 'alert', + success: false, + error: { + error: '', + message: 'UPS', + statusCode: 409, + }, + }, + ], + }); + + encryptedSavedObjects.createPointInTimeFinderDecryptedAsInternalUser = jest + .fn() + .mockResolvedValueOnce({ + close: jest.fn(), + find: function* asyncGenerator() { + yield { saved_objects: [existingDecryptedRule1, existingDecryptedRule2] }; + }, + }) + .mockResolvedValueOnce({ + close: jest.fn(), + find: function* asyncGenerator() { + yield { saved_objects: [existingDecryptedRule2] }; + }, + }) + .mockResolvedValueOnce({ + close: jest.fn(), + find: function* asyncGenerator() { + yield { saved_objects: [existingDecryptedRule2] }; + }, + }); + + const result = await rulesClient.bulkDeleteRules({ ids: ['id1', 'id2'] }); + + expect(unsecuredSavedObjectsClient.bulkDelete).toHaveBeenCalledTimes(3); + expect(taskManager.bulkRemoveIfExist).toHaveBeenCalledTimes(1); + expect(taskManager.bulkRemoveIfExist).toHaveBeenCalledWith(['taskId1']); + expect(bulkMarkApiKeysForInvalidation).toHaveBeenCalledTimes(1); + expect(bulkMarkApiKeysForInvalidation).toHaveBeenCalledWith( + { apiKeys: ['MTIzOmFiYw=='] }, + expect.anything(), + expect.anything() + ); + expect(result).toStrictEqual({ + errors: [{ message: 'UPS', rule: { id: 'id2', name: 'n/a' }, status: 409 }], + total: 2, + taskIdsFailedToBeDeleted: [], + }); + }); + + test('should try to delete rules, one successful and one with 409 error, which successfully will be deleted with retry', async () => { + unsecuredSavedObjectsClient.bulkDelete + .mockResolvedValueOnce({ + statuses: [ + { id: 'id1', type: 'alert', success: true }, + { + id: 'id2', + type: 'alert', + success: false, + error: { + error: '', + message: 'UPS', + statusCode: 409, + }, + }, + ], + }) + .mockResolvedValueOnce({ + statuses: [ + { + id: 'id2', + type: 'alert', + success: true, + }, + ], + }); + + encryptedSavedObjects.createPointInTimeFinderDecryptedAsInternalUser = jest + .fn() + .mockResolvedValueOnce({ + close: jest.fn(), + find: function* asyncGenerator() { + yield { saved_objects: [existingDecryptedRule1, existingDecryptedRule2] }; + }, + }) + .mockResolvedValueOnce({ + close: jest.fn(), + find: function* asyncGenerator() { + yield { saved_objects: [existingDecryptedRule2] }; + }, + }) + .mockResolvedValueOnce({ + close: jest.fn(), + find: function* asyncGenerator() { + yield { saved_objects: [existingDecryptedRule2] }; + }, + }); + + const result = await rulesClient.bulkDeleteRules({ ids: ['id1', 'id2'] }); + + expect(unsecuredSavedObjectsClient.bulkDelete).toHaveBeenCalledTimes(2); + expect(taskManager.bulkRemoveIfExist).toHaveBeenCalledTimes(1); + expect(taskManager.bulkRemoveIfExist).toHaveBeenCalledWith(['taskId1', 'taskId2']); + expect(bulkMarkApiKeysForInvalidation).toHaveBeenCalledTimes(1); + expect(bulkMarkApiKeysForInvalidation).toHaveBeenCalledWith( + { apiKeys: ['MTIzOmFiYw==', 'MzIxOmFiYw=='] }, + expect.anything(), + expect.anything() + ); + expect(result).toStrictEqual({ + errors: [], + total: 2, + taskIdsFailedToBeDeleted: [], + }); + }); + + test('should thow an error if number of matched rules greater than 10,000', async () => { + unsecuredSavedObjectsClient.find.mockResolvedValue({ + aggregations: { + alertTypeId: { + buckets: [{ key: ['myType', 'myApp'], key_as_string: 'myType|myApp', doc_count: 2 }], + }, + }, + saved_objects: [], + per_page: 0, + page: 0, + total: 10001, + }); + + await expect(rulesClient.bulkDeleteRules({ filter: 'fake_filter' })).rejects.toThrow( + 'More than 10000 rules matched for bulk delete' + ); + }); + + test('should throw an error if we do not get buckets', async () => { + mockCreatePointInTimeFinderAsInternalUser(); + unsecuredSavedObjectsClient.find.mockResolvedValue({ + aggregations: { + alertTypeId: {}, + }, + saved_objects: [], + per_page: 0, + page: 0, + total: 2, + }); + + await expect(rulesClient.bulkDeleteRules({ filter: 'fake_filter' })).rejects.toThrow( + 'No rules found for bulk delete' + ); + }); + + describe('taskManager', () => { + test('should return task id if deleting task failed', async () => { + mockCreatePointInTimeFinderAsInternalUser(); + unsecuredSavedObjectsClient.bulkDelete.mockResolvedValue({ + statuses: [ + { id: 'id1', type: 'alert', success: true }, + { id: 'id2', type: 'alert', success: true }, + ], + }); + taskManager.bulkRemoveIfExist.mockImplementation(async () => ({ + statuses: [ + { + id: 'taskId1', + type: 'alert', + success: true, + }, + { + id: 'taskId2', + type: 'alert', + success: false, + error: { + error: '', + message: 'UPS', + statusCode: 500, + }, + }, + ], + })); + + const result = await rulesClient.bulkDeleteRules({ filter: 'fake_filter' }); + + expect(logger.debug).toBeCalledTimes(1); + expect(logger.debug).toBeCalledWith( + 'Successfully deleted schedules for underlying tasks: taskId2' + ); + expect(result).toStrictEqual({ + errors: [], + total: 2, + taskIdsFailedToBeDeleted: ['taskId2'], + }); + }); + + test('should not throw an error if taskManager throw an error', async () => { + mockCreatePointInTimeFinderAsInternalUser(); + unsecuredSavedObjectsClient.bulkDelete.mockResolvedValue({ + statuses: [ + { id: 'id1', type: 'alert', success: true }, + { id: 'id2', type: 'alert', success: true }, + ], + }); + taskManager.bulkRemoveIfExist.mockImplementation(() => { + throw new Error('UPS'); + }); + + const result = await rulesClient.bulkDeleteRules({ filter: 'fake_filter' }); + + expect(logger.error).toBeCalledTimes(1); + expect(logger.error).toBeCalledWith( + 'Failure to delete schedules for underlying tasks: taskId1, taskId2. TaskManager bulkRemoveIfExist failed with Error: UPS' + ); + expect(result).toStrictEqual({ + errors: [], + taskIdsFailedToBeDeleted: [], + total: 2, + }); + }); + }); + + describe('auditLogger', () => { + jest.spyOn(auditLogger, 'log').mockImplementation(); + + test('logs audit event when deleting rules', async () => { + mockCreatePointInTimeFinderAsInternalUser(); + unsecuredSavedObjectsClient.bulkDelete.mockResolvedValue({ + statuses: [ + { id: 'id1', type: 'alert', success: true }, + { id: 'id2', type: 'alert', success: true }, + ], + }); + + await rulesClient.bulkDeleteRules({ filter: 'fake_filter' }); + + expect(auditLogger.log.mock.calls[0][0]?.event?.action).toEqual('rule_delete'); + expect(auditLogger.log.mock.calls[0][0]?.event?.outcome).toEqual('unknown'); + expect(auditLogger.log.mock.calls[0][0]?.kibana).toEqual({ + saved_object: { id: 'id1', type: 'alert' }, + }); + expect(auditLogger.log.mock.calls[1][0]?.event?.action).toEqual('rule_delete'); + expect(auditLogger.log.mock.calls[1][0]?.event?.outcome).toEqual('unknown'); + expect(auditLogger.log.mock.calls[1][0]?.kibana).toEqual({ + saved_object: { id: 'id2', type: 'alert' }, + }); + }); + + test('logs audit event when authentication failed', async () => { + mockCreatePointInTimeFinderAsInternalUser(); + authorization.ensureAuthorized.mockImplementation(() => { + throw new Error('Unauthorized'); + }); + unsecuredSavedObjectsClient.bulkDelete.mockResolvedValue({ + statuses: [{ id: 'id1', type: 'alert', success: true }], + }); + + await expect(rulesClient.bulkDeleteRules({ filter: 'fake_filter' })).rejects.toThrowError( + 'Unauthorized' + ); + + expect(auditLogger.log.mock.calls[0][0]?.event?.action).toEqual('rule_delete'); + expect(auditLogger.log.mock.calls[0][0]?.event?.outcome).toEqual('failure'); + }); + + test('logs audit event when getting an authorization filter failed', async () => { + mockCreatePointInTimeFinderAsInternalUser(); + authorization.getFindAuthorizationFilter.mockImplementation(() => { + throw new Error('Error'); + }); + unsecuredSavedObjectsClient.bulkDelete.mockResolvedValue({ + statuses: [{ id: 'id1', type: 'alert', success: true }], + }); + + await expect(rulesClient.bulkDeleteRules({ filter: 'fake_filter' })).rejects.toThrowError( + 'Error' + ); + + expect(auditLogger.log.mock.calls[0][0]?.event?.action).toEqual('rule_delete'); + expect(auditLogger.log.mock.calls[0][0]?.event?.outcome).toEqual('failure'); + }); + }); +}); diff --git a/x-pack/plugins/alerting/server/rules_client/tests/lib.ts b/x-pack/plugins/alerting/server/rules_client/tests/lib.ts index 37eccba1d0d5..6a1e84b38d03 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/lib.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/lib.ts @@ -51,6 +51,9 @@ export function getBeforeSetup( rulesClientParams.createAPIKey.mockResolvedValue({ apiKeysEnabled: false }); rulesClientParams.getUserName.mockResolvedValue('elastic'); taskManager.runSoon.mockResolvedValue({ id: '' }); + taskManager.bulkRemoveIfExist.mockResolvedValue({ + statuses: [{ id: 'taskId', type: 'alert', success: true }], + }); const actionsClient = actionsClientMock.create(); actionsClient.getBulk.mockResolvedValueOnce([ diff --git a/x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap b/x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap index 95c36d24aad5..8a1ae899ddd3 100644 --- a/x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap +++ b/x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap @@ -75,6 +75,8 @@ exports[`Error FAAS_DURATION 1`] = `undefined`; exports[`Error FAAS_ID 1`] = `undefined`; +exports[`Error FAAS_NAME 1`] = `undefined`; + exports[`Error FAAS_TRIGGER_TYPE 1`] = `undefined`; exports[`Error HOST 1`] = ` @@ -330,6 +332,8 @@ exports[`Span FAAS_DURATION 1`] = `undefined`; exports[`Span FAAS_ID 1`] = `undefined`; +exports[`Span FAAS_NAME 1`] = `undefined`; + exports[`Span FAAS_TRIGGER_TYPE 1`] = `undefined`; exports[`Span HOST 1`] = `undefined`; @@ -581,6 +585,8 @@ exports[`Transaction FAAS_DURATION 1`] = `undefined`; exports[`Transaction FAAS_ID 1`] = `undefined`; +exports[`Transaction FAAS_NAME 1`] = `undefined`; + exports[`Transaction FAAS_TRIGGER_TYPE 1`] = `undefined`; exports[`Transaction HOST 1`] = ` diff --git a/x-pack/plugins/apm/common/agent_configuration/runtime_types/trace_continuation_strategy_rt.ts b/x-pack/plugins/apm/common/agent_configuration/runtime_types/trace_continuation_strategy_rt.ts new file mode 100644 index 000000000000..13eed0a78f96 --- /dev/null +++ b/x-pack/plugins/apm/common/agent_configuration/runtime_types/trace_continuation_strategy_rt.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as t from 'io-ts'; + +export const traceContinuationStrategyRt = t.union([ + t.literal('continue'), + t.literal('restart'), + t.literal('restart_external'), +]); diff --git a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/__snapshots__/index.test.ts.snap b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/__snapshots__/index.test.ts.snap index fc42af5ff772..a4bc508856b4 100644 --- a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/__snapshots__/index.test.ts.snap +++ b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/__snapshots__/index.test.ts.snap @@ -47,21 +47,57 @@ Array [ "type": "select", "validationName": "(\\"off\\" | \\"errors\\" | \\"transactions\\" | \\"all\\")", }, + Object { + "key": "capture_body_content_types", + "type": "text", + "validationName": "string", + }, Object { "key": "capture_headers", "type": "boolean", "validationName": "(\\"true\\" | \\"false\\")", }, + Object { + "key": "capture_jmx_metrics", + "type": "text", + "validationName": "string", + }, Object { "key": "circuit_breaker_enabled", "type": "boolean", "validationName": "(\\"true\\" | \\"false\\")", }, + Object { + "key": "dedot_custom_metrics", + "type": "boolean", + "validationName": "(\\"true\\" | \\"false\\")", + }, Object { "key": "enable_log_correlation", "type": "boolean", "validationName": "(\\"true\\" | \\"false\\")", }, + Object { + "key": "exit_span_min_duration", + "min": "0ms", + "type": "duration", + "units": Array [ + "ms", + "s", + "m", + ], + "validationName": "durationRt", + }, + Object { + "key": "ignore_exceptions", + "type": "text", + "validationName": "string", + }, + Object { + "key": "ignore_message_queues", + "type": "text", + "validationName": "string", + }, Object { "key": "log_level", "options": Array [ @@ -156,6 +192,33 @@ Array [ ], "validationName": "durationRt", }, + Object { + "key": "span_compression_enabled", + "type": "boolean", + "validationName": "(\\"true\\" | \\"false\\")", + }, + Object { + "key": "span_compression_exact_match_max_duration", + "min": "0ms", + "type": "duration", + "units": Array [ + "ms", + "s", + "m", + ], + "validationName": "durationRt", + }, + Object { + "key": "span_compression_same_kind_max_duration", + "min": "0ms", + "type": "duration", + "units": Array [ + "ms", + "s", + "m", + ], + "validationName": "durationRt", + }, Object { "key": "span_frames_min_duration", "min": "-1ms", @@ -205,11 +268,40 @@ Array [ "type": "float", "validationName": "floatRt", }, + Object { + "key": "trace_continuation_strategy", + "options": Array [ + Object { + "text": "continue", + "value": "continue", + }, + Object { + "text": "restart", + "value": "restart", + }, + Object { + "text": "restart_external", + "value": "restart_external", + }, + ], + "type": "select", + "validationName": "(\\"continue\\" | \\"restart\\" | \\"restart_external\\")", + }, + Object { + "key": "trace_methods", + "type": "text", + "validationName": "string", + }, Object { "key": "transaction_ignore_urls", "type": "text", "validationName": "string", }, + Object { + "key": "transaction_ignore_user_agents", + "type": "text", + "validationName": "string", + }, Object { "key": "transaction_max_spans", "max": undefined, @@ -217,10 +309,25 @@ Array [ "type": "integer", "validationName": "integerRt", }, + Object { + "key": "transaction_name_groups", + "type": "text", + "validationName": "string", + }, Object { "key": "transaction_sample_rate", "type": "float", "validationName": "floatRt", }, + Object { + "key": "unnest_exceptions", + "type": "text", + "validationName": "string", + }, + Object { + "key": "use_path_as_transaction_name", + "type": "boolean", + "validationName": "(\\"true\\" | \\"false\\")", + }, ] `; diff --git a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/general_settings.ts b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/general_settings.ts index 0e565e1d8803..4e0f37fc76f3 100644 --- a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/general_settings.ts +++ b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/general_settings.ts @@ -8,6 +8,7 @@ import { i18n } from '@kbn/i18n'; import { captureBodyRt } from '../runtime_types/capture_body_rt'; import { logLevelRt } from '../runtime_types/log_level_rt'; +import { traceContinuationStrategyRt } from '../runtime_types/trace_continuation_strategy_rt'; import { RawSettingDefinition } from './types'; export const generalSettings: RawSettingDefinition[] = [ @@ -72,6 +73,29 @@ export const generalSettings: RawSettingDefinition[] = [ excludeAgents: ['js-base', 'rum-js', 'php'], }, + { + key: 'capture_body_content_types', + type: 'text', + defaultValue: + 'application/x-www-form-urlencoded*, text/*, application/json*, application/xml*', + label: i18n.translate( + 'xpack.apm.agentConfig.captureBodyContentTypes.label', + { + defaultMessage: 'Capture Body Content Types', + } + ), + description: i18n.translate( + 'xpack.apm.agentConfig.captureBodyContentTypes.description', + { + defaultMessage: + 'Configures which content types should be recorded.\n' + + '\n' + + 'The defaults end with a wildcard so that content types like `text/plain; charset=utf-8` are captured as well.', + } + ), + includeAgents: ['java'], + }, + // Capture headers { key: 'capture_headers', @@ -90,6 +114,67 @@ export const generalSettings: RawSettingDefinition[] = [ excludeAgents: ['js-base', 'rum-js', 'nodejs', 'php'], }, + { + key: 'dedot_custom_metrics', + type: 'boolean', + defaultValue: 'true', + label: i18n.translate('xpack.apm.agentConfig.dedotCustomMetrics.label', { + defaultMessage: 'Dedot custom metrics', + }), + description: i18n.translate( + 'xpack.apm.agentConfig.dedotCustomMetrics.description', + { + defaultMessage: + 'Replaces dots with underscores in the metric names for custom metrics.\n' + + '\n' + + 'WARNING: Setting this to `false` can lead to mapping conflicts as dots indicate nesting in Elasticsearch.\n' + + 'An example of when a conflict happens is two metrics with the name `foo` and `foo.bar`.\n' + + 'The first metric maps `foo` to a number and the second metric maps `foo` as an object.', + } + ), + includeAgents: ['java'], + }, + + { + key: 'exit_span_min_duration', + type: 'duration', + defaultValue: '0ms', + min: '0ms', + label: i18n.translate('xpack.apm.agentConfig.exitSpanMinDuration.label', { + defaultMessage: 'Exit span min duration', + }), + description: i18n.translate( + 'xpack.apm.agentConfig.exitSpanMinDuration.description', + { + defaultMessage: + 'Exit spans are spans that represent a call to an external service, like a database. If such calls are very short, they are usually not relevant and can be ignored.\n' + + '\n' + + 'NOTE: If a span propagates distributed tracing ids, it will not be ignored, even if it is shorter than the configured threshold. This is to ensure that no broken traces are recorded.', + } + ), + includeAgents: ['java'], + }, + + { + key: 'ignore_message_queues', + type: 'text', + defaultValue: '', + label: i18n.translate('xpack.apm.agentConfig.ignoreMessageQueues.label', { + defaultMessage: 'Ignore message queues', + }), + description: i18n.translate( + 'xpack.apm.agentConfig.ignoreMessageQueues.description', + { + defaultMessage: + 'Used to filter out specific messaging queues/topics from being traced. \n' + + '\n' + + 'This property should be set to an array containing one or more strings.\n' + + 'When set, sends-to and receives-from the specified queues/topic will be ignored.', + } + ), + includeAgents: ['java'], + }, + // LOG_LEVEL { key: 'log_level', @@ -147,6 +232,68 @@ export const generalSettings: RawSettingDefinition[] = [ includeAgents: ['java'], }, + { + key: 'span_compression_enabled', + type: 'boolean', + defaultValue: 'true', + label: i18n.translate( + 'xpack.apm.agentConfig.spanCompressionEnabled.label', + { + defaultMessage: 'Span compression enabled', + } + ), + description: i18n.translate( + 'xpack.apm.agentConfig.spanCompressionEnabled.description', + { + defaultMessage: + 'Setting this option to true will enable span compression feature.\n' + + 'Span compression reduces the collection, processing, and storage overhead, and removes clutter from the UI. The tradeoff is that some information such as DB statements of all the compressed spans will not be collected.', + } + ), + includeAgents: ['java'], + }, + + { + key: 'span_compression_exact_match_max_duration', + type: 'duration', + defaultValue: '50ms', + min: '0ms', + label: i18n.translate( + 'xpack.apm.agentConfig.spanCompressionExactMatchMaxDuration.label', + { + defaultMessage: 'Span compression exact match max duration', + } + ), + description: i18n.translate( + 'xpack.apm.agentConfig.spanCompressionExactMatchMaxDuration.description', + { + defaultMessage: + 'Consecutive spans that are exact match and that are under this threshold will be compressed into a single composite span. This option does not apply to composite spans. This reduces the collection, processing, and storage overhead, and removes clutter from the UI. The tradeoff is that the DB statements of all the compressed spans will not be collected.', + } + ), + includeAgents: ['java'], + }, + { + key: 'span_compression_same_kind_max_duration', + type: 'duration', + defaultValue: '0ms', + min: '0ms', + label: i18n.translate( + 'xpack.apm.agentConfig.spanCompressionSameKindMaxDuration.label', + { + defaultMessage: 'Span compression same kind max duration', + } + ), + description: i18n.translate( + 'xpack.apm.agentConfig.spanCompressionSameKindMaxDuration.description', + { + defaultMessage: + 'Consecutive spans to the same destination that are under this threshold will be compressed into a single composite span. This option does not apply to composite spans. This reduces the collection, processing, and storage overhead, and removes clutter from the UI. The tradeoff is that the DB statements of all the compressed spans will not be collected.', + } + ), + includeAgents: ['java'], + }, + // SPAN_FRAMES_MIN_DURATION { key: 'span_frames_min_duration', @@ -184,6 +331,44 @@ export const generalSettings: RawSettingDefinition[] = [ includeAgents: ['java', 'dotnet', 'go'], }, + { + key: 'trace_continuation_strategy', + validation: traceContinuationStrategyRt, + type: 'select', + defaultValue: 'continue', + label: i18n.translate( + 'xpack.apm.agentConfig.traceContinuationStrategy.label', + { + defaultMessage: 'Trace continuation strategy', + } + ), + description: i18n.translate( + 'xpack.apm.agentConfig.traceContinuationStrategy.description', + { + defaultMessage: + 'This option allows some control over how the APM agent handles W3C trace-context headers on incoming requests. By default, the `traceparent` and `tracestate` headers are used per W3C spec for distributed tracing. However, in certain cases it can be helpful to not use the incoming `traceparent` header. Some example use cases:\n' + + '\n' + + '* An Elastic-monitored service is receiving requests with `traceparent` headers from unmonitored services.\n' + + '* An Elastic-monitored service is publicly exposed, and does not want tracing data (trace-ids, sampling decisions) to possibly be spoofed by user requests.\n' + + '\n' + + 'Valid values are:\n' + + "* 'continue': The default behavior. An incoming `traceparent` value is used to continue the trace and determine the sampling decision.\n" + + "* 'restart': Always ignores the `traceparent` header of incoming requests. A new trace-id will be generated and the sampling decision will be made based on transaction_sample_rate. A span link will be made to the incoming `traceparent`.\n" + + "* 'restart_external': If an incoming request includes the `es` vendor flag in `tracestate`, then any `traceparent` will be considered internal and will be handled as described for 'continue' above. Otherwise, any `traceparent` is considered external and will be handled as described for 'restart' above.\n" + + '\n' + + 'Starting with Elastic Observability 8.2, span links are visible in trace views.\n' + + '\n' + + 'This option is case-insensitive.', + } + ), + options: [ + { text: 'continue', value: 'continue' }, + { text: 'restart', value: 'restart' }, + { text: 'restart_external', value: 'restart_external' }, + ], + includeAgents: ['java'], + }, + // Transaction max spans { key: 'transaction_max_spans', @@ -257,4 +442,76 @@ export const generalSettings: RawSettingDefinition[] = [ ), includeAgents: ['java', 'nodejs', 'python', 'dotnet', 'ruby', 'go'], }, + + { + key: 'transaction_ignore_user_agents', + type: 'text', + defaultValue: '', + label: i18n.translate( + 'xpack.apm.agentConfig.transactionIgnoreUserAgents.label', + { + defaultMessage: 'Transaction ignore user agents', + } + ), + description: i18n.translate( + 'xpack.apm.agentConfig.transactionIgnoreUserAgents.description', + { + defaultMessage: + 'Used to restrict requests from certain User-Agents from being instrumented.\n' + + '\n' + + 'When an incoming HTTP request is detected,\n' + + 'the User-Agent from the request headers will be tested against each element in this list.\n' + + 'Example: `curl/*`, `*pingdom*`', + } + ), + includeAgents: ['java'], + }, + + { + key: 'use_path_as_transaction_name', + type: 'boolean', + defaultValue: 'false', + label: i18n.translate( + 'xpack.apm.agentConfig.usePathAsTransactionName.label', + { + defaultMessage: 'Use path as transaction name', + } + ), + description: i18n.translate( + 'xpack.apm.agentConfig.usePathAsTransactionName.description', + { + defaultMessage: + 'If set to `true`,\n' + + 'transaction names of unsupported or partially-supported frameworks will be in the form of `$method $path` instead of just `$method unknown route`.\n' + + '\n' + + 'WARNING: If your URLs contain path parameters like `/user/$userId`,\n' + + 'you should be very careful when enabling this flag,\n' + + 'as it can lead to an explosion of transaction groups.\n' + + 'Take a look at the `transaction_name_groups` option on how to mitigate this problem by grouping URLs together.', + } + ), + includeAgents: ['java'], + }, + + { + key: 'transaction_name_groups', + type: 'text', + defaultValue: '', + label: i18n.translate('xpack.apm.agentConfig.transactionNameGroups.label', { + defaultMessage: 'Transaction name groups', + }), + description: i18n.translate( + 'xpack.apm.agentConfig.transactionNameGroups.description', + { + defaultMessage: + 'With this option,\n' + + 'you can group transaction names that contain dynamic parts with a wildcard expression.\n' + + 'For example,\n' + + 'the pattern `GET /user/*/cart` would consolidate transactions,\n' + + 'such as `GET /users/42/cart` and `GET /users/73/cart` into a single transaction name `GET /users/*/cart`,\n' + + 'hence reducing the transaction name cardinality.', + } + ), + includeAgents: ['java'], + }, ]; diff --git a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/index.test.ts b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/index.test.ts index c9d33d37b660..e41a3b6896ee 100644 --- a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/index.test.ts +++ b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/index.test.ts @@ -43,134 +43,150 @@ describe('filterByAgent', () => { describe('options per agent', () => { it('go', () => { - expect(getSettingKeysForAgent('go')).toEqual([ - 'capture_body', - 'capture_headers', - 'log_level', - 'recording', - 'sanitize_field_names', - 'span_frames_min_duration', - 'stack_trace_limit', - 'transaction_ignore_urls', - 'transaction_max_spans', - 'transaction_sample_rate', - ]); + expect(getSettingKeysForAgent('go')).toEqual( + expect.arrayContaining([ + 'capture_body', + 'capture_headers', + 'log_level', + 'recording', + 'sanitize_field_names', + 'span_frames_min_duration', + 'stack_trace_limit', + 'transaction_ignore_urls', + 'transaction_max_spans', + 'transaction_sample_rate', + ]) + ); }); it('java', () => { - expect(getSettingKeysForAgent('java')).toEqual([ - 'api_request_size', - 'api_request_time', - 'capture_body', - 'capture_headers', - 'circuit_breaker_enabled', - 'enable_log_correlation', - 'log_level', - 'profiling_inferred_spans_enabled', - 'profiling_inferred_spans_excluded_classes', - 'profiling_inferred_spans_included_classes', - 'profiling_inferred_spans_min_duration', - 'profiling_inferred_spans_sampling_interval', - 'recording', - 'sanitize_field_names', - 'server_timeout', - 'span_frames_min_duration', - 'stack_trace_limit', - 'stress_monitor_cpu_duration_threshold', - 'stress_monitor_gc_relief_threshold', - 'stress_monitor_gc_stress_threshold', - 'stress_monitor_system_cpu_relief_threshold', - 'stress_monitor_system_cpu_stress_threshold', - 'transaction_ignore_urls', - 'transaction_max_spans', - 'transaction_sample_rate', - ]); + expect(getSettingKeysForAgent('java')).toEqual( + expect.arrayContaining([ + 'api_request_size', + 'api_request_time', + 'capture_body', + 'capture_headers', + 'circuit_breaker_enabled', + 'enable_log_correlation', + 'log_level', + 'profiling_inferred_spans_enabled', + 'profiling_inferred_spans_excluded_classes', + 'profiling_inferred_spans_included_classes', + 'profiling_inferred_spans_min_duration', + 'profiling_inferred_spans_sampling_interval', + 'recording', + 'sanitize_field_names', + 'server_timeout', + 'span_frames_min_duration', + 'stack_trace_limit', + 'stress_monitor_cpu_duration_threshold', + 'stress_monitor_gc_relief_threshold', + 'stress_monitor_gc_stress_threshold', + 'stress_monitor_system_cpu_relief_threshold', + 'stress_monitor_system_cpu_stress_threshold', + 'transaction_ignore_urls', + 'transaction_max_spans', + 'transaction_sample_rate', + ]) + ); }); it('js-base', () => { - expect(getSettingKeysForAgent('js-base')).toEqual([ - 'transaction_sample_rate', - ]); + expect(getSettingKeysForAgent('js-base')).toEqual( + expect.arrayContaining(['transaction_sample_rate']) + ); }); it('rum-js', () => { - expect(getSettingKeysForAgent('rum-js')).toEqual([ - 'transaction_sample_rate', - ]); + expect(getSettingKeysForAgent('rum-js')).toEqual( + expect.arrayContaining(['transaction_sample_rate']) + ); }); it('nodejs', () => { - expect(getSettingKeysForAgent('nodejs')).toEqual([ - 'capture_body', - 'log_level', - 'sanitize_field_names', - 'transaction_ignore_urls', - 'transaction_max_spans', - 'transaction_sample_rate', - ]); + expect(getSettingKeysForAgent('nodejs')).toEqual( + expect.arrayContaining([ + 'capture_body', + 'log_level', + 'sanitize_field_names', + 'transaction_ignore_urls', + 'transaction_max_spans', + 'transaction_sample_rate', + ]) + ); }); it('python', () => { - expect(getSettingKeysForAgent('python')).toEqual([ - 'api_request_size', - 'api_request_time', - 'capture_body', - 'capture_headers', - 'log_level', - 'recording', - 'sanitize_field_names', - 'span_frames_min_duration', - 'transaction_ignore_urls', - 'transaction_max_spans', - 'transaction_sample_rate', - ]); + expect(getSettingKeysForAgent('python')).toEqual( + expect.arrayContaining([ + 'api_request_size', + 'api_request_time', + 'capture_body', + 'capture_headers', + 'log_level', + 'recording', + 'sanitize_field_names', + 'span_frames_min_duration', + 'transaction_ignore_urls', + 'transaction_max_spans', + 'transaction_sample_rate', + ]) + ); }); it('dotnet', () => { - expect(getSettingKeysForAgent('dotnet')).toEqual([ - 'capture_body', - 'capture_headers', - 'log_level', - 'recording', - 'sanitize_field_names', - 'span_frames_min_duration', - 'stack_trace_limit', - 'transaction_ignore_urls', - 'transaction_max_spans', - 'transaction_sample_rate', - ]); + expect(getSettingKeysForAgent('dotnet')).toEqual( + expect.arrayContaining([ + 'capture_body', + 'capture_headers', + 'log_level', + 'recording', + 'sanitize_field_names', + 'span_frames_min_duration', + 'stack_trace_limit', + 'transaction_ignore_urls', + 'transaction_max_spans', + 'transaction_sample_rate', + ]) + ); }); it('ruby', () => { - expect(getSettingKeysForAgent('ruby')).toEqual([ - 'api_request_size', - 'api_request_time', - 'capture_body', - 'capture_headers', - 'log_level', - 'recording', - 'sanitize_field_names', - 'span_frames_min_duration', - 'transaction_ignore_urls', - 'transaction_max_spans', - 'transaction_sample_rate', - ]); + expect(getSettingKeysForAgent('ruby')).toEqual( + expect.arrayContaining([ + 'api_request_size', + 'api_request_time', + 'capture_body', + 'capture_headers', + 'log_level', + 'recording', + 'sanitize_field_names', + 'span_frames_min_duration', + 'transaction_ignore_urls', + 'transaction_max_spans', + 'transaction_sample_rate', + ]) + ); }); it('php', () => { - expect(getSettingKeysForAgent('php')).toEqual([ - 'log_level', - 'recording', - 'transaction_max_spans', - 'transaction_sample_rate', - ]); + expect(getSettingKeysForAgent('php')).toEqual( + expect.arrayContaining([ + 'log_level', + 'recording', + 'transaction_max_spans', + 'transaction_sample_rate', + ]) + ); }); it('"All" services (no agent name)', () => { - expect(getSettingKeysForAgent(undefined)).toEqual([ - 'transaction_max_spans', - 'transaction_sample_rate', - ]); + expect(getSettingKeysForAgent(undefined)).toEqual( + expect.arrayContaining([ + 'transaction_max_spans', + 'transaction_sample_rate', + ]) + ); }); }); }); diff --git a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/java_settings.ts b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/java_settings.ts index dc4eb89cf7de..694eef7885b3 100644 --- a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/java_settings.ts +++ b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/java_settings.ts @@ -238,4 +238,95 @@ export const javaSettings: RawSettingDefinition[] = [ ), includeAgents: ['java'], }, + + { + key: 'capture_jmx_metrics', + type: 'text', + defaultValue: '', + label: i18n.translate('xpack.apm.agentConfig.captureJmxMetrics.label', { + defaultMessage: 'Capture JMX metrics', + }), + description: i18n.translate( + 'xpack.apm.agentConfig.captureJmxMetrics.description', + { + defaultMessage: + 'Report metrics from JMX to the APM Server\n' + + '\n' + + 'Can contain multiple comma separated JMX metric definitions:\n' + + '\n' + + '`object_name[] attribute[:metric_name=]`\n' + + '\n' + + 'See the Java agent documentation for more details.', + } + ), + includeAgents: ['java'], + }, + + { + key: 'ignore_exceptions', + type: 'text', + defaultValue: '', + label: i18n.translate('xpack.apm.agentConfig.ignoreExceptions.label', { + defaultMessage: 'Ignore exceptions', + }), + description: i18n.translate( + 'xpack.apm.agentConfig.ignoreExceptions.description', + { + defaultMessage: + 'A list of exceptions that should be ignored and not reported as errors.\n' + + 'This allows to ignore exceptions thrown in regular control flow that are not actual errors.', + } + ), + includeAgents: ['java'], + }, + + { + key: 'trace_methods', + type: 'text', + defaultValue: '', + label: i18n.translate('xpack.apm.agentConfig.traceMethods.label', { + defaultMessage: 'Trace methods', + }), + description: i18n.translate( + 'xpack.apm.agentConfig.traceMethods.description', + { + defaultMessage: + 'A list of methods for which to create a transaction or span.\n' + + '\n' + + 'If you want to monitor a large number of methods,\n' + + 'use `profiling_inferred_spans_enabled`.\n' + + '\n' + + 'This works by instrumenting each matching method to include code that creates a span for the method.\n' + + 'While creating a span is quite cheap in terms of performance,\n' + + 'instrumenting a whole code base or a method which is executed in a tight loop leads to significant overhead.\n' + + '\n' + + 'NOTE: Only use wildcards if necessary.\n' + + 'The more methods you match the more overhead will be caused by the agent.\n' + + 'Also note that there is a maximum amount of spans per transaction `transaction_max_spans`.\n' + + '\n' + + 'See the Java agent documentation for more details.', + } + ), + includeAgents: ['java'], + }, + + { + key: 'unnest_exceptions', + type: 'text', + defaultValue: '', + label: i18n.translate('xpack.apm.agentConfig.unnestExceptions.label', { + defaultMessage: 'Unnest exceptions', + }), + description: i18n.translate( + 'xpack.apm.agentConfig.unnestExceptions.description', + { + defaultMessage: + 'When reporting exceptions,\n' + + 'un-nests the exceptions matching the wildcard pattern.\n' + + "This can come in handy for Spring's `org.springframework.web.util.NestedServletException`,\n" + + 'for example.', + } + ), + includeAgents: ['java'], + }, ]; diff --git a/x-pack/plugins/apm/common/elasticsearch_fieldnames.ts b/x-pack/plugins/apm/common/elasticsearch_fieldnames.ts index f29bf3c607e8..bbd5755e3afb 100644 --- a/x-pack/plugins/apm/common/elasticsearch_fieldnames.ts +++ b/x-pack/plugins/apm/common/elasticsearch_fieldnames.ts @@ -147,6 +147,7 @@ export const USER_AGENT_DEVICE = 'user_agent.device.name'; export const USER_AGENT_OS = 'user_agent.os.name'; export const FAAS_ID = 'faas.id'; +export const FAAS_NAME = 'faas.name'; export const FAAS_COLDSTART = 'faas.coldstart'; export const FAAS_TRIGGER_TYPE = 'faas.trigger.type'; export const FAAS_DURATION = 'faas.duration'; diff --git a/x-pack/plugins/apm/common/serverless.test.ts b/x-pack/plugins/apm/common/serverless.test.ts new file mode 100644 index 000000000000..5473e9d735d8 --- /dev/null +++ b/x-pack/plugins/apm/common/serverless.test.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { getServerlessFunctionNameFromId } from './serverless'; + +describe('getServerlessFunctionNameFromId', () => { + it('returns serverlessId when regex does not match', () => { + expect(getServerlessFunctionNameFromId('foo')).toEqual('foo'); + }); + + it('returns correct serverless function name', () => { + expect( + getServerlessFunctionNameFromId( + 'arn:aws:lambda:us-west-2:123456789012:function:my-function' + ) + ).toEqual('my-function'); + expect( + getServerlessFunctionNameFromId( + 'arn:aws:lambda:us-west-2:123456789012:function:my:function' + ) + ).toEqual('my:function'); + }); +}); diff --git a/x-pack/plugins/apm/common/serverless.ts b/x-pack/plugins/apm/common/serverless.ts new file mode 100644 index 000000000000..5e91cb04d868 --- /dev/null +++ b/x-pack/plugins/apm/common/serverless.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/** + * Gets the serverless function name from serverless id. + * Serverless id example: arn:aws:lambda:us-west-2:123456789012:function:my-function + * The function name is the last part after "function:" + */ +const serverlessIdRegex = /function:(.*)/; +export function getServerlessFunctionNameFromId(serverlessId: string) { + const match = serverlessIdRegex.exec(serverlessId); + return match ? match[1] : serverlessId; +} diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/storage_explorer/storage_explorer.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/storage_explorer/storage_explorer.cy.ts index 2efebecf2575..3fdeff2e406a 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/storage_explorer/storage_explorer.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/storage_explorer/storage_explorer.cy.ts @@ -79,6 +79,8 @@ describe('Storage Explorer', () => { it('has a list of summary stats', () => { cy.contains('Total APM size'); + cy.contains('Disk space used'); + cy.contains('Incremental APM size'); cy.contains('Daily data generation'); cy.contains('Traces per minute'); cy.contains('Number of services'); @@ -200,6 +202,7 @@ describe('Storage Explorer', () => { cy.contains('Service storage details'); cy.getByTestSubj('storageExplorerTimeseriesChart'); cy.getByTestSubj('serviceStorageDetailsTable'); + cy.getByTestSubj('storageExplorerIndicesStatsTable'); }); }); }); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts b/x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts index 9e6e0189e636..013296d815a5 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts @@ -21,7 +21,7 @@ Cypress.Commands.add('loginAsEditorUser', () => { Cypress.Commands.add('loginAsMonitorUser', () => { return cy.loginAs({ - username: ApmUsername.apmMonitorIndices, + username: ApmUsername.apmMonitorClusterAndIndices, password: 'changeme', }); }); diff --git a/x-pack/plugins/apm/public/components/app/correlations/use_failed_transactions_correlations.test.tsx b/x-pack/plugins/apm/public/components/app/correlations/use_failed_transactions_correlations.test.tsx index 54d455519329..00c768fc1abb 100644 --- a/x-pack/plugins/apm/public/components/app/correlations/use_failed_transactions_correlations.test.tsx +++ b/x-pack/plugins/apm/public/components/app/correlations/use_failed_transactions_correlations.test.tsx @@ -108,7 +108,7 @@ function wrapper({ describe('useFailedTransactionsCorrelations', () => { beforeEach(async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); // Running all pending timers and switching to real timers using Jest afterEach(() => { diff --git a/x-pack/plugins/apm/public/components/app/correlations/use_latency_correlations.test.tsx b/x-pack/plugins/apm/public/components/app/correlations/use_latency_correlations.test.tsx index b63327901a02..a09530960589 100644 --- a/x-pack/plugins/apm/public/components/app/correlations/use_latency_correlations.test.tsx +++ b/x-pack/plugins/apm/public/components/app/correlations/use_latency_correlations.test.tsx @@ -102,7 +102,7 @@ function wrapper({ describe('useLatencyCorrelations', () => { beforeEach(async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterEach(() => { jest.useRealTimers(); diff --git a/x-pack/plugins/apm/public/components/app/metrics/index.tsx b/x-pack/plugins/apm/public/components/app/metrics/index.tsx index 3149a13b940f..0463df3778e8 100644 --- a/x-pack/plugins/apm/public/components/app/metrics/index.tsx +++ b/x-pack/plugins/apm/public/components/app/metrics/index.tsx @@ -6,17 +6,30 @@ */ import React from 'react'; -import { isJavaAgentName, isJRubyAgent } from '../../../../common/agent_name'; +import { + isJavaAgentName, + isJRubyAgent, + isServerlessAgent, +} from '../../../../common/agent_name'; import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; +import { ServerlessMetrics } from './serverless_metrics'; import { ServiceMetrics } from './service_metrics'; import { JvmMetricsOverview } from './jvm_metrics_overview'; export function Metrics() { const { agentName, runtimeName } = useApmServiceContext(); + const isServerless = isServerlessAgent(runtimeName); - if (isJavaAgentName(agentName) || isJRubyAgent(agentName, runtimeName)) { + if ( + !isServerless && + (isJavaAgentName(agentName) || isJRubyAgent(agentName, runtimeName)) + ) { return ; } + if (isServerless) { + return ; + } + return ; } diff --git a/x-pack/plugins/apm/public/components/app/metrics/serverless_metrics/index.tsx b/x-pack/plugins/apm/public/components/app/metrics/serverless_metrics/index.tsx new file mode 100644 index 000000000000..439192503244 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/metrics/serverless_metrics/index.tsx @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import React from 'react'; +import { ServerlessFunctions } from './serverless_functions'; +import { ServerlessSummary } from './serverless_summary'; +import { ServerlessActiveInstances } from './serverless_active_instances'; +import { ServerlessMetricsCharts } from './serverless_metrics_charts'; +import { ChartPointerEventContextProvider } from '../../../../context/chart_pointer_event/chart_pointer_event_context'; + +interface Props { + serverlessId?: string; +} + +export function ServerlessMetrics({ serverlessId }: Props) { + return ( + + + + + {!serverlessId && ( + + + + )} + + + + + + + + + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/metrics/serverless_metrics/serverless_active_instances.tsx b/x-pack/plugins/apm/public/components/app/metrics/serverless_metrics/serverless_active_instances.tsx new file mode 100644 index 000000000000..9c44d472c5b7 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/metrics/serverless_metrics/serverless_active_instances.tsx @@ -0,0 +1,223 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { + EuiBasicTableColumn, + EuiFlexGroup, + EuiFlexItem, + EuiInMemoryTable, + euiPaletteColorBlind, + EuiPanel, + EuiTitle, + PropertySort, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React, { useMemo } from 'react'; +import { + asDynamicBytes, + asInteger, + asMillisecondDuration, +} from '../../../../../common/utils/formatters'; +import { Coordinate, TimeSeries } from '../../../../../typings/timeseries'; +import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; +import { useApmParams } from '../../../../hooks/use_apm_params'; +import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; +import { useTimeRange } from '../../../../hooks/use_time_range'; +import { APIReturnType } from '../../../../services/rest/create_call_apm_api'; +import { TimeseriesChart } from '../../../shared/charts/timeseries_chart'; +import { ListMetric } from '../../../shared/list_metric'; +import { ServerlessFunctionNameLink } from './serverless_function_name_link'; + +type ServerlessActiveInstances = + APIReturnType<'GET /internal/apm/services/{serviceName}/metrics/serverless/active_instances'>; + +const palette = euiPaletteColorBlind({ rotations: 2 }); + +interface Props { + serverlessId?: string; +} + +export function ServerlessActiveInstances({ serverlessId }: Props) { + const { + query: { environment, kuery, rangeFrom, rangeTo }, + } = useApmParams('/services/{serviceName}/metrics'); + const { start, end } = useTimeRange({ rangeFrom, rangeTo }); + const { serviceName } = useApmServiceContext(); + + const { data = { activeInstances: [], timeseries: [] }, status } = useFetcher( + (callApmApi) => { + if (!start || !end) { + return undefined; + } + return callApmApi( + 'GET /internal/apm/services/{serviceName}/metrics/serverless/active_instances', + { + params: { + path: { + serviceName, + }, + query: { + kuery, + environment, + start, + end, + serverlessId, + }, + }, + } + ); + }, + [kuery, environment, serviceName, start, end, serverlessId] + ); + + const isLoading = status === FETCH_STATUS.LOADING; + + const columns: Array< + EuiBasicTableColumn + > = [ + { + field: 'serverlessFunctionName', + name: i18n.translate( + 'xpack.apm.serverlessMetrics.activeInstances.functionName', + { defaultMessage: 'Function name' } + ), + sortable: true, + truncateText: true, + render: (_, item) => { + return ( + + ); + }, + }, + { + field: 'activeInstanceName', + name: i18n.translate('xpack.apm.serverlessMetrics.activeInstances.name', { + defaultMessage: 'Name', + }), + sortable: true, + }, + { + field: 'serverlessDurationAvg', + name: i18n.translate( + 'xpack.apm.serverlessMetrics.serverlessFunctions.functionDuration', + { defaultMessage: 'Function duration' } + ), + sortable: true, + render: (_, { serverlessDurationAvg, timeseries }) => { + return ( + + ); + }, + }, + { + field: 'billedDurationAvg', + name: i18n.translate( + 'xpack.apm.serverlessMetrics.activeInstances.billedDuration', + { defaultMessage: 'Billed duration' } + ), + sortable: true, + render: (_, { billedDurationAvg, timeseries }) => { + return ( + + ); + }, + }, + { + field: 'avgMemoryUsed', + name: i18n.translate( + 'xpack.apm.serverlessMetrics.activeInstances.memoryUsageAvg', + { defaultMessage: 'Memory usage avg.' } + ), + sortable: true, + render: (_, { avgMemoryUsed }) => { + return asDynamicBytes(avgMemoryUsed); + }, + }, + { + field: 'memorySize', + name: i18n.translate( + 'xpack.apm.serverlessMetrics.activeInstances.memorySize', + { defaultMessage: 'Memory size' } + ), + sortable: true, + render: (_, { memorySize }) => { + return asDynamicBytes(memorySize); + }, + }, + ]; + + const sorting = useMemo( + () => ({ + sort: { + field: 'serverlessDurationAvg', + direction: 'desc', + } as PropertySort, + }), + [] + ); + + const charts: Array> = useMemo( + () => [ + { + title: i18n.translate( + 'xpack.apm.serverlessMetrics.activeInstances.title', + { defaultMessage: 'Active instances' } + ), + data: data.timeseries, + type: 'bar', + color: palette[2], + }, + ], + [data.timeseries] + ); + + return ( + + + + +

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

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

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

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

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

+
+
+ + + {i18n.translate('xpack.apm.serverlessMetrics.summary.feedback', { + defaultMessage: 'Send feedback', + })} + + +
+ + + + + + {showVerticalRule && } + + + + + + + + + + {showVerticalRule && } + +
+ ); +} diff --git a/x-pack/plugins/apm/public/components/app/metrics_details/index.tsx b/x-pack/plugins/apm/public/components/app/metrics_details/index.tsx index 4adbb25c26d1..13082d41f597 100644 --- a/x-pack/plugins/apm/public/components/app/metrics_details/index.tsx +++ b/x-pack/plugins/apm/public/components/app/metrics_details/index.tsx @@ -5,8 +5,21 @@ * 2.0. */ import React from 'react'; +import { isServerlessAgent } from '../../../../common/agent_name'; +import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; +import { useApmParams } from '../../../hooks/use_apm_params'; +import { ServerlessMetricsDetails } from './serverless_metrics_details'; import { ServiceNodeMetrics } from './service_node_metrics'; export function MetricsDetails() { - return ; + const { + path: { id }, + } = useApmParams('/services/{serviceName}/metrics/{id}'); + const { runtimeName } = useApmServiceContext(); + + if (isServerlessAgent(runtimeName)) { + return ; + } + + return ; } diff --git a/x-pack/plugins/apm/public/components/app/metrics_details/serverless_metrics_details/index.tsx b/x-pack/plugins/apm/public/components/app/metrics_details/serverless_metrics_details/index.tsx new file mode 100644 index 000000000000..7b80ed857134 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/metrics_details/serverless_metrics_details/index.tsx @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React, { useMemo } from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; +import { ServerlessMetrics } from '../../metrics/serverless_metrics'; +import { getServerlessFunctionNameFromId } from '../../../../../common/serverless'; +import { useBreadcrumb } from '../../../../context/breadcrumbs/use_breadcrumb'; +import { useApmRouter } from '../../../../hooks/use_apm_router'; +import { useApmParams } from '../../../../hooks/use_apm_params'; + +interface Props { + serverlessId: string; +} + +export function ServerlessMetricsDetails({ serverlessId }: Props) { + const apmRouter = useApmRouter(); + const { path, query } = useApmParams('/services/{serviceName}/metrics/{id}'); + + const serverlessFunctionName = useMemo( + () => getServerlessFunctionNameFromId(serverlessId), + [serverlessId] + ); + + useBreadcrumb( + () => ({ + title: serverlessFunctionName, + href: apmRouter.link('/services/{serviceName}/metrics/{id}', { + path, + query, + }), + }), + [apmRouter, path, query, serverlessFunctionName] + ); + + return ( + + + +

{serverlessFunctionName}

+
+
+ + + +
+ ); +} diff --git a/x-pack/plugins/apm/public/components/app/metrics_details/service_node_metrics/index.test.tsx b/x-pack/plugins/apm/public/components/app/metrics_details/service_node_metrics/index.test.tsx index 9356cab2d1f9..e8deea04a71e 100644 --- a/x-pack/plugins/apm/public/components/app/metrics_details/service_node_metrics/index.test.tsx +++ b/x-pack/plugins/apm/public/components/app/metrics_details/service_node_metrics/index.test.tsx @@ -16,7 +16,7 @@ describe('ServiceNodeMetrics', () => { expect(() => shallow( - + ) ).not.toThrowError(); diff --git a/x-pack/plugins/apm/public/components/app/metrics_details/service_node_metrics/index.tsx b/x-pack/plugins/apm/public/components/app/metrics_details/service_node_metrics/index.tsx index cc0703c03678..b78542f291f7 100644 --- a/x-pack/plugins/apm/public/components/app/metrics_details/service_node_metrics/index.tsx +++ b/x-pack/plugins/apm/public/components/app/metrics_details/service_node_metrics/index.tsx @@ -46,15 +46,16 @@ const Truncate = euiStyled.span` ${truncate(unit * 12)} `; -export function ServiceNodeMetrics() { +interface Props { + serviceNodeName: string; +} + +export function ServiceNodeMetrics({ serviceNodeName }: Props) { const { agentName, serviceName } = useApmServiceContext(); const apmRouter = useApmRouter(); - const { - path: { id: serviceNodeName }, - query, - } = useApmParams('/services/{serviceName}/metrics/{id}'); + const { query } = useApmParams('/services/{serviceName}/metrics/{id}'); const { environment, kuery, rangeFrom, rangeTo } = query; diff --git a/x-pack/plugins/apm/public/components/app/storage_explorer/get_storage_explorer_links.ts b/x-pack/plugins/apm/public/components/app/storage_explorer/get_storage_explorer_links.ts new file mode 100644 index 000000000000..1736cd852a41 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/storage_explorer/get_storage_explorer_links.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { CoreStart } from '@kbn/core-lifecycle-browser'; + +export function getIndexManagementHref(core: CoreStart) { + return core.application.getUrlForApp('management', { + path: '/data/index_management/data_streams', + }); +} + +export function getStorageExplorerFeedbackHref() { + return 'https://ela.st/feedback-storage-explorer'; +} + +export function getKibanaAdvancedSettingsHref(core: CoreStart) { + return core.application.getUrlForApp('management', { + path: '/kibana/settings?query=category:(observability)', + }); +} diff --git a/x-pack/plugins/apm/public/components/app/storage_explorer/index.tsx b/x-pack/plugins/apm/public/components/app/storage_explorer/index.tsx index 5b8c044d2276..ef0c198000dd 100644 --- a/x-pack/plugins/apm/public/components/app/storage_explorer/index.tsx +++ b/x-pack/plugins/apm/public/components/app/storage_explorer/index.tsx @@ -12,8 +12,14 @@ import { EuiSpacer, EuiEmptyPrompt, EuiLoadingSpinner, + EuiCallOut, + EuiLink, + EuiButton, + EuiPanel, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; import { IndexLifecyclePhaseSelect } from './index_lifecycle_phase_select'; import { ServicesTable } from './services_table'; import { SearchBar } from '../../shared/search_bar'; @@ -22,18 +28,51 @@ import { PermissionDenied } from './prompts/permission_denied'; import { useFetcher, FETCH_STATUS } from '../../../hooks/use_fetcher'; import { SummaryStats } from './summary_stats'; import { ApmEnvironmentFilter } from '../../shared/environment_filter'; +import { TipsAndResources } from './resources/tips_and_resources'; +import { useLocalStorage } from '../../../hooks/use_local_storage'; +import { getKibanaAdvancedSettingsHref } from './get_storage_explorer_links'; -const INITIAL_DATA = { hasPrivileges: false }; +type CalloutType = 'crossClusterSearch' | 'optimizePerformance'; + +const CALLOUT_DISMISS_INITIAL_STATE: Record = { + crossClusterSearch: false, + optimizePerformance: false, +}; + +const dismissButtonText = i18n.translate( + 'xpack.apm.storageExplorer.callout.dimissButton', + { + defaultMessage: 'Dismiss', + } +); export function StorageExplorer() { - const { data: { hasPrivileges } = INITIAL_DATA, status } = useFetcher( + const { core } = useApmPluginContext(); + + const [calloutDismissed, setCalloutDismissed] = useLocalStorage( + 'apm.storageExplorer.calloutDismissed', + CALLOUT_DISMISS_INITIAL_STATE + ); + + const { data: hasPrivilegesData, status: hasPrivilegesStatus } = useFetcher( (callApmApi) => { return callApmApi('GET /internal/apm/storage_explorer/privileges'); }, [] ); - const loading = status === FETCH_STATUS.LOADING; + const { data: isCrossClusterSearchData } = useFetcher( + (callApmApi) => { + if (!calloutDismissed.crossClusterSearch) { + return callApmApi( + 'GET /internal/apm/storage_explorer/is_cross_cluster_search' + ); + } + }, + [calloutDismissed] + ); + + const loading = hasPrivilegesStatus === FETCH_STATUS.LOADING; if (loading) { return ( @@ -51,7 +90,7 @@ export function StorageExplorer() { ); } - if (!hasPrivileges) { + if (!hasPrivilegesData?.hasPrivileges) { return ; } @@ -67,11 +106,94 @@ export function StorageExplorer() { + + {!calloutDismissed.optimizePerformance && ( + +

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

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

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

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

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

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

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

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

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

-
-
- - - -
+ +

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

+
), rightSideItems: [ - + {i18n.translate( 'xpack.apm.views.storageExplorer.giveFeedback', { diff --git a/x-pack/plugins/apm/public/components/shared/charts/spark_plot/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/spark_plot/index.tsx index ad3def190351..58ff1bbfdb6c 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/spark_plot/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/spark_plot/index.tsx @@ -50,7 +50,7 @@ export function SparkPlot({ valueLabel: React.ReactNode; compact?: boolean; comparisonSeries?: Coordinate[]; - comparisonSeriesColor: string; + comparisonSeriesColor?: string; }) { return ( { }); it('enables auto-refresh when refreshPaused is false', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); const { wrapper } = mountDatePicker({ rangeFrom: 'now-15m', rangeTo: 'now', @@ -139,7 +139,7 @@ describe('DatePicker', () => { }); it('disables auto-refresh when refreshPaused is true', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); mountDatePicker({ rangeFrom: 'now-15m', rangeTo: 'now', diff --git a/x-pack/plugins/apm/public/hooks/use_fetcher.integration.test.tsx b/x-pack/plugins/apm/public/hooks/use_fetcher.integration.test.tsx index 4b19ea5dddbd..27f29b7dff12 100644 --- a/x-pack/plugins/apm/public/hooks/use_fetcher.integration.test.tsx +++ b/x-pack/plugins/apm/public/hooks/use_fetcher.integration.test.tsx @@ -23,7 +23,7 @@ describe('when simulating race condition', () => { let renderSpy: jest.Mock; beforeEach(async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); renderSpy = jest.fn(); requestCallOrder = []; diff --git a/x-pack/plugins/apm/public/hooks/use_fetcher.test.tsx b/x-pack/plugins/apm/public/hooks/use_fetcher.test.tsx index ed4d1f079199..a25fcb724545 100644 --- a/x-pack/plugins/apm/public/hooks/use_fetcher.test.tsx +++ b/x-pack/plugins/apm/public/hooks/use_fetcher.test.tsx @@ -36,7 +36,7 @@ describe('useFetcher', () => { >; beforeEach(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); async function fn() { await delay(500); return 'response from hook'; @@ -86,7 +86,7 @@ describe('useFetcher', () => { >; beforeEach(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); async function fn(): Promise { await delay(500); throw new Error('Something went wrong'); @@ -129,7 +129,7 @@ describe('useFetcher', () => { describe('when a hook already has data', () => { it('should show "first response" while loading "second response"', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); const hook = renderHook( /* eslint-disable-next-line react-hooks/exhaustive-deps */ diff --git a/x-pack/plugins/apm/public/hooks/use_service_metric_charts_fetcher.ts b/x-pack/plugins/apm/public/hooks/use_service_metric_charts_fetcher.ts index 8c55178d7bbe..624c3c626fe9 100644 --- a/x-pack/plugins/apm/public/hooks/use_service_metric_charts_fetcher.ts +++ b/x-pack/plugins/apm/public/hooks/use_service_metric_charts_fetcher.ts @@ -32,7 +32,7 @@ export function useServiceMetricChartsFetcher({ } = useApmParams('/services/{serviceName}'); const { start, end } = useTimeRange({ rangeFrom, rangeTo }); - const { agentName, serviceName, runtimeName } = useApmServiceContext(); + const { agentName, serviceName } = useApmServiceContext(); const { data = INITIAL_DATA, @@ -53,23 +53,13 @@ export function useServiceMetricChartsFetcher({ start, end, agentName, - serviceRuntimeName: runtimeName, }, }, } ); } }, - [ - environment, - kuery, - serviceName, - start, - end, - agentName, - serviceNodeName, - runtimeName, - ] + [environment, kuery, serviceName, start, end, agentName, serviceNodeName] ); return { diff --git a/x-pack/plugins/apm/server/lib/connections/get_connection_stats/get_destination_map.ts b/x-pack/plugins/apm/server/lib/connections/get_connection_stats/get_destination_map.ts index a9c774517821..4c76699d2089 100644 --- a/x-pack/plugins/apm/server/lib/connections/get_connection_stats/get_destination_map.ts +++ b/x-pack/plugins/apm/server/lib/connections/get_connection_stats/get_destination_map.ts @@ -24,10 +24,10 @@ import { SPAN_SUBTYPE, SPAN_TYPE, } from '../../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../helpers/setup_request'; import { withApmSpan } from '../../../utils/with_apm_span'; import { Node, NodeType } from '../../../../common/connections'; import { excludeRumExitSpansQuery } from '../exclude_rum_exit_spans_query'; +import { APMEventClient } from '../../helpers/create_es_client/create_apm_event_client'; type Destination = { dependencyName: string; @@ -48,21 +48,19 @@ type Destination = { // - for each span, find the transaction it creates // - if there is a transaction, match the dependency name (span.destination.service.resource) to a service export const getDestinationMap = ({ - setup, + apmEventClient, start, end, filter, offset, }: { - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; filter: QueryDslQueryContainer[]; offset?: string; }) => { return withApmSpan('get_destination_map', async () => { - const { apmEventClient } = setup; - const { startWithOffset, endWithOffset } = getOffsetInMs({ start, end, diff --git a/x-pack/plugins/apm/server/lib/connections/get_connection_stats/get_stats.ts b/x-pack/plugins/apm/server/lib/connections/get_connection_stats/get_stats.ts index 7057847c34c0..409565db5729 100644 --- a/x-pack/plugins/apm/server/lib/connections/get_connection_stats/get_stats.ts +++ b/x-pack/plugins/apm/server/lib/connections/get_connection_stats/get_stats.ts @@ -27,27 +27,25 @@ import { } from '../../../../common/elasticsearch_fieldnames'; import { getBucketSize } from '../../helpers/get_bucket_size'; import { EventOutcome } from '../../../../common/event_outcome'; -import { Setup } from '../../helpers/setup_request'; import { NodeType } from '../../../../common/connections'; import { excludeRumExitSpansQuery } from '../exclude_rum_exit_spans_query'; +import { APMEventClient } from '../../helpers/create_es_client/create_apm_event_client'; export const getStats = async ({ - setup, + apmEventClient, start, end, filter, numBuckets, offset, }: { - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; filter: QueryDslQueryContainer[]; numBuckets: number; offset?: string; }) => { - const { apmEventClient } = setup; - const { offsetInMs, startWithOffset, endWithOffset } = getOffsetInMs({ start, end, diff --git a/x-pack/plugins/apm/server/lib/connections/get_connection_stats/index.ts b/x-pack/plugins/apm/server/lib/connections/get_connection_stats/index.ts index 7c9781e08db3..4ba113b698bc 100644 --- a/x-pack/plugins/apm/server/lib/connections/get_connection_stats/index.ts +++ b/x-pack/plugins/apm/server/lib/connections/get_connection_stats/index.ts @@ -9,14 +9,14 @@ import { ValuesType } from 'utility-types'; import { merge } from 'lodash'; import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { joinByKey } from '../../../../common/utils/join_by_key'; -import { Setup } from '../../helpers/setup_request'; import { getStats } from './get_stats'; import { getDestinationMap } from './get_destination_map'; import { calculateThroughputWithRange } from '../../helpers/calculate_throughput'; import { withApmSpan } from '../../../utils/with_apm_span'; +import { APMEventClient } from '../../helpers/create_es_client/create_apm_event_client'; export function getConnectionStats({ - setup, + apmEventClient, start, end, numBuckets, @@ -24,7 +24,7 @@ export function getConnectionStats({ collapseBy, offset, }: { - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; numBuckets: number; @@ -35,7 +35,7 @@ export function getConnectionStats({ return withApmSpan('get_connection_stats_and_map', async () => { const [allMetrics, destinationMap] = await Promise.all([ getStats({ - setup, + apmEventClient, start, end, filter, @@ -43,7 +43,7 @@ export function getConnectionStats({ offset, }), getDestinationMap({ - setup, + apmEventClient, start, end, filter, diff --git a/x-pack/plugins/apm/server/lib/helpers/get_apm_event_client.ts b/x-pack/plugins/apm/server/lib/helpers/get_apm_event_client.ts new file mode 100644 index 000000000000..6ee9bb94a62b --- /dev/null +++ b/x-pack/plugins/apm/server/lib/helpers/get_apm_event_client.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { UI_SETTINGS } from '@kbn/data-plugin/common'; +import { APMRouteHandlerResources } from '../../routes/typings'; +import { getApmIndices } from '../../routes/settings/apm_indices/get_apm_indices'; +import { APMEventClient } from './create_es_client/create_apm_event_client'; +import { withApmSpan } from '../../utils/with_apm_span'; + +export async function getApmEventClient({ + context, + params, + config, + request, +}: APMRouteHandlerResources): Promise { + return withApmSpan('get_apm_event_client', async () => { + const coreContext = await context.core; + const [indices, includeFrozen] = await Promise.all([ + getApmIndices({ + savedObjectsClient: coreContext.savedObjects.client, + config, + }), + withApmSpan('get_ui_settings', () => + coreContext.uiSettings.client.get( + UI_SETTINGS.SEARCH_INCLUDE_FROZEN + ) + ), + ]); + + return new APMEventClient({ + esClient: coreContext.elasticsearch.client.asCurrentUser, + debug: params.query._inspect, + request, + indices, + options: { includeFrozen }, + }); + }); +} diff --git a/x-pack/plugins/apm/server/lib/helpers/setup_request.test.ts b/x-pack/plugins/apm/server/lib/helpers/setup_request.test.ts index faaeb5db9140..ef8dcbbe63ee 100644 --- a/x-pack/plugins/apm/server/lib/helpers/setup_request.test.ts +++ b/x-pack/plugins/apm/server/lib/helpers/setup_request.test.ts @@ -9,7 +9,6 @@ import { elasticsearchServiceMock } from '@kbn/core/server/mocks'; import { setupRequest } from './setup_request'; import { APMConfig } from '../..'; import { APMRouteHandlerResources } from '../../routes/typings'; -import { ProcessorEvent } from '@kbn/observability-plugin/common'; import { getApmIndices } from '../../routes/settings/apm_indices/get_apm_indices'; jest.mock('../../routes/settings/apm_indices/get_apm_indices', () => ({ @@ -97,38 +96,6 @@ function getMockResources() { describe('setupRequest', () => { describe('with default args', () => { - it('calls callWithRequest', async () => { - const mockResources = getMockResources(); - const { apmEventClient } = await setupRequest(mockResources); - await apmEventClient.search('foo', { - apm: { events: [ProcessorEvent.transaction] }, - body: { track_total_hits: 10_000, size: 10 }, - }); - - expect( - mockResources.context.core.elasticsearch.client.asCurrentUser.search - ).toHaveBeenCalledWith( - { - index: ['apm-*'], - body: { - track_total_hits: 10000, - size: 10, - query: { - bool: { - filter: [{ terms: { 'processor.event': ['transaction'] } }], - }, - }, - }, - ignore_unavailable: true, - preference: 'any', - }, - { - signal: expect.any(Object), - meta: true, - } - ); - }); - it('calls callWithInternalUser', async () => { const mockResources = getMockResources(); const { internalClient } = await setupRequest(mockResources); @@ -153,49 +120,3 @@ describe('setupRequest', () => { }); }); }); - -describe('with includeFrozen=false', () => { - it('should NOT send "ignore_throttled:true" in the request', async () => { - const mockResources = getMockResources(); - - // mock includeFrozen to return false - mockResources.context.core.uiSettings.client.get.mockResolvedValue(false); - - const { apmEventClient } = await setupRequest(mockResources); - - await apmEventClient.search('foo', { - apm: { - events: [], - }, - body: { track_total_hits: 10_000, size: 10 }, - }); - - const params = - mockResources.context.core.elasticsearch.client.asCurrentUser.search.mock - .calls[0][0]; - // @ts-expect-error missing body definition - expect(params.ignore_throttled).toBe(undefined); - }); -}); - -describe('with includeFrozen=true', () => { - it('sets `ignore_throttled=false`', async () => { - const mockResources = getMockResources(); - - // mock includeFrozen to return true - mockResources.context.core.uiSettings.client.get.mockResolvedValue(true); - - const { apmEventClient } = await setupRequest(mockResources); - - await apmEventClient.search('foo', { - apm: { events: [] }, - body: { track_total_hits: 10_000, size: 10 }, - }); - - const params = - mockResources.context.core.elasticsearch.client.asCurrentUser.search.mock - .calls[0][0]; - // @ts-expect-error missing body definition - expect(params.ignore_throttled).toBe(false); - }); -}); diff --git a/x-pack/plugins/apm/server/lib/helpers/setup_request.ts b/x-pack/plugins/apm/server/lib/helpers/setup_request.ts index dc6e37936740..457c002c67b9 100644 --- a/x-pack/plugins/apm/server/lib/helpers/setup_request.ts +++ b/x-pack/plugins/apm/server/lib/helpers/setup_request.ts @@ -14,7 +14,6 @@ import { ApmIndicesConfig, getApmIndices, } from '../../routes/settings/apm_indices/get_apm_indices'; -import { APMEventClient } from './create_es_client/create_apm_event_client'; import { APMInternalClient, createInternalESClient, @@ -25,7 +24,6 @@ import { withApmSpan } from '../../utils/with_apm_span'; // https://github.com/microsoft/TypeScript/issues/34933 export interface Setup { - apmEventClient: APMEventClient; internalClient: APMInternalClient; ml?: ReturnType; config: APMConfig; @@ -45,7 +43,7 @@ export async function setupRequest({ const coreContext = await context.core; const licensingContext = await context.licensing; - const [indices, includeFrozen] = await Promise.all([ + const [indices] = await Promise.all([ getApmIndices({ savedObjectsClient: coreContext.savedObjects.client, config, @@ -59,13 +57,6 @@ export async function setupRequest({ return { indices, - apmEventClient: new APMEventClient({ - esClient: coreContext.elasticsearch.client.asCurrentUser, - debug: query._inspect, - request, - indices, - options: { includeFrozen }, - }), internalClient: await createInternalESClient({ context, request, diff --git a/x-pack/plugins/apm/server/lib/helpers/spans/get_is_using_service_destination_metrics.ts b/x-pack/plugins/apm/server/lib/helpers/spans/get_is_using_service_destination_metrics.ts index 0dbf3006a324..6b58db02bbbb 100644 --- a/x-pack/plugins/apm/server/lib/helpers/spans/get_is_using_service_destination_metrics.ts +++ b/x-pack/plugins/apm/server/lib/helpers/spans/get_is_using_service_destination_metrics.ts @@ -19,7 +19,7 @@ import { SPAN_DURATION, SPAN_NAME, } from '../../../../common/elasticsearch_fieldnames'; -import { Setup } from '../setup_request'; +import { APMEventClient } from '../create_es_client/create_apm_event_client'; export function getProcessorEventForServiceDestinationStatistics( searchServiceDestinationMetrics: boolean @@ -54,20 +54,18 @@ export function getDocCountFieldForServiceDestinationStatistics( } export async function getIsUsingServiceDestinationMetrics({ - setup, + apmEventClient, useSpanName, kuery, start, end, }: { - setup: Setup; + apmEventClient: APMEventClient; useSpanName: boolean; kuery: string; start: number; end: number; }) { - const { apmEventClient } = setup; - async function getServiceDestinationMetricsCount( query?: QueryDslQueryContainer ) { diff --git a/x-pack/plugins/apm/server/lib/helpers/transactions/get_is_using_transaction_events.test.ts b/x-pack/plugins/apm/server/lib/helpers/transactions/get_is_using_transaction_events.test.ts index 1fac873ced7b..97b4fcbf9584 100644 --- a/x-pack/plugins/apm/server/lib/helpers/transactions/get_is_using_transaction_events.test.ts +++ b/x-pack/plugins/apm/server/lib/helpers/transactions/get_is_using_transaction_events.test.ts @@ -63,7 +63,12 @@ describe('getIsUsingTransactionEvents', () => { it('should be false', async () => { mock = await inspectSearchParams( - (setup) => getIsUsingTransactionEvents({ setup, kuery: '' }), + (setup, apmEventClient) => + getIsUsingTransactionEvents({ + config: setup.config, + apmEventClient, + kuery: '', + }), { config } ); expect(mock.response).toBe(false); @@ -71,7 +76,12 @@ describe('getIsUsingTransactionEvents', () => { it('should not query for data', async () => { mock = await inspectSearchParams( - (setup) => getIsUsingTransactionEvents({ setup, kuery: '' }), + (setup, apmEventClient) => + getIsUsingTransactionEvents({ + config: setup.config, + apmEventClient, + kuery: '', + }), { config } ); expect(mock.spy).toHaveBeenCalledTimes(0); @@ -84,7 +94,12 @@ describe('getIsUsingTransactionEvents', () => { }; it('should be false when kuery is empty', async () => { mock = await inspectSearchParams( - (setup) => getIsUsingTransactionEvents({ setup, kuery: '' }), + (setup, apmEventClient) => + getIsUsingTransactionEvents({ + config: setup.config, + apmEventClient, + kuery: '', + }), { config } ); expect(mock.response).toBe(false); @@ -92,9 +107,10 @@ describe('getIsUsingTransactionEvents', () => { it('should be false when kuery is set and metrics data found', async () => { mock = await inspectSearchParams( - (setup) => + (setup, apmEventClient) => getIsUsingTransactionEvents({ - setup, + config: setup.config, + apmEventClient, kuery: 'proccessor.event: "transaction"', }), { @@ -116,9 +132,10 @@ describe('getIsUsingTransactionEvents', () => { it('should be true when kuery is set and metrics data are not found', async () => { mock = await inspectSearchParams( - (setup) => + (setup, apmEventClient) => getIsUsingTransactionEvents({ - setup, + config: setup.config, + apmEventClient, kuery: 'proccessor.event: "transaction"', }), { @@ -140,7 +157,12 @@ describe('getIsUsingTransactionEvents', () => { it('should not query for data when kuery is empty', async () => { mock = await inspectSearchParams( - (setup) => getIsUsingTransactionEvents({ setup, kuery: '' }), + (setup, apmEventClient) => + getIsUsingTransactionEvents({ + config: setup.config, + apmEventClient, + kuery: '', + }), { config } ); expect(mock.spy).toHaveBeenCalledTimes(0); @@ -148,9 +170,10 @@ describe('getIsUsingTransactionEvents', () => { it('should query for data when kuery is set', async () => { mock = await inspectSearchParams( - (setup) => + (setup, apmEventClient) => getIsUsingTransactionEvents({ - setup, + config: setup.config, + apmEventClient, kuery: 'proccessor.event: "transaction"', }), { config } @@ -167,7 +190,12 @@ describe('getIsUsingTransactionEvents', () => { it('should query for data once if metrics data found', async () => { mock = await inspectSearchParams( - (setup) => getIsUsingTransactionEvents({ setup, kuery: '' }), + (setup, apmEventClient) => + getIsUsingTransactionEvents({ + config: setup.config, + apmEventClient, + kuery: '', + }), { config, mockResponse: (request) => { @@ -187,7 +215,12 @@ describe('getIsUsingTransactionEvents', () => { it('should query for data twice if metrics data not found', async () => { mock = await inspectSearchParams( - (setup) => getIsUsingTransactionEvents({ setup, kuery: '' }), + (setup, apmEventClient) => + getIsUsingTransactionEvents({ + config: setup.config, + apmEventClient, + kuery: '', + }), { config, mockResponse: (request) => { @@ -207,7 +240,12 @@ describe('getIsUsingTransactionEvents', () => { it('should be false if metrics data are found', async () => { mock = await inspectSearchParams( - (setup) => getIsUsingTransactionEvents({ setup, kuery: '' }), + (setup, apmEventClient) => + getIsUsingTransactionEvents({ + config: setup.config, + apmEventClient, + kuery: '', + }), { config, mockResponse: (request) => { @@ -226,7 +264,12 @@ describe('getIsUsingTransactionEvents', () => { it('should be true if no metrics data are found', async () => { mock = await inspectSearchParams( - (setup) => getIsUsingTransactionEvents({ setup, kuery: '' }), + (setup, apmEventClient) => + getIsUsingTransactionEvents({ + config: setup.config, + apmEventClient, + kuery: '', + }), { config, mockResponse: (request) => { @@ -245,7 +288,12 @@ describe('getIsUsingTransactionEvents', () => { it('should be false if no metrics or transactions data are found', async () => { mock = await inspectSearchParams( - (setup) => getIsUsingTransactionEvents({ setup, kuery: '' }), + (setup, apmEventClient) => + getIsUsingTransactionEvents({ + config: setup.config, + apmEventClient, + kuery: '', + }), { config, mockResponse: () => mockResponseNoHits } ); expect(mock.response).toBe(false); diff --git a/x-pack/plugins/apm/server/lib/helpers/transactions/get_is_using_transaction_events.ts b/x-pack/plugins/apm/server/lib/helpers/transactions/get_is_using_transaction_events.ts index 409010d3c695..20832c70d007 100644 --- a/x-pack/plugins/apm/server/lib/helpers/transactions/get_is_using_transaction_events.ts +++ b/x-pack/plugins/apm/server/lib/helpers/transactions/get_is_using_transaction_events.ts @@ -8,17 +8,19 @@ import { kqlQuery, rangeQuery } from '@kbn/observability-plugin/server'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; import { getSearchTransactionsEvents } from '.'; -import { Setup } from '../setup_request'; import { APMEventClient } from '../create_es_client/create_apm_event_client'; import { SearchAggregatedTransactionSetting } from '../../../../common/aggregated_transactions'; +import { APMConfig } from '../../..'; export async function getIsUsingTransactionEvents({ - setup: { config, apmEventClient }, + config, + apmEventClient, kuery, start, end, }: { - setup: Setup; + config: APMConfig; + apmEventClient: APMEventClient; kuery: string; start?: number; end?: number; diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/get_coldstart_rate.ts b/x-pack/plugins/apm/server/lib/transaction_groups/get_coldstart_rate.ts index c16b600cda14..74419d47507a 100644 --- a/x-pack/plugins/apm/server/lib/transaction_groups/get_coldstart_rate.ts +++ b/x-pack/plugins/apm/server/lib/transaction_groups/get_coldstart_rate.ts @@ -21,13 +21,13 @@ import { getProcessorEventForTransactions, } from '../helpers/transactions'; import { getBucketSizeForAggregatedTransactions } from '../helpers/get_bucket_size_for_aggregated_transactions'; -import { Setup } from '../helpers/setup_request'; import { calculateTransactionColdstartRate, getColdstartAggregation, getTransactionColdstartRateTimeSeries, } from '../helpers/transaction_coldstart_rate'; import { getOffsetInMs } from '../../../common/utils/get_offset_in_ms'; +import { APMEventClient } from '../helpers/create_es_client/create_apm_event_client'; export async function getColdstartRate({ environment, @@ -35,7 +35,7 @@ export async function getColdstartRate({ serviceName, transactionType, transactionName, - setup, + apmEventClient, searchAggregatedTransactions, start, end, @@ -46,7 +46,7 @@ export async function getColdstartRate({ serviceName: string; transactionType?: string; transactionName: string; - setup: Setup; + apmEventClient: APMEventClient; searchAggregatedTransactions: boolean; start: number; end: number; @@ -55,8 +55,6 @@ export async function getColdstartRate({ transactionColdstartRate: Coordinate[]; average: number | null; }> { - const { apmEventClient } = setup; - const { startWithOffset, endWithOffset } = getOffsetInMs({ start, end, @@ -131,7 +129,7 @@ export async function getColdstartRatePeriods({ serviceName, transactionType, transactionName = '', - setup, + apmEventClient, searchAggregatedTransactions, start, end, @@ -142,7 +140,7 @@ export async function getColdstartRatePeriods({ serviceName: string; transactionType?: string; transactionName?: string; - setup: Setup; + apmEventClient: APMEventClient; searchAggregatedTransactions: boolean; start: number; end: number; @@ -154,7 +152,7 @@ export async function getColdstartRatePeriods({ serviceName, transactionType, transactionName, - setup, + apmEventClient, searchAggregatedTransactions, }; diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/get_failed_transaction_rate.ts b/x-pack/plugins/apm/server/lib/transaction_groups/get_failed_transaction_rate.ts index 6cebbce9d974..8fb57d037a3c 100644 --- a/x-pack/plugins/apm/server/lib/transaction_groups/get_failed_transaction_rate.ts +++ b/x-pack/plugins/apm/server/lib/transaction_groups/get_failed_transaction_rate.ts @@ -24,13 +24,13 @@ import { getProcessorEventForTransactions, } from '../helpers/transactions'; import { getBucketSizeForAggregatedTransactions } from '../helpers/get_bucket_size_for_aggregated_transactions'; -import { Setup } from '../helpers/setup_request'; import { calculateFailedTransactionRate, getOutcomeAggregation, getFailedTransactionRateTimeSeries, } from '../helpers/transaction_error_rate'; import { getOffsetInMs } from '../../../common/utils/get_offset_in_ms'; +import { APMEventClient } from '../helpers/create_es_client/create_apm_event_client'; export async function getFailedTransactionRate({ environment, @@ -38,7 +38,7 @@ export async function getFailedTransactionRate({ serviceName, transactionTypes, transactionName, - setup, + apmEventClient, searchAggregatedTransactions, start, end, @@ -50,7 +50,7 @@ export async function getFailedTransactionRate({ serviceName: string; transactionTypes: string[]; transactionName?: string; - setup: Setup; + apmEventClient: APMEventClient; searchAggregatedTransactions: boolean; start: number; end: number; @@ -60,8 +60,6 @@ export async function getFailedTransactionRate({ timeseries: Coordinate[]; average: number | null; }> { - const { apmEventClient } = setup; - const { startWithOffset, endWithOffset } = getOffsetInMs({ start, end, diff --git a/x-pack/plugins/apm/server/routes/alerts/alerting_es_client.ts b/x-pack/plugins/apm/server/routes/alerts/alerting_es_client.ts index f0dc94daadf0..fabe71a874be 100644 --- a/x-pack/plugins/apm/server/routes/alerts/alerting_es_client.ts +++ b/x-pack/plugins/apm/server/routes/alerts/alerting_es_client.ts @@ -8,7 +8,13 @@ import type { ESSearchRequest, ESSearchResponse } from '@kbn/es-types'; import { RuleExecutorServices } from '@kbn/alerting-plugin/server'; -export async function alertingEsClient({ +export type APMEventESSearchRequestParams = ESSearchRequest & { + body: { size: number; track_total_hits: boolean | number }; +}; + +export async function alertingEsClient< + TParams extends APMEventESSearchRequestParams +>({ scopedClusterClient, params, }: { diff --git a/x-pack/plugins/apm/server/routes/alerts/route.ts b/x-pack/plugins/apm/server/routes/alerts/route.ts index 56e23d271286..389f6fc87c24 100644 --- a/x-pack/plugins/apm/server/routes/alerts/route.ts +++ b/x-pack/plugins/apm/server/routes/alerts/route.ts @@ -13,6 +13,7 @@ import { setupRequest } from '../../lib/helpers/setup_request'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { environmentRt, rangeRt } from '../default_api_types'; import { AggregationType } from '../../../common/rules/apm_rule_types'; +import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; const alertParamsRt = t.intersection([ t.partial({ @@ -40,12 +41,16 @@ const transactionErrorRateChartPreview = createApmServerRoute({ handler: async ( resources ): Promise<{ errorRateChartPreview: Array<{ x: number; y: number }> }> => { - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { params } = resources; const { _inspect, ...alertParams } = params.query; const errorRateChartPreview = await getTransactionErrorRateChartPreview({ - setup, + config: setup.config, + apmEventClient, alertParams, }); @@ -60,13 +65,13 @@ const transactionErrorCountChartPreview = createApmServerRoute({ handler: async ( resources ): Promise<{ errorCountChartPreview: Array<{ x: number; y: number }> }> => { - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { params } = resources; const { _inspect, ...alertParams } = params.query; const errorCountChartPreview = await getTransactionErrorCountChartPreview({ - setup, + apmEventClient, alertParams, }); @@ -86,7 +91,10 @@ const transactionDurationChartPreview = createApmServerRoute({ data: Array<{ x: number; y: number | null }>; }>; }> => { - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { params } = resources; @@ -94,7 +102,8 @@ const transactionDurationChartPreview = createApmServerRoute({ const latencyChartPreview = await getTransactionDurationChartPreview({ alertParams, - setup, + config: setup.config, + apmEventClient, }); return { latencyChartPreview }; diff --git a/x-pack/plugins/apm/server/routes/alerts/rule_types/anomaly/register_anomaly_rule_type.ts b/x-pack/plugins/apm/server/routes/alerts/rule_types/anomaly/register_anomaly_rule_type.ts index 13d0f3311f18..d82a4997ffe0 100644 --- a/x-pack/plugins/apm/server/routes/alerts/rule_types/anomaly/register_anomaly_rule_type.ts +++ b/x-pack/plugins/apm/server/routes/alerts/rule_types/anomaly/register_anomaly_rule_type.ts @@ -145,6 +145,7 @@ export function registerAnomalyRuleType({ const jobIds = mlJobs.map((job) => job.jobId); const anomalySearchParams = { body: { + track_total_hits: false, size: 0, query: { bool: { diff --git a/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/get_error_count_chart_preview.ts b/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/get_error_count_chart_preview.ts index 3489ae4d91be..fa819e268c80 100644 --- a/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/get_error_count_chart_preview.ts +++ b/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/get_error_count_chart_preview.ts @@ -10,16 +10,15 @@ import { ProcessorEvent } from '@kbn/observability-plugin/common'; import { SERVICE_NAME } from '../../../../../common/elasticsearch_fieldnames'; import { AlertParams } from '../../route'; import { environmentQuery } from '../../../../../common/utils/environment_query'; -import { Setup } from '../../../../lib/helpers/setup_request'; +import { APMEventClient } from '../../../../lib/helpers/create_es_client/create_apm_event_client'; export async function getTransactionErrorCountChartPreview({ - setup, + apmEventClient, alertParams, }: { - setup: Setup; + apmEventClient: APMEventClient; alertParams: AlertParams; }) { - const { apmEventClient } = setup; const { serviceName, environment, interval, start, end } = alertParams; const query = { diff --git a/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/register_error_count_rule_type.ts b/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/register_error_count_rule_type.ts index 93c88b84d1d3..58e475ced07f 100644 --- a/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/register_error_count_rule_type.ts +++ b/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/register_error_count_rule_type.ts @@ -83,18 +83,19 @@ export function registerErrorCountRuleType({ producer: APM_SERVER_FEATURE_ID, minimumLicenseRequired: 'basic', isExportable: true, - executor: async ({ services, params }) => { + executor: async ({ services, params: ruleParams }) => { const config = await firstValueFrom(config$); - const ruleParams = params; const indices = await getApmIndices({ config, savedObjectsClient: services.savedObjectsClient, }); + const searchParams = { index: indices.error, - size: 0, body: { + track_total_hits: false, + size: 0, query: { bool: { filter: [ diff --git a/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_duration/get_transaction_duration_chart_preview.ts b/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_duration/get_transaction_duration_chart_preview.ts index ad01f8a4c3c2..292748f3af16 100644 --- a/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_duration/get_transaction_duration_chart_preview.ts +++ b/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_duration/get_transaction_duration_chart_preview.ts @@ -21,21 +21,23 @@ import { getDurationFieldForTransactions, getProcessorEventForTransactions, } from '../../../../lib/helpers/transactions'; -import { Setup } from '../../../../lib/helpers/setup_request'; import { ENVIRONMENT_NOT_DEFINED, getEnvironmentLabel, } from '../../../../../common/environment_filter_values'; import { averageOrPercentileAgg } from '../../average_or_percentile_agg'; +import { APMConfig } from '../../../..'; +import { APMEventClient } from '../../../../lib/helpers/create_es_client/create_apm_event_client'; export async function getTransactionDurationChartPreview({ alertParams, - setup, + config, + apmEventClient, }: { alertParams: AlertParams; - setup: Setup; + config: APMConfig; + apmEventClient: APMEventClient; }) { - const { apmEventClient } = setup; const { aggregationType = AggregationType.Avg, environment, @@ -46,7 +48,8 @@ export async function getTransactionDurationChartPreview({ end, } = alertParams; const searchAggregatedTransactions = await getSearchTransactionsEvents({ - ...setup, + config, + apmEventClient, kuery: '', }); diff --git a/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_duration/register_transaction_duration_rule_type.ts b/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_duration/register_transaction_duration_rule_type.ts index 7a7e84414aec..0ea099c8d4bc 100644 --- a/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_duration/register_transaction_duration_rule_type.ts +++ b/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_duration/register_transaction_duration_rule_type.ts @@ -125,6 +125,7 @@ export function registerTransactionDurationRuleType({ const searchParams = { index, body: { + track_total_hits: false, size: 0, query: { bool: { diff --git a/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_error_rate/get_transaction_error_rate_chart_preview.ts b/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_error_rate/get_transaction_error_rate_chart_preview.ts index 9921b0ce16a3..d799e025c345 100644 --- a/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_error_rate/get_transaction_error_rate_chart_preview.ts +++ b/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_error_rate/get_transaction_error_rate_chart_preview.ts @@ -17,25 +17,28 @@ import { getDocumentTypeFilterForTransactions, getProcessorEventForTransactions, } from '../../../../lib/helpers/transactions'; -import { Setup } from '../../../../lib/helpers/setup_request'; import { calculateFailedTransactionRate, getOutcomeAggregation, } from '../../../../lib/helpers/transaction_error_rate'; +import { APMConfig } from '../../../..'; +import { APMEventClient } from '../../../../lib/helpers/create_es_client/create_apm_event_client'; export async function getTransactionErrorRateChartPreview({ - setup, + config, + apmEventClient, alertParams, }: { - setup: Setup; + config: APMConfig; + apmEventClient: APMEventClient; alertParams: AlertParams; }) { - const { apmEventClient } = setup; const { serviceName, environment, transactionType, interval, start, end } = alertParams; const searchAggregatedTransactions = await getSearchTransactionsEvents({ - ...setup, + config, + apmEventClient, kuery: '', start, end, diff --git a/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_error_rate/register_transaction_error_rate_rule_type.ts b/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_error_rate/register_transaction_error_rate_rule_type.ts index 5e4e04d293cd..15a5880345ff 100644 --- a/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_error_rate/register_transaction_error_rate_rule_type.ts +++ b/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_error_rate/register_transaction_error_rate_rule_type.ts @@ -112,8 +112,9 @@ export function registerTransactionErrorRateRuleType({ const searchParams = { index, - size: 0, body: { + track_total_hits: false, + size: 0, query: { bool: { filter: [ diff --git a/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_correlation.ts b/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_correlation.ts index 74ef7bc56fdc..48d468c51797 100644 --- a/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_correlation.ts +++ b/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_correlation.ts @@ -12,12 +12,11 @@ import { TRANSACTION_DURATION, } from '../../../../common/elasticsearch_fieldnames'; import type { CommonCorrelationsQueryParams } from '../../../../common/correlations/types'; - -import { Setup } from '../../../lib/helpers/setup_request'; import { getCommonCorrelationsQuery } from './get_common_correlations_query'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; export const fetchDurationCorrelation = async ({ - setup, + apmEventClient, eventType, start, end, @@ -29,7 +28,7 @@ export const fetchDurationCorrelation = async ({ fractions, totalDocCount, }: CommonCorrelationsQueryParams & { - setup: Setup; + apmEventClient: APMEventClient; eventType: ProcessorEvent; expectations: number[]; ranges: estypes.AggregationsAggregationRange[]; @@ -40,8 +39,6 @@ export const fetchDurationCorrelation = async ({ correlation: number | null; ksTest: number | null; }> => { - const { apmEventClient } = setup; - const resp = await apmEventClient.search('get_duration_correlation', { apm: { events: [eventType], diff --git a/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_correlation_with_histogram.ts b/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_correlation_with_histogram.ts index 4fd82d431af6..a4b2b0c6284d 100644 --- a/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_correlation_with_histogram.ts +++ b/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_correlation_with_histogram.ts @@ -19,13 +19,13 @@ import { } from '../../../../common/correlations/constants'; import { LatencyDistributionChartType } from '../../../../common/latency_distribution_chart_types'; -import { Setup } from '../../../lib/helpers/setup_request'; import { fetchDurationCorrelation } from './fetch_duration_correlation'; import { fetchDurationRanges } from './fetch_duration_ranges'; import { getEventType } from '../utils'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; export async function fetchDurationCorrelationWithHistogram({ - setup, + apmEventClient, chartType, start, end, @@ -39,7 +39,7 @@ export async function fetchDurationCorrelationWithHistogram({ totalDocCount, fieldValuePair, }: CommonCorrelationsQueryParams & { - setup: Setup; + apmEventClient: APMEventClient; chartType: LatencyDistributionChartType; expectations: number[]; ranges: estypes.AggregationsAggregationRange[]; @@ -60,7 +60,7 @@ export async function fetchDurationCorrelationWithHistogram({ }; const { correlation, ksTest } = await fetchDurationCorrelation({ - setup, + apmEventClient, eventType, start, end, @@ -76,7 +76,7 @@ export async function fetchDurationCorrelationWithHistogram({ if (correlation !== null && ksTest !== null && !isNaN(ksTest)) { if (correlation > CORRELATION_THRESHOLD && ksTest < KS_TEST_THRESHOLD) { const { durationRanges: histogram } = await fetchDurationRanges({ - setup, + apmEventClient, chartType, start, end, diff --git a/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_field_candidates.ts b/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_field_candidates.ts index 29863b52c42b..c15f40d78d2c 100644 --- a/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_field_candidates.ts +++ b/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_field_candidates.ts @@ -16,9 +16,8 @@ import { POPULATED_DOC_COUNT_SAMPLE_SIZE, } from '../../../../common/correlations/constants'; import { hasPrefixToInclude } from '../../../../common/correlations/utils'; - -import { Setup } from '../../../lib/helpers/setup_request'; import { getCommonCorrelationsQuery } from './get_common_correlations_query'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; const SUPPORTED_ES_FIELD_TYPES = [ ES_FIELD_TYPES.KEYWORD, @@ -36,7 +35,7 @@ export const shouldBeExcluded = (fieldName: string) => { }; export const fetchDurationFieldCandidates = async ({ - setup, + apmEventClient, eventType, query, start, @@ -45,13 +44,11 @@ export const fetchDurationFieldCandidates = async ({ kuery, }: CommonCorrelationsQueryParams & { query: estypes.QueryDslQueryContainer; - setup: Setup; + apmEventClient: APMEventClient; eventType: ProcessorEvent.transaction | ProcessorEvent.span; }): Promise<{ fieldCandidates: string[]; }> => { - const { apmEventClient } = setup; - // Get all supported fields const [respMapping, respRandomDoc] = await Promise.all([ apmEventClient.fieldCaps('get_field_caps', { diff --git a/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_fractions.ts b/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_fractions.ts index c77399d215d5..222cce131372 100644 --- a/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_fractions.ts +++ b/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_fractions.ts @@ -13,14 +13,14 @@ import { SPAN_DURATION, TRANSACTION_DURATION, } from '../../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../../lib/helpers/setup_request'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; import { getCommonCorrelationsQuery } from './get_common_correlations_query'; /** * Compute the actual percentile bucket counts and actual fractions */ export const fetchDurationFractions = async ({ - setup, + apmEventClient, eventType, start, end, @@ -29,11 +29,10 @@ export const fetchDurationFractions = async ({ query, ranges, }: CommonCorrelationsQueryParams & { - setup: Setup; + apmEventClient: APMEventClient; eventType: ProcessorEvent; ranges: estypes.AggregationsAggregationRange[]; }): Promise<{ fractions: number[]; totalDocCount: number }> => { - const { apmEventClient } = setup; const resp = await apmEventClient.search('get_duration_fractions', { apm: { events: [eventType], diff --git a/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_histogram_range_steps.ts b/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_histogram_range_steps.ts index 4e7c852ca992..7e86fc70bad7 100644 --- a/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_histogram_range_steps.ts +++ b/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_histogram_range_steps.ts @@ -10,9 +10,9 @@ import { scaleLog } from 'd3-scale'; import { isFiniteNumber } from '@kbn/observability-plugin/common/utils/is_finite_number'; import { CommonCorrelationsQueryParams } from '../../../../common/correlations/types'; import { LatencyDistributionChartType } from '../../../../common/latency_distribution_chart_types'; -import { Setup } from '../../../lib/helpers/setup_request'; import { getCommonCorrelationsQuery } from './get_common_correlations_query'; import { getDurationField, getEventType } from '../utils'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; const getHistogramRangeSteps = (min: number, max: number, steps: number) => { // A d3 based scale function as a helper to get equally distributed bins on a log scale. @@ -25,7 +25,7 @@ const getHistogramRangeSteps = (min: number, max: number, steps: number) => { export const fetchDurationHistogramRangeSteps = async ({ chartType, - setup, + apmEventClient, start, end, environment, @@ -36,7 +36,7 @@ export const fetchDurationHistogramRangeSteps = async ({ durationMaxOverride, }: CommonCorrelationsQueryParams & { chartType: LatencyDistributionChartType; - setup: Setup; + apmEventClient: APMEventClient; searchMetrics: boolean; durationMinOverride?: number; durationMaxOverride?: number; @@ -59,8 +59,6 @@ export const fetchDurationHistogramRangeSteps = async ({ }; } - const { apmEventClient } = setup; - const durationField = getDurationField(chartType, searchMetrics); // when using metrics data, ensure we filter by docs with the appropriate duration field diff --git a/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_percentiles.ts b/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_percentiles.ts index 618ee3a32193..d303777b08c7 100644 --- a/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_percentiles.ts +++ b/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_percentiles.ts @@ -6,15 +6,15 @@ */ import { SIGNIFICANT_VALUE_DIGITS } from '../../../../common/correlations/constants'; -import { Setup } from '../../../lib/helpers/setup_request'; import { LatencyDistributionChartType } from '../../../../common/latency_distribution_chart_types'; import { getCommonCorrelationsQuery } from './get_common_correlations_query'; import { CommonCorrelationsQueryParams } from '../../../../common/correlations/types'; import { getDurationField, getEventType } from '../utils'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; export const fetchDurationPercentiles = async ({ chartType, - setup, + apmEventClient, start, end, environment, @@ -24,7 +24,7 @@ export const fetchDurationPercentiles = async ({ searchMetrics, }: CommonCorrelationsQueryParams & { chartType: LatencyDistributionChartType; - setup: Setup; + apmEventClient: APMEventClient; percents?: number[]; searchMetrics: boolean; }): Promise<{ @@ -63,7 +63,7 @@ export const fetchDurationPercentiles = async ({ }, }, }; - const response = await setup.apmEventClient.search( + const response = await apmEventClient.search( 'get_duration_percentiles', params ); diff --git a/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_ranges.ts b/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_ranges.ts index 1aca4f7be852..c8f2aae2e937 100644 --- a/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_ranges.ts +++ b/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_ranges.ts @@ -8,14 +8,14 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { sumBy } from 'lodash'; import { LatencyDistributionChartType } from '../../../../common/latency_distribution_chart_types'; -import { Setup } from '../../../lib/helpers/setup_request'; import { getCommonCorrelationsQuery } from './get_common_correlations_query'; import { Environment } from '../../../../common/environment_rt'; import { getDurationField, getEventType } from '../utils'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; export const fetchDurationRanges = async ({ rangeSteps, - setup, + apmEventClient, start, end, environment, @@ -25,7 +25,7 @@ export const fetchDurationRanges = async ({ searchMetrics, }: { rangeSteps: number[]; - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; environment: Environment; @@ -37,7 +37,6 @@ export const fetchDurationRanges = async ({ totalDocCount: number; durationRanges: Array<{ key: number; doc_count: number }>; }> => { - const { apmEventClient } = setup; const durationField = getDurationField(chartType, searchMetrics); // when using metrics data, ensure we filter by docs with the appropriate duration field diff --git a/x-pack/plugins/apm/server/routes/correlations/queries/fetch_failed_events_correlation_p_values.ts b/x-pack/plugins/apm/server/routes/correlations/queries/fetch_failed_events_correlation_p_values.ts index 0eddb2b655e3..405ca6250e5d 100644 --- a/x-pack/plugins/apm/server/routes/correlations/queries/fetch_failed_events_correlation_p_values.ts +++ b/x-pack/plugins/apm/server/routes/correlations/queries/fetch_failed_events_correlation_p_values.ts @@ -13,13 +13,13 @@ import { } from '../../../../common/elasticsearch_fieldnames'; import { EventOutcome } from '../../../../common/event_outcome'; import { LatencyDistributionChartType } from '../../../../common/latency_distribution_chart_types'; -import { Setup } from '../../../lib/helpers/setup_request'; import { getCommonCorrelationsQuery } from './get_common_correlations_query'; import { fetchDurationRanges } from './fetch_duration_ranges'; import { getEventType } from '../utils'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; export const fetchFailedEventsCorrelationPValues = async ({ - setup, + apmEventClient, start, end, environment, @@ -28,12 +28,10 @@ export const fetchFailedEventsCorrelationPValues = async ({ rangeSteps, fieldName, }: CommonCorrelationsQueryParams & { - setup: Setup; + apmEventClient: APMEventClient; rangeSteps: number[]; fieldName: string; }) => { - const { apmEventClient } = setup; - const chartType = LatencyDistributionChartType.failedTransactionsCorrelations; const searchMetrics = false; // failed transactions correlations does not search metrics documents const eventType = getEventType(chartType, searchMetrics); @@ -106,7 +104,7 @@ export const fetchFailedEventsCorrelationPValues = async ({ 0.25 * Math.min(Math.max((bucket.score - 13.816) / 101.314, 0), 1); const { durationRanges: histogram } = await fetchDurationRanges({ - setup, + apmEventClient, chartType, start, end, diff --git a/x-pack/plugins/apm/server/routes/correlations/queries/fetch_field_value_pairs.ts b/x-pack/plugins/apm/server/routes/correlations/queries/fetch_field_value_pairs.ts index e0b12cb8bb9c..72ffea93e2ca 100644 --- a/x-pack/plugins/apm/server/routes/correlations/queries/fetch_field_value_pairs.ts +++ b/x-pack/plugins/apm/server/routes/correlations/queries/fetch_field_value_pairs.ts @@ -13,11 +13,11 @@ import type { import { TERMS_SIZE } from '../../../../common/correlations/constants'; import { splitAllSettledPromises } from '../utils'; -import { Setup } from '../../../lib/helpers/setup_request'; import { getCommonCorrelationsQuery } from './get_common_correlations_query'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; export const fetchFieldValuePairs = async ({ - setup, + apmEventClient, fieldCandidates, eventType, start, @@ -26,12 +26,10 @@ export const fetchFieldValuePairs = async ({ kuery, query, }: CommonCorrelationsQueryParams & { - setup: Setup; + apmEventClient: APMEventClient; fieldCandidates: string[]; eventType: ProcessorEvent; }): Promise<{ fieldValuePairs: FieldValuePair[]; errors: any[] }> => { - const { apmEventClient } = setup; - const { fulfilled: responses, rejected: errors } = splitAllSettledPromises( await Promise.allSettled( fieldCandidates.map(async (fieldName) => { diff --git a/x-pack/plugins/apm/server/routes/correlations/queries/fetch_p_values.ts b/x-pack/plugins/apm/server/routes/correlations/queries/fetch_p_values.ts index 72cd6baaefec..8f2e46b3f4d3 100644 --- a/x-pack/plugins/apm/server/routes/correlations/queries/fetch_p_values.ts +++ b/x-pack/plugins/apm/server/routes/correlations/queries/fetch_p_values.ts @@ -10,6 +10,7 @@ import type { FailedTransactionsCorrelation } from '../../../../common/correlati import { CommonCorrelationsQueryParams } from '../../../../common/correlations/types'; import { LatencyDistributionChartType } from '../../../../common/latency_distribution_chart_types'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; import { Setup } from '../../../lib/helpers/setup_request'; import { splitAllSettledPromises, getEventType } from '../utils'; import { fetchDurationHistogramRangeSteps } from './fetch_duration_histogram_range_steps'; @@ -17,6 +18,7 @@ import { fetchFailedEventsCorrelationPValues } from './fetch_failed_events_corre export const fetchPValues = async ({ setup, + apmEventClient, start, end, environment, @@ -27,6 +29,7 @@ export const fetchPValues = async ({ fieldCandidates, }: CommonCorrelationsQueryParams & { setup: Setup; + apmEventClient: APMEventClient; durationMin?: number; durationMax?: number; fieldCandidates: string[]; @@ -36,7 +39,7 @@ export const fetchPValues = async ({ const eventType = getEventType(chartType, searchMetrics); const { rangeSteps } = await fetchDurationHistogramRangeSteps({ - setup, + apmEventClient, chartType, start, end, @@ -52,7 +55,7 @@ export const fetchPValues = async ({ await Promise.allSettled( fieldCandidates.map((fieldName) => fetchFailedEventsCorrelationPValues({ - setup, + apmEventClient, start, end, environment, diff --git a/x-pack/plugins/apm/server/routes/correlations/queries/fetch_significant_correlations.ts b/x-pack/plugins/apm/server/routes/correlations/queries/fetch_significant_correlations.ts index abb23fbfac0e..3bfa96423dca 100644 --- a/x-pack/plugins/apm/server/routes/correlations/queries/fetch_significant_correlations.ts +++ b/x-pack/plugins/apm/server/routes/correlations/queries/fetch_significant_correlations.ts @@ -26,9 +26,11 @@ import { fetchDurationFractions } from './fetch_duration_fractions'; import { fetchDurationHistogramRangeSteps } from './fetch_duration_histogram_range_steps'; import { fetchDurationRanges } from './fetch_duration_ranges'; import { getEventType } from '../utils'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; export const fetchSignificantCorrelations = async ({ setup, + apmEventClient, start, end, environment, @@ -39,6 +41,7 @@ export const fetchSignificantCorrelations = async ({ fieldValuePairs, }: CommonCorrelationsQueryParams & { setup: Setup; + apmEventClient: APMEventClient; durationMinOverride?: number; durationMaxOverride?: number; fieldValuePairs: FieldValuePair[]; @@ -50,7 +53,7 @@ export const fetchSignificantCorrelations = async ({ const eventType = getEventType(chartType, searchMetrics); const { percentiles: percentilesRecords } = await fetchDurationPercentiles({ - setup, + apmEventClient, chartType, start, end, @@ -69,7 +72,7 @@ export const fetchSignificantCorrelations = async ({ const { expectations, ranges } = computeExpectationsAndRanges(percentiles); const { fractions, totalDocCount } = await fetchDurationFractions({ - setup, + apmEventClient, eventType, start, end, @@ -80,7 +83,7 @@ export const fetchSignificantCorrelations = async ({ }); const { rangeSteps } = await fetchDurationHistogramRangeSteps({ - setup, + apmEventClient, chartType, start, end, @@ -96,7 +99,7 @@ export const fetchSignificantCorrelations = async ({ await Promise.allSettled( fieldValuePairs.map((fieldValuePair) => fetchDurationCorrelationWithHistogram({ - setup, + apmEventClient, chartType, start, end, @@ -142,7 +145,7 @@ export const fetchSignificantCorrelations = async ({ if (latencyCorrelations.length === 0 && fallbackResult) { const { fieldName, fieldValue } = fallbackResult; const { durationRanges: histogram } = await fetchDurationRanges({ - setup, + apmEventClient, chartType, start, end, diff --git a/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/fetch_boolean_field_stats.ts b/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/fetch_boolean_field_stats.ts index 0d03843fd208..ff1019778ad5 100644 --- a/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/fetch_boolean_field_stats.ts +++ b/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/fetch_boolean_field_stats.ts @@ -11,11 +11,11 @@ import { FieldValuePair, } from '../../../../../common/correlations/types'; import { BooleanFieldStats } from '../../../../../common/correlations/field_stats_types'; -import { Setup } from '../../../../lib/helpers/setup_request'; import { getCommonCorrelationsQuery } from '../get_common_correlations_query'; +import { APMEventClient } from '../../../../lib/helpers/create_es_client/create_apm_event_client'; export const fetchBooleanFieldStats = async ({ - setup, + apmEventClient, eventType, start, end, @@ -24,12 +24,10 @@ export const fetchBooleanFieldStats = async ({ field, query, }: CommonCorrelationsQueryParams & { - setup: Setup; + apmEventClient: APMEventClient; eventType: ProcessorEvent; field: FieldValuePair; }): Promise => { - const { apmEventClient } = setup; - const { fieldName } = field; const { aggregations } = await apmEventClient.search( diff --git a/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/fetch_field_value_field_stats.ts b/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/fetch_field_value_field_stats.ts index 9208a8ad682d..622c7b7d8952 100644 --- a/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/fetch_field_value_field_stats.ts +++ b/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/fetch_field_value_field_stats.ts @@ -14,11 +14,11 @@ import { FieldValueFieldStats, TopValueBucket, } from '../../../../../common/correlations/field_stats_types'; -import { Setup } from '../../../../lib/helpers/setup_request'; import { getCommonCorrelationsQuery } from '../get_common_correlations_query'; +import { APMEventClient } from '../../../../lib/helpers/create_es_client/create_apm_event_client'; export const fetchFieldValueFieldStats = async ({ - setup, + apmEventClient, eventType, start, end, @@ -28,11 +28,9 @@ export const fetchFieldValueFieldStats = async ({ field, }: CommonCorrelationsQueryParams & { eventType: ProcessorEvent; - setup: Setup; + apmEventClient: APMEventClient; field: FieldValuePair; }): Promise => { - const { apmEventClient } = setup; - const { aggregations } = await apmEventClient.search( 'get_field_value_field_stats', { diff --git a/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/fetch_fields_stats.ts b/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/fetch_fields_stats.ts index 2cd1843c6d00..90ed9b3a9295 100644 --- a/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/fetch_fields_stats.ts +++ b/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/fetch_fields_stats.ts @@ -16,10 +16,10 @@ import { FieldStats } from '../../../../../common/correlations/field_stats_types import { fetchKeywordFieldStats } from './fetch_keyword_field_stats'; import { fetchNumericFieldStats } from './fetch_numeric_field_stats'; import { fetchBooleanFieldStats } from './fetch_boolean_field_stats'; -import { Setup } from '../../../../lib/helpers/setup_request'; +import { APMEventClient } from '../../../../lib/helpers/create_es_client/create_apm_event_client'; export const fetchFieldsStats = async ({ - setup, + apmEventClient, eventType, start, end, @@ -29,13 +29,12 @@ export const fetchFieldsStats = async ({ fieldsToSample, }: CommonCorrelationsQueryParams & { eventType: ProcessorEvent; - setup: Setup; + apmEventClient: APMEventClient; fieldsToSample: string[]; }): Promise<{ stats: FieldStats[]; errors: any[]; }> => { - const { apmEventClient } = setup; const stats: FieldStats[] = []; const errors: any[] = []; @@ -68,7 +67,7 @@ export const fetchFieldsStats = async ({ case ES_FIELD_TYPES.KEYWORD: case ES_FIELD_TYPES.IP: return fetchKeywordFieldStats({ - setup, + apmEventClient, eventType, start, end, @@ -91,7 +90,7 @@ export const fetchFieldsStats = async ({ case ES_FIELD_TYPES.UNSIGNED_LONG: case ES_FIELD_TYPES.BYTE: return fetchNumericFieldStats({ - setup, + apmEventClient, eventType, start, end, @@ -104,7 +103,7 @@ export const fetchFieldsStats = async ({ break; case ES_FIELD_TYPES.BOOLEAN: return fetchBooleanFieldStats({ - setup, + apmEventClient, eventType, start, end, diff --git a/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/fetch_keyword_field_stats.ts b/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/fetch_keyword_field_stats.ts index 30e88c0eb8ef..7127db07721e 100644 --- a/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/fetch_keyword_field_stats.ts +++ b/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/fetch_keyword_field_stats.ts @@ -11,11 +11,11 @@ import { FieldValuePair, } from '../../../../../common/correlations/types'; import { KeywordFieldStats } from '../../../../../common/correlations/field_stats_types'; -import { Setup } from '../../../../lib/helpers/setup_request'; import { getCommonCorrelationsQuery } from '../get_common_correlations_query'; +import { APMEventClient } from '../../../../lib/helpers/create_es_client/create_apm_event_client'; export const fetchKeywordFieldStats = async ({ - setup, + apmEventClient, eventType, start, end, @@ -24,12 +24,10 @@ export const fetchKeywordFieldStats = async ({ query, field, }: CommonCorrelationsQueryParams & { - setup: Setup; + apmEventClient: APMEventClient; eventType: ProcessorEvent; field: FieldValuePair; }): Promise => { - const { apmEventClient } = setup; - const body = await apmEventClient.search('get_keyword_field_stats', { apm: { events: [eventType], diff --git a/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/fetch_numeric_field_stats.ts b/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/fetch_numeric_field_stats.ts index 04b43e09d182..63bd2a3ea9c8 100644 --- a/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/fetch_numeric_field_stats.ts +++ b/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/fetch_numeric_field_stats.ts @@ -15,11 +15,11 @@ import { CommonCorrelationsQueryParams, FieldValuePair, } from '../../../../../common/correlations/types'; -import { Setup } from '../../../../lib/helpers/setup_request'; +import { APMEventClient } from '../../../../lib/helpers/create_es_client/create_apm_event_client'; import { getCommonCorrelationsQuery } from '../get_common_correlations_query'; export const fetchNumericFieldStats = async ({ - setup, + apmEventClient, eventType, start, end, @@ -28,12 +28,10 @@ export const fetchNumericFieldStats = async ({ query, field, }: CommonCorrelationsQueryParams & { - setup: Setup; + apmEventClient: APMEventClient; eventType: ProcessorEvent; field: FieldValuePair; }): Promise => { - const { apmEventClient } = setup; - const { fieldName } = field; const { aggregations } = await apmEventClient.search( diff --git a/x-pack/plugins/apm/server/routes/correlations/route.ts b/x-pack/plugins/apm/server/routes/correlations/route.ts index fd3a405bc7a9..4af95e7a31c1 100644 --- a/x-pack/plugins/apm/server/routes/correlations/route.ts +++ b/x-pack/plugins/apm/server/routes/correlations/route.ts @@ -30,6 +30,7 @@ import { fetchFieldValuePairs } from './queries/fetch_field_value_pairs'; import { fetchSignificantCorrelations } from './queries/fetch_significant_correlations'; import { fetchFieldsStats } from './queries/field_stats/fetch_fields_stats'; import { fetchPValues } from './queries/fetch_p_values'; +import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; const INVALID_LICENSE = i18n.translate('xpack.apm.correlations.license.text', { defaultMessage: @@ -58,7 +59,7 @@ const fieldCandidatesTransactionsRoute = createApmServerRoute({ throw Boom.forbidden(INVALID_LICENSE); } - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { query: { @@ -87,7 +88,7 @@ const fieldCandidatesTransactionsRoute = createApmServerRoute({ ], }, }, - setup, + apmEventClient, }); }, }); @@ -124,7 +125,7 @@ const fieldStatsTransactionsRoute = createApmServerRoute({ throw Boom.forbidden(INVALID_LICENSE); } - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { body: { @@ -140,7 +141,7 @@ const fieldStatsTransactionsRoute = createApmServerRoute({ } = resources.params; return fetchFieldsStats({ - setup, + apmEventClient, eventType: ProcessorEvent.transaction, start, end, @@ -190,7 +191,7 @@ const fieldValueStatsTransactionsRoute = createApmServerRoute({ throw Boom.forbidden(INVALID_LICENSE); } - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { query: { @@ -207,7 +208,7 @@ const fieldValueStatsTransactionsRoute = createApmServerRoute({ } = resources.params; return fetchFieldValueFieldStats({ - setup, + apmEventClient, eventType: ProcessorEvent.transaction, start, end, @@ -262,7 +263,7 @@ const fieldValuePairsTransactionsRoute = createApmServerRoute({ throw Boom.forbidden(INVALID_LICENSE); } - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { body: { @@ -278,7 +279,7 @@ const fieldValuePairsTransactionsRoute = createApmServerRoute({ } = resources.params; return fetchFieldValuePairs({ - setup, + apmEventClient, eventType: ProcessorEvent.transaction, start, end, @@ -334,8 +335,10 @@ const significantCorrelationsTransactionsRoute = createApmServerRoute({ totalDocCount: number; fallbackResult?: import('./../../../common/correlations/latency_correlations/types').LatencyCorrelation; }> => { - const setup = await setupRequest(resources); - + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { body: { serviceName, @@ -353,6 +356,7 @@ const significantCorrelationsTransactionsRoute = createApmServerRoute({ return fetchSignificantCorrelations({ setup, + apmEventClient, start, end, environment, @@ -402,7 +406,10 @@ const pValuesTransactionsRoute = createApmServerRoute({ ccsWarning: boolean; fallbackResult?: import('./../../../common/correlations/failed_transactions_correlations/types').FailedTransactionsCorrelation; }> => { - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { body: { @@ -421,6 +428,7 @@ const pValuesTransactionsRoute = createApmServerRoute({ return fetchPValues({ setup, + apmEventClient, start, end, environment, diff --git a/x-pack/plugins/apm/server/routes/data_view/create_static_data_view.test.ts b/x-pack/plugins/apm/server/routes/data_view/create_static_data_view.test.ts index 65ecb93bcb76..be38d78fb208 100644 --- a/x-pack/plugins/apm/server/routes/data_view/create_static_data_view.test.ts +++ b/x-pack/plugins/apm/server/routes/data_view/create_static_data_view.test.ts @@ -11,6 +11,7 @@ import * as HistoricalAgentData from '../historical_data/has_historical_agent_da import { DataViewsService } from '@kbn/data-views-plugin/common'; import { APMRouteHandlerResources, APMCore } from '../typings'; import { APMConfig } from '../..'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; function getMockedDataViewService(existingDataViewTitle: string) { return { @@ -44,11 +45,14 @@ const coreMock = { }, } as unknown as APMCore; +const apmEventClientMock = { search: jest.fn() } as unknown as APMEventClient; + describe('createStaticDataView', () => { it(`should not create data view if 'xpack.apm.autocreateApmIndexPattern=false'`, async () => { const dataViewService = getMockedDataViewService('apm-*'); await createStaticDataView({ setup: setupMock, + apmEventClient: apmEventClientMock, resources: { config: { autoCreateApmDataView: false }, } as APMRouteHandlerResources, @@ -67,6 +71,7 @@ describe('createStaticDataView', () => { await createStaticDataView({ setup: setupMock, + apmEventClient: apmEventClientMock, resources: { config: { autoCreateApmDataView: false }, } as APMRouteHandlerResources, @@ -85,6 +90,7 @@ describe('createStaticDataView', () => { await createStaticDataView({ setup: setupMock, + apmEventClient: apmEventClientMock, resources: { core: coreMock, config: { autoCreateApmDataView: true }, @@ -107,6 +113,7 @@ describe('createStaticDataView', () => { await createStaticDataView({ setup: setupMock, + apmEventClient: apmEventClientMock, resources: { core: coreMock, config: { autoCreateApmDataView: true }, @@ -136,6 +143,7 @@ describe('createStaticDataView', () => { await createStaticDataView({ setup: setupMock, + apmEventClient: apmEventClientMock, resources: { core: coreMock, config: { autoCreateApmDataView: true }, diff --git a/x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts b/x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts index c2310acadcff..ff25167a0a12 100644 --- a/x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts +++ b/x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts @@ -19,6 +19,7 @@ import { getApmDataViewTitle } from './get_apm_data_view_title'; import { APMRouteHandlerResources } from '../typings'; import { Setup } from '../../lib/helpers/setup_request'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export type CreateDataViewResponse = Promise< | { created: boolean; dataView: DataView } @@ -29,10 +30,12 @@ export async function createStaticDataView({ dataViewService, resources, setup, + apmEventClient, }: { dataViewService: DataViewsService; resources: APMRouteHandlerResources; setup: Setup; + apmEventClient: APMEventClient; }): CreateDataViewResponse { const { config } = resources; @@ -50,7 +53,7 @@ export async function createStaticDataView({ // Discover and other apps will throw errors if an data view exists without having matching indices. // The following ensures the data view is only created if APM data is found - const hasData = await hasHistoricalAgentData(setup); + const hasData = await hasHistoricalAgentData(apmEventClient); if (!hasData) { return { diff --git a/x-pack/plugins/apm/server/routes/data_view/route.ts b/x-pack/plugins/apm/server/routes/data_view/route.ts index 6f55f67fc9b4..8af2a522b7dc 100644 --- a/x-pack/plugins/apm/server/routes/data_view/route.ts +++ b/x-pack/plugins/apm/server/routes/data_view/route.ts @@ -13,13 +13,17 @@ import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { getApmDataViewTitle } from './get_apm_data_view_title'; import { getApmIndices } from '../settings/apm_indices/get_apm_indices'; import { setupRequest } from '../../lib/helpers/setup_request'; +import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; const staticDataViewRoute = createApmServerRoute({ endpoint: 'POST /internal/apm/data_view/static', options: { tags: ['access:apm'] }, handler: async (resources): CreateDataViewResponse => { const { context, plugins, request } = resources; - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const coreContext = await context.core; const dataViewStart = await plugins.dataViews.start(); @@ -34,6 +38,7 @@ const staticDataViewRoute = createApmServerRoute({ dataViewService, resources, setup, + apmEventClient, }); return res; diff --git a/x-pack/plugins/apm/server/routes/dependencies/get_dependency_latency_distribution.ts b/x-pack/plugins/apm/server/routes/dependencies/get_dependency_latency_distribution.ts index 9f9e356c96dc..150ec8664a24 100644 --- a/x-pack/plugins/apm/server/routes/dependencies/get_dependency_latency_distribution.ts +++ b/x-pack/plugins/apm/server/routes/dependencies/get_dependency_latency_distribution.ts @@ -14,12 +14,12 @@ import { import { Environment } from '../../../common/environment_rt'; import { EventOutcome } from '../../../common/event_outcome'; import { LatencyDistributionChartType } from '../../../common/latency_distribution_chart_types'; -import { Setup } from '../../lib/helpers/setup_request'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; import { getOverallLatencyDistribution } from '../latency_distribution/get_overall_latency_distribution'; import { OverallLatencyDistributionResponse } from '../latency_distribution/types'; export async function getDependencyLatencyDistribution({ - setup, + apmEventClient, dependencyName, spanName, kuery, @@ -28,7 +28,7 @@ export async function getDependencyLatencyDistribution({ end, percentileThreshold, }: { - setup: Setup; + apmEventClient: APMEventClient; dependencyName: string; spanName: string; kuery: string; @@ -40,9 +40,9 @@ export async function getDependencyLatencyDistribution({ allSpansDistribution: OverallLatencyDistributionResponse; failedSpansDistribution: OverallLatencyDistributionResponse; }> { - const commonProps = { + const commonParams = { chartType: LatencyDistributionChartType.dependencyLatency, - setup, + apmEventClient, start, end, environment, @@ -62,11 +62,11 @@ export async function getDependencyLatencyDistribution({ const [allSpansDistribution, failedSpansDistribution] = await Promise.all([ getOverallLatencyDistribution({ - ...commonProps, + ...commonParams, query: commonQuery, }), getOverallLatencyDistribution({ - ...commonProps, + ...commonParams, query: { bool: { filter: [ diff --git a/x-pack/plugins/apm/server/routes/dependencies/get_error_rate_charts_for_dependency.ts b/x-pack/plugins/apm/server/routes/dependencies/get_error_rate_charts_for_dependency.ts index d1f7a655c9f1..135178aca2ac 100644 --- a/x-pack/plugins/apm/server/routes/dependencies/get_error_rate_charts_for_dependency.ts +++ b/x-pack/plugins/apm/server/routes/dependencies/get_error_rate_charts_for_dependency.ts @@ -17,7 +17,6 @@ import { SPAN_NAME, } from '../../../common/elasticsearch_fieldnames'; import { environmentQuery } from '../../../common/utils/environment_query'; -import { Setup } from '../../lib/helpers/setup_request'; import { getMetricsDateHistogramParams } from '../../lib/helpers/metrics'; import { getOffsetInMs } from '../../../common/utils/get_offset_in_ms'; import { @@ -25,11 +24,12 @@ import { getDocumentTypeFilterForServiceDestinationStatistics, getProcessorEventForServiceDestinationStatistics, } from '../../lib/helpers/spans/get_is_using_service_destination_metrics'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getErrorRateChartsForDependency({ dependencyName, spanName, - setup, + apmEventClient, start, end, environment, @@ -39,7 +39,7 @@ export async function getErrorRateChartsForDependency({ }: { dependencyName: string; spanName: string; - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; environment: string; @@ -47,8 +47,6 @@ export async function getErrorRateChartsForDependency({ searchServiceDestinationMetrics: boolean; offset?: string; }) { - const { apmEventClient } = setup; - const { offsetInMs, startWithOffset, endWithOffset } = getOffsetInMs({ start, end, diff --git a/x-pack/plugins/apm/server/routes/dependencies/get_latency_charts_for_dependency.ts b/x-pack/plugins/apm/server/routes/dependencies/get_latency_charts_for_dependency.ts index 706b0c9d59b9..14689f966570 100644 --- a/x-pack/plugins/apm/server/routes/dependencies/get_latency_charts_for_dependency.ts +++ b/x-pack/plugins/apm/server/routes/dependencies/get_latency_charts_for_dependency.ts @@ -15,7 +15,6 @@ import { SPAN_NAME, } from '../../../common/elasticsearch_fieldnames'; import { environmentQuery } from '../../../common/utils/environment_query'; -import { Setup } from '../../lib/helpers/setup_request'; import { getMetricsDateHistogramParams } from '../../lib/helpers/metrics'; import { getOffsetInMs } from '../../../common/utils/get_offset_in_ms'; import { @@ -24,12 +23,13 @@ import { getLatencyFieldForServiceDestinationStatistics, getProcessorEventForServiceDestinationStatistics, } from '../../lib/helpers/spans/get_is_using_service_destination_metrics'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getLatencyChartsForDependency({ dependencyName, spanName, searchServiceDestinationMetrics, - setup, + apmEventClient, start, end, environment, @@ -39,15 +39,13 @@ export async function getLatencyChartsForDependency({ dependencyName: string; spanName: string; searchServiceDestinationMetrics: boolean; - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; environment: string; kuery: string; offset?: string; }) { - const { apmEventClient } = setup; - const { offsetInMs, startWithOffset, endWithOffset } = getOffsetInMs({ start, end, diff --git a/x-pack/plugins/apm/server/routes/dependencies/get_metadata_for_dependency.ts b/x-pack/plugins/apm/server/routes/dependencies/get_metadata_for_dependency.ts index 1ebfeab5d8a0..5daf4483f8fd 100644 --- a/x-pack/plugins/apm/server/routes/dependencies/get_metadata_for_dependency.ts +++ b/x-pack/plugins/apm/server/routes/dependencies/get_metadata_for_dependency.ts @@ -9,21 +9,19 @@ import { rangeQuery } from '@kbn/observability-plugin/server'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; import { maybe } from '../../../common/utils/maybe'; import { SPAN_DESTINATION_SERVICE_RESOURCE } from '../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../lib/helpers/setup_request'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getMetadataForDependency({ - setup, + apmEventClient, dependencyName, start, end, }: { - setup: Setup; + apmEventClient: APMEventClient; dependencyName: string; start: number; end: number; }) { - const { apmEventClient } = setup; - const sampleResponse = await apmEventClient.search( 'get_metadata_for_dependency', { diff --git a/x-pack/plugins/apm/server/routes/dependencies/get_throughput_charts_for_dependency.ts b/x-pack/plugins/apm/server/routes/dependencies/get_throughput_charts_for_dependency.ts index 63c72f6c460d..6ba07907666b 100644 --- a/x-pack/plugins/apm/server/routes/dependencies/get_throughput_charts_for_dependency.ts +++ b/x-pack/plugins/apm/server/routes/dependencies/get_throughput_charts_for_dependency.ts @@ -15,7 +15,6 @@ import { SPAN_NAME, } from '../../../common/elasticsearch_fieldnames'; import { environmentQuery } from '../../../common/utils/environment_query'; -import { Setup } from '../../lib/helpers/setup_request'; import { getOffsetInMs } from '../../../common/utils/get_offset_in_ms'; import { getBucketSize } from '../../lib/helpers/get_bucket_size'; import { @@ -23,11 +22,12 @@ import { getDocumentTypeFilterForServiceDestinationStatistics, getProcessorEventForServiceDestinationStatistics, } from '../../lib/helpers/spans/get_is_using_service_destination_metrics'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getThroughputChartsForDependency({ dependencyName, spanName, - setup, + apmEventClient, start, end, environment, @@ -37,7 +37,7 @@ export async function getThroughputChartsForDependency({ }: { dependencyName: string; spanName: string; - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; environment: string; @@ -45,8 +45,6 @@ export async function getThroughputChartsForDependency({ searchServiceDestinationMetrics: boolean; offset?: string; }) { - const { apmEventClient } = setup; - const { offsetInMs, startWithOffset, endWithOffset } = getOffsetInMs({ start, end, diff --git a/x-pack/plugins/apm/server/routes/dependencies/get_top_dependencies.ts b/x-pack/plugins/apm/server/routes/dependencies/get_top_dependencies.ts index 08e2f6183303..d8ee73af18e8 100644 --- a/x-pack/plugins/apm/server/routes/dependencies/get_top_dependencies.ts +++ b/x-pack/plugins/apm/server/routes/dependencies/get_top_dependencies.ts @@ -10,10 +10,10 @@ import { NodeType } from '../../../common/connections'; import { environmentQuery } from '../../../common/utils/environment_query'; import { getConnectionStats } from '../../lib/connections/get_connection_stats'; import { getConnectionStatsItemsWithRelativeImpact } from '../../lib/connections/get_connection_stats/get_connection_stats_items_with_relative_impact'; -import { Setup } from '../../lib/helpers/setup_request'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getTopDependencies({ - setup, + apmEventClient, start, end, numBuckets, @@ -21,7 +21,7 @@ export async function getTopDependencies({ offset, kuery, }: { - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; numBuckets: number; @@ -30,7 +30,7 @@ export async function getTopDependencies({ kuery: string; }) { const statsItems = await getConnectionStats({ - setup, + apmEventClient, start, end, numBuckets, diff --git a/x-pack/plugins/apm/server/routes/dependencies/get_top_dependency_operations.ts b/x-pack/plugins/apm/server/routes/dependencies/get_top_dependency_operations.ts index 3c688f9aaa48..b2e4c44a730f 100644 --- a/x-pack/plugins/apm/server/routes/dependencies/get_top_dependency_operations.ts +++ b/x-pack/plugins/apm/server/routes/dependencies/get_top_dependency_operations.ts @@ -24,13 +24,13 @@ import { environmentQuery } from '../../../common/utils/environment_query'; import { getOffsetInMs } from '../../../common/utils/get_offset_in_ms'; import { calculateThroughputWithRange } from '../../lib/helpers/calculate_throughput'; import { getBucketSizeForAggregatedTransactions } from '../../lib/helpers/get_bucket_size_for_aggregated_transactions'; -import { Setup } from '../../lib/helpers/setup_request'; import { getDocumentTypeFilterForServiceDestinationStatistics, getLatencyFieldForServiceDestinationStatistics, getProcessorEventForServiceDestinationStatistics, } from '../../lib/helpers/spans/get_is_using_service_destination_metrics'; import { calculateImpactBuilder } from '../traces/calculate_impact_builder'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; const MAX_NUM_OPERATIONS = 500; @@ -47,7 +47,7 @@ export interface DependencyOperation { } export async function getTopDependencyOperations({ - setup, + apmEventClient, dependencyName, start, end, @@ -56,7 +56,7 @@ export async function getTopDependencyOperations({ kuery, searchServiceDestinationMetrics, }: { - setup: Setup; + apmEventClient: APMEventClient; dependencyName: string; start: number; end: number; @@ -65,8 +65,6 @@ export async function getTopDependencyOperations({ kuery: string; searchServiceDestinationMetrics: boolean; }) { - const { apmEventClient } = setup; - const { startWithOffset, endWithOffset, offsetInMs } = getOffsetInMs({ start, end, diff --git a/x-pack/plugins/apm/server/routes/dependencies/get_top_dependency_spans.ts b/x-pack/plugins/apm/server/routes/dependencies/get_top_dependency_spans.ts index 6b4998517735..5a2df933c7db 100644 --- a/x-pack/plugins/apm/server/routes/dependencies/get_top_dependency_spans.ts +++ b/x-pack/plugins/apm/server/routes/dependencies/get_top_dependency_spans.ts @@ -30,7 +30,7 @@ import { Environment } from '../../../common/environment_rt'; import { EventOutcome } from '../../../common/event_outcome'; import { environmentQuery } from '../../../common/utils/environment_query'; import { AgentName } from '../../../typings/es_schemas/ui/fields/agent'; -import { Setup } from '../../lib/helpers/setup_request'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; const MAX_NUM_SPANS = 1000; @@ -48,7 +48,7 @@ export interface DependencySpan { } export async function getTopDependencySpans({ - setup, + apmEventClient, dependencyName, spanName, start, @@ -58,7 +58,7 @@ export async function getTopDependencySpans({ sampleRangeFrom, sampleRangeTo, }: { - setup: Setup; + apmEventClient: APMEventClient; dependencyName: string; spanName: string; start: number; @@ -68,8 +68,6 @@ export async function getTopDependencySpans({ sampleRangeFrom?: number; sampleRangeTo?: number; }): Promise { - const { apmEventClient } = setup; - const spans = ( await apmEventClient.search('get_top_dependency_spans', { apm: { diff --git a/x-pack/plugins/apm/server/routes/dependencies/get_upstream_services_for_dependency.ts b/x-pack/plugins/apm/server/routes/dependencies/get_upstream_services_for_dependency.ts index 4d620619e2eb..8f2710cbc972 100644 --- a/x-pack/plugins/apm/server/routes/dependencies/get_upstream_services_for_dependency.ts +++ b/x-pack/plugins/apm/server/routes/dependencies/get_upstream_services_for_dependency.ts @@ -10,10 +10,10 @@ import { SPAN_DESTINATION_SERVICE_RESOURCE } from '../../../common/elasticsearch import { environmentQuery } from '../../../common/utils/environment_query'; import { getConnectionStats } from '../../lib/connections/get_connection_stats'; import { getConnectionStatsItemsWithRelativeImpact } from '../../lib/connections/get_connection_stats/get_connection_stats_items_with_relative_impact'; -import { Setup } from '../../lib/helpers/setup_request'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getUpstreamServicesForDependency({ - setup, + apmEventClient, start, end, dependencyName, @@ -22,7 +22,7 @@ export async function getUpstreamServicesForDependency({ environment, offset, }: { - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; dependencyName: string; @@ -32,7 +32,7 @@ export async function getUpstreamServicesForDependency({ offset?: string; }) { const statsItems = await getConnectionStats({ - setup, + apmEventClient, start, end, filter: [ diff --git a/x-pack/plugins/apm/server/routes/dependencies/route.ts b/x-pack/plugins/apm/server/routes/dependencies/route.ts index 1ba2e92eee57..964e1ed95834 100644 --- a/x-pack/plugins/apm/server/routes/dependencies/route.ts +++ b/x-pack/plugins/apm/server/routes/dependencies/route.ts @@ -7,7 +7,6 @@ import * as t from 'io-ts'; import { toBooleanRt, toNumberRt } from '@kbn/io-ts-utils'; -import { setupRequest } from '../../lib/helpers/setup_request'; import { environmentRt, kueryRt, rangeRt } from '../default_api_types'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { getMetadataForDependency } from './get_metadata_for_dependency'; @@ -28,6 +27,7 @@ import { DependencySpan, getTopDependencySpans, } from './get_top_dependency_spans'; +import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; const topDependenciesRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/dependencies/top_dependencies', @@ -100,11 +100,11 @@ const topDependenciesRoute = createApmServerRoute({ location: import('./../../../common/connections').Node; }>; }> => { - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { environment, offset, numBuckets, kuery, start, end } = resources.params.query; - const opts = { setup, start, end, numBuckets, environment, kuery }; + const opts = { apmEventClient, start, end, numBuckets, environment, kuery }; const [currentDependencies, previousDependencies] = await Promise.all([ getTopDependencies(opts), @@ -198,7 +198,7 @@ const upstreamServicesForDependencyRoute = createApmServerRoute({ location: import('./../../../common/connections').Node; }>; }> => { - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { query: { dependencyName, @@ -213,7 +213,7 @@ const upstreamServicesForDependencyRoute = createApmServerRoute({ const opts = { dependencyName, - setup, + apmEventClient, start, end, numBuckets, @@ -264,14 +264,14 @@ const dependencyMetadataRoute = createApmServerRoute({ ): Promise<{ metadata: { spanType: string | undefined; spanSubtype: string | undefined }; }> => { - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { params } = resources; const { dependencyName, start, end } = params.query; const metadata = await getMetadataForDependency({ dependencyName, - setup, + apmEventClient, start, end, }); @@ -304,7 +304,7 @@ const dependencyLatencyChartsRoute = createApmServerRoute({ currentTimeseries: Array<{ x: number; y: number }>; comparisonTimeseries: Array<{ x: number; y: number }> | null; }> => { - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { params } = resources; const { dependencyName, @@ -322,7 +322,7 @@ const dependencyLatencyChartsRoute = createApmServerRoute({ dependencyName, spanName, searchServiceDestinationMetrics, - setup, + apmEventClient, start, end, kuery, @@ -333,7 +333,7 @@ const dependencyLatencyChartsRoute = createApmServerRoute({ dependencyName, spanName, searchServiceDestinationMetrics, - setup, + apmEventClient, start, end, kuery, @@ -371,7 +371,7 @@ const dependencyThroughputChartsRoute = createApmServerRoute({ currentTimeseries: Array<{ x: number; y: number | null }>; comparisonTimeseries: Array<{ x: number; y: number | null }> | null; }> => { - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { params } = resources; const { dependencyName, @@ -388,7 +388,7 @@ const dependencyThroughputChartsRoute = createApmServerRoute({ getThroughputChartsForDependency({ dependencyName, spanName, - setup, + apmEventClient, start, end, kuery, @@ -399,7 +399,7 @@ const dependencyThroughputChartsRoute = createApmServerRoute({ ? getThroughputChartsForDependency({ dependencyName, spanName, - setup, + apmEventClient, start, end, kuery, @@ -438,7 +438,7 @@ const dependencyFailedTransactionRateChartsRoute = createApmServerRoute({ currentTimeseries: Array<{ x: number; y: number }>; comparisonTimeseries: Array<{ x: number; y: number }> | null; }> => { - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { params } = resources; const { dependencyName, @@ -455,7 +455,7 @@ const dependencyFailedTransactionRateChartsRoute = createApmServerRoute({ getErrorRateChartsForDependency({ dependencyName, spanName, - setup, + apmEventClient, start, end, kuery, @@ -466,7 +466,7 @@ const dependencyFailedTransactionRateChartsRoute = createApmServerRoute({ ? getErrorRateChartsForDependency({ dependencyName, spanName, - setup, + apmEventClient, start, end, kuery, @@ -501,7 +501,7 @@ const dependencyOperationsRoute = createApmServerRoute({ handler: async ( resources ): Promise<{ operations: DependencyOperation[] }> => { - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { query: { @@ -516,7 +516,7 @@ const dependencyOperationsRoute = createApmServerRoute({ } = resources.params; const operations = await getTopDependencyOperations({ - setup, + apmEventClient, dependencyName, start, end, @@ -553,7 +553,7 @@ const dependencyLatencyDistributionChartsRoute = createApmServerRoute({ allSpansDistribution: OverallLatencyDistributionResponse; failedSpansDistribution: OverallLatencyDistributionResponse; }> => { - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { params } = resources; const { dependencyName, @@ -566,7 +566,7 @@ const dependencyLatencyDistributionChartsRoute = createApmServerRoute({ } = params.query; return getDependencyLatencyDistribution({ - setup, + apmEventClient, dependencyName, spanName, percentileThreshold, @@ -593,7 +593,7 @@ const topDependencySpansRoute = createApmServerRoute({ ]), }), handler: async (resources): Promise<{ spans: DependencySpan[] }> => { - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { query: { @@ -609,7 +609,7 @@ const topDependencySpansRoute = createApmServerRoute({ } = resources.params; const spans = await getTopDependencySpans({ - setup, + apmEventClient, dependencyName, spanName, start, diff --git a/x-pack/plugins/apm/server/routes/environments/get_all_environments.test.ts b/x-pack/plugins/apm/server/routes/environments/get_all_environments.test.ts index 0f27839d9404..dd8dcfb31ba6 100644 --- a/x-pack/plugins/apm/server/routes/environments/get_all_environments.test.ts +++ b/x-pack/plugins/apm/server/routes/environments/get_all_environments.test.ts @@ -19,11 +19,11 @@ describe('getAllEnvironments', () => { }); it('fetches all environments', async () => { - mock = await inspectSearchParams((setup) => + mock = await inspectSearchParams((setup, apmEventClient) => getAllEnvironments({ searchAggregatedTransactions: false, serviceName: 'test', - setup, + apmEventClient, size: 50, }) ); @@ -32,12 +32,12 @@ describe('getAllEnvironments', () => { }); it('fetches all environments with includeMissing', async () => { - mock = await inspectSearchParams((setup) => + mock = await inspectSearchParams((setup, apmEventClient) => getAllEnvironments({ includeMissing: true, searchAggregatedTransactions: false, serviceName: 'test', - setup, + apmEventClient, size: 50, }) ); diff --git a/x-pack/plugins/apm/server/routes/environments/get_all_environments.ts b/x-pack/plugins/apm/server/routes/environments/get_all_environments.ts index 6cac07f1ea9b..8cd7c14a0d62 100644 --- a/x-pack/plugins/apm/server/routes/environments/get_all_environments.ts +++ b/x-pack/plugins/apm/server/routes/environments/get_all_environments.ts @@ -7,13 +7,13 @@ import { termQuery } from '@kbn/observability-plugin/server'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; -import { Setup } from '../../lib/helpers/setup_request'; import { SERVICE_NAME, SERVICE_ENVIRONMENT, } from '../../../common/elasticsearch_fieldnames'; import { ENVIRONMENT_NOT_DEFINED } from '../../../common/environment_filter_values'; import { getProcessorEventForTransactions } from '../../lib/helpers/transactions'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; /** * This is used for getting *all* environments, and does not filter by range. @@ -23,21 +23,19 @@ export async function getAllEnvironments({ includeMissing = false, searchAggregatedTransactions, serviceName, - setup, + apmEventClient, size, }: { includeMissing?: boolean; searchAggregatedTransactions: boolean; serviceName?: string; - setup: Setup; + apmEventClient: APMEventClient; size: number; }) { const operationName = serviceName ? 'get_all_environments_for_service' : 'get_all_environments_for_all_services'; - const { apmEventClient } = setup; - const params = { apm: { events: [ diff --git a/x-pack/plugins/apm/server/routes/environments/get_environments.test.ts b/x-pack/plugins/apm/server/routes/environments/get_environments.test.ts index 472fd9d226e3..21daac57a752 100644 --- a/x-pack/plugins/apm/server/routes/environments/get_environments.test.ts +++ b/x-pack/plugins/apm/server/routes/environments/get_environments.test.ts @@ -19,9 +19,9 @@ describe('getEnvironments', () => { }); it('fetches environments', async () => { - mock = await inspectSearchParams((setup) => + mock = await inspectSearchParams((setup, apmEventClient) => getEnvironments({ - setup, + apmEventClient, serviceName: 'foo', searchAggregatedTransactions: false, size: 50, @@ -34,9 +34,9 @@ describe('getEnvironments', () => { }); it('fetches environments without a service name', async () => { - mock = await inspectSearchParams((setup) => + mock = await inspectSearchParams((setup, apmEventClient) => getEnvironments({ - setup, + apmEventClient, searchAggregatedTransactions: false, size: 50, start: 0, diff --git a/x-pack/plugins/apm/server/routes/environments/get_environments.ts b/x-pack/plugins/apm/server/routes/environments/get_environments.ts index dbd0eb5a9d9c..e8a3abace204 100644 --- a/x-pack/plugins/apm/server/routes/environments/get_environments.ts +++ b/x-pack/plugins/apm/server/routes/environments/get_environments.ts @@ -13,8 +13,8 @@ import { } from '../../../common/elasticsearch_fieldnames'; import { ENVIRONMENT_NOT_DEFINED } from '../../../common/environment_filter_values'; import { getProcessorEventForTransactions } from '../../lib/helpers/transactions'; -import { Setup } from '../../lib/helpers/setup_request'; import { Environment } from '../../../common/environment_rt'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; /** * This is used for getting the list of environments for the environments selector, @@ -23,12 +23,12 @@ import { Environment } from '../../../common/environment_rt'; export async function getEnvironments({ searchAggregatedTransactions, serviceName, - setup, + apmEventClient, size, start, end, }: { - setup: Setup; + apmEventClient: APMEventClient; serviceName?: string; searchAggregatedTransactions: boolean; size: number; @@ -39,8 +39,6 @@ export async function getEnvironments({ ? 'get_environments_for_service' : 'get_environments'; - const { apmEventClient } = setup; - const params = { apm: { events: [ diff --git a/x-pack/plugins/apm/server/routes/environments/route.ts b/x-pack/plugins/apm/server/routes/environments/route.ts index 4a7755230754..6b0225caa9b3 100644 --- a/x-pack/plugins/apm/server/routes/environments/route.ts +++ b/x-pack/plugins/apm/server/routes/environments/route.ts @@ -12,6 +12,7 @@ import { setupRequest } from '../../lib/helpers/setup_request'; import { getEnvironments } from './get_environments'; import { rangeRt } from '../default_api_types'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; +import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; const environmentsRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/environments', @@ -36,11 +37,14 @@ const environmentsRoute = createApmServerRoute({ > >; }> => { - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { context, params } = resources; const { serviceName, start, end } = params.query; const searchAggregatedTransactions = await getSearchTransactionsEvents({ - apmEventClient: setup.apmEventClient, + apmEventClient, config: setup.config, start, end, @@ -51,7 +55,7 @@ const environmentsRoute = createApmServerRoute({ maxSuggestions ); const environments = await getEnvironments({ - setup, + apmEventClient, serviceName, searchAggregatedTransactions, size, diff --git a/x-pack/plugins/apm/server/routes/errors/distribution/get_buckets.test.ts b/x-pack/plugins/apm/server/routes/errors/distribution/get_buckets.test.ts index 6d460e5d42d7..4a3fc6d969cd 100644 --- a/x-pack/plugins/apm/server/routes/errors/distribution/get_buckets.test.ts +++ b/x-pack/plugins/apm/server/routes/errors/distribution/get_buckets.test.ts @@ -6,7 +6,6 @@ */ import { getBuckets } from './get_buckets'; -import { APMConfig } from '../../..'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; describe('get buckets', () => { @@ -29,30 +28,9 @@ describe('get buckets', () => { serviceName: 'myServiceName', bucketSize: 10, kuery: '', - setup: { - apmEventClient: { - search: clientSpy, - } as any, - internalClient: { - search: clientSpy, - } as any, - config: new Proxy( - {}, - { - get: () => 'myIndex', - } - ) as APMConfig, - indices: { - sourcemap: 'apm-*', - error: 'apm-*', - onboarding: 'apm-*', - span: 'apm-*', - transaction: 'apm-*', - metric: 'apm-*', - apmAgentConfigurationIndex: '.apm-agent-configuration', - apmCustomLinkIndex: '.apm-custom-link', - }, - }, + apmEventClient: { + search: clientSpy, + } as any, start: 1528113600000, end: 1528977600000, }); diff --git a/x-pack/plugins/apm/server/routes/errors/distribution/get_buckets.ts b/x-pack/plugins/apm/server/routes/errors/distribution/get_buckets.ts index bd1b070da90d..770305df2aab 100644 --- a/x-pack/plugins/apm/server/routes/errors/distribution/get_buckets.ts +++ b/x-pack/plugins/apm/server/routes/errors/distribution/get_buckets.ts @@ -16,7 +16,7 @@ import { SERVICE_NAME, } from '../../../../common/elasticsearch_fieldnames'; import { environmentQuery } from '../../../../common/utils/environment_query'; -import { Setup } from '../../../lib/helpers/setup_request'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; export async function getBuckets({ environment, @@ -24,7 +24,7 @@ export async function getBuckets({ serviceName, groupId, bucketSize, - setup, + apmEventClient, start, end, }: { @@ -33,12 +33,10 @@ export async function getBuckets({ serviceName: string; groupId?: string; bucketSize: number; - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; }) { - const { apmEventClient } = setup; - const params = { apm: { events: [ProcessorEvent.error], diff --git a/x-pack/plugins/apm/server/routes/errors/distribution/get_distribution.ts b/x-pack/plugins/apm/server/routes/errors/distribution/get_distribution.ts index f4df8ab5b7df..9d7116a03226 100644 --- a/x-pack/plugins/apm/server/routes/errors/distribution/get_distribution.ts +++ b/x-pack/plugins/apm/server/routes/errors/distribution/get_distribution.ts @@ -6,10 +6,10 @@ */ import { offsetPreviousPeriodCoordinates } from '../../../../common/utils/offset_previous_period_coordinate'; -import { Setup } from '../../../lib/helpers/setup_request'; import { BUCKET_TARGET_COUNT } from '../../transactions/constants'; import { getBuckets } from './get_buckets'; import { getOffsetInMs } from '../../../../common/utils/get_offset_in_ms'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; function getBucketSize({ start, end }: { start: number; end: number }) { return Math.floor((end - start) / BUCKET_TARGET_COUNT); @@ -20,7 +20,7 @@ export async function getErrorDistribution({ kuery, serviceName, groupId, - setup, + apmEventClient, start, end, offset, @@ -29,7 +29,7 @@ export async function getErrorDistribution({ kuery: string; serviceName: string; groupId?: string; - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; offset?: string; @@ -50,7 +50,7 @@ export async function getErrorDistribution({ kuery, serviceName, groupId, - setup, + apmEventClient, bucketSize, }; const currentPeriodPromise = getBuckets({ diff --git a/x-pack/plugins/apm/server/routes/errors/distribution/queries.test.ts b/x-pack/plugins/apm/server/routes/errors/distribution/queries.test.ts index bda8abc1659e..cbb606b8a0c9 100644 --- a/x-pack/plugins/apm/server/routes/errors/distribution/queries.test.ts +++ b/x-pack/plugins/apm/server/routes/errors/distribution/queries.test.ts @@ -20,10 +20,10 @@ describe('error distribution queries', () => { }); it('fetches an error distribution', async () => { - mock = await inspectSearchParams((setup) => + mock = await inspectSearchParams((setup, apmEventClient) => getErrorDistribution({ serviceName: 'serviceName', - setup, + apmEventClient, environment: ENVIRONMENT_ALL.value, kuery: '', start: 0, @@ -35,11 +35,11 @@ describe('error distribution queries', () => { }); it('fetches an error distribution with a group id', async () => { - mock = await inspectSearchParams((setup) => + mock = await inspectSearchParams((setup, apmEventClient) => getErrorDistribution({ serviceName: 'serviceName', groupId: 'foo', - setup, + apmEventClient, environment: ENVIRONMENT_ALL.value, kuery: '', start: 0, diff --git a/x-pack/plugins/apm/server/routes/errors/erroneous_transactions/get_top_erroneous_transactions.ts b/x-pack/plugins/apm/server/routes/errors/erroneous_transactions/get_top_erroneous_transactions.ts index d126057f515b..70ee012635b0 100644 --- a/x-pack/plugins/apm/server/routes/errors/erroneous_transactions/get_top_erroneous_transactions.ts +++ b/x-pack/plugins/apm/server/routes/errors/erroneous_transactions/get_top_erroneous_transactions.ts @@ -26,16 +26,16 @@ import { TRANSACTION_TYPE, } from '../../../../common/elasticsearch_fieldnames'; import { environmentQuery } from '../../../../common/utils/environment_query'; -import { Setup } from '../../../lib/helpers/setup_request'; import { getBucketSize } from '../../../lib/helpers/get_bucket_size'; import { getOffsetInMs } from '../../../../common/utils/get_offset_in_ms'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; async function getTopErroneousTransactions({ environment, kuery, serviceName, groupId, - setup, + apmEventClient, start, end, numBuckets, @@ -45,14 +45,12 @@ async function getTopErroneousTransactions({ kuery: string; serviceName: string; groupId: string; - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; numBuckets: number; offset?: string; }) { - const { apmEventClient } = setup; - const { startWithOffset, endWithOffset, offsetInMs } = getOffsetInMs({ start, end, @@ -132,7 +130,7 @@ async function getTopErroneousTransactions({ export async function getTopErroneousTransactionsPeriods({ kuery, serviceName, - setup, + apmEventClient, numBuckets, groupId, environment, @@ -142,7 +140,7 @@ export async function getTopErroneousTransactionsPeriods({ }: { kuery: string; serviceName: string; - setup: Setup; + apmEventClient: APMEventClient; numBuckets: number; groupId: string; environment: string; @@ -155,7 +153,7 @@ export async function getTopErroneousTransactionsPeriods({ environment, kuery, serviceName, - setup, + apmEventClient, numBuckets, groupId, start, @@ -166,7 +164,7 @@ export async function getTopErroneousTransactionsPeriods({ environment, kuery, serviceName, - setup, + apmEventClient, numBuckets, groupId, start, diff --git a/x-pack/plugins/apm/server/routes/errors/get_error_groups/get_error_group_detailed_statistics.ts b/x-pack/plugins/apm/server/routes/errors/get_error_groups/get_error_group_detailed_statistics.ts index 325b134902c8..30720c2d799c 100644 --- a/x-pack/plugins/apm/server/routes/errors/get_error_groups/get_error_group_detailed_statistics.ts +++ b/x-pack/plugins/apm/server/routes/errors/get_error_groups/get_error_group_detailed_statistics.ts @@ -20,13 +20,13 @@ import { } from '../../../../common/elasticsearch_fieldnames'; import { environmentQuery } from '../../../../common/utils/environment_query'; import { getBucketSize } from '../../../lib/helpers/get_bucket_size'; -import { Setup } from '../../../lib/helpers/setup_request'; import { getOffsetInMs } from '../../../../common/utils/get_offset_in_ms'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; export async function getErrorGroupDetailedStatistics({ kuery, serviceName, - setup, + apmEventClient, numBuckets, groupIds, environment, @@ -36,7 +36,7 @@ export async function getErrorGroupDetailedStatistics({ }: { kuery: string; serviceName: string; - setup: Setup; + apmEventClient: APMEventClient; numBuckets: number; groupIds: string[]; environment: string; @@ -44,8 +44,6 @@ export async function getErrorGroupDetailedStatistics({ end: number; offset?: string; }): Promise> { - const { apmEventClient } = setup; - const { startWithOffset, endWithOffset } = getOffsetInMs({ start, end, @@ -124,7 +122,7 @@ export async function getErrorGroupDetailedStatistics({ export async function getErrorGroupPeriods({ kuery, serviceName, - setup, + apmEventClient, numBuckets, groupIds, environment, @@ -134,7 +132,7 @@ export async function getErrorGroupPeriods({ }: { kuery: string; serviceName: string; - setup: Setup; + apmEventClient: APMEventClient; numBuckets: number; groupIds: string[]; environment: string; @@ -146,7 +144,7 @@ export async function getErrorGroupPeriods({ environment, kuery, serviceName, - setup, + apmEventClient, numBuckets, groupIds, }; diff --git a/x-pack/plugins/apm/server/routes/errors/get_error_groups/get_error_group_main_statistics.ts b/x-pack/plugins/apm/server/routes/errors/get_error_groups/get_error_group_main_statistics.ts index c27d30a253f2..f4d2fcff4a40 100644 --- a/x-pack/plugins/apm/server/routes/errors/get_error_groups/get_error_group_main_statistics.ts +++ b/x-pack/plugins/apm/server/routes/errors/get_error_groups/get_error_group_main_statistics.ts @@ -25,12 +25,12 @@ import { } from '../../../../common/elasticsearch_fieldnames'; import { environmentQuery } from '../../../../common/utils/environment_query'; import { getErrorName } from '../../../lib/helpers/get_error_name'; -import { Setup } from '../../../lib/helpers/setup_request'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; export async function getErrorGroupMainStatistics({ kuery, serviceName, - setup, + apmEventClient, environment, sortField, sortDirection = 'desc', @@ -42,7 +42,7 @@ export async function getErrorGroupMainStatistics({ }: { kuery: string; serviceName: string; - setup: Setup; + apmEventClient: APMEventClient; environment: string; sortField?: string; sortDirection?: 'asc' | 'desc'; @@ -52,8 +52,6 @@ export async function getErrorGroupMainStatistics({ transactionName?: string; transactionType?: string; }) { - const { apmEventClient } = setup; - // sort buckets by last occurrence of error const sortByLatestOccurrence = sortField === 'lastSeen'; diff --git a/x-pack/plugins/apm/server/routes/errors/get_error_groups/get_error_group_sample.ts b/x-pack/plugins/apm/server/routes/errors/get_error_groups/get_error_group_sample.ts index d5a8b8a5ac65..744db1c9c21b 100644 --- a/x-pack/plugins/apm/server/routes/errors/get_error_groups/get_error_group_sample.ts +++ b/x-pack/plugins/apm/server/routes/errors/get_error_groups/get_error_group_sample.ts @@ -15,14 +15,14 @@ import { } from '../../../../common/elasticsearch_fieldnames'; import { environmentQuery } from '../../../../common/utils/environment_query'; import { getTransaction } from '../../transactions/get_transaction'; -import { Setup } from '../../../lib/helpers/setup_request'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; export async function getErrorGroupSample({ environment, kuery, serviceName, groupId, - setup, + apmEventClient, start, end, }: { @@ -30,12 +30,10 @@ export async function getErrorGroupSample({ kuery: string; serviceName: string; groupId: string; - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; }) { - const { apmEventClient } = setup; - const params = { apm: { events: [ProcessorEvent.error as const], @@ -72,7 +70,7 @@ export async function getErrorGroupSample({ transaction = await getTransaction({ transactionId, traceId, - setup, + apmEventClient, start, end, }); diff --git a/x-pack/plugins/apm/server/routes/errors/queries.test.ts b/x-pack/plugins/apm/server/routes/errors/queries.test.ts index 7cb84db0d786..080f175c2d5e 100644 --- a/x-pack/plugins/apm/server/routes/errors/queries.test.ts +++ b/x-pack/plugins/apm/server/routes/errors/queries.test.ts @@ -21,11 +21,11 @@ describe('error queries', () => { }); it('fetches a single error group', async () => { - mock = await inspectSearchParams((setup) => + mock = await inspectSearchParams((setup, apmEventClient) => getErrorGroupSample({ groupId: 'groupId', serviceName: 'serviceName', - setup, + apmEventClient, environment: ENVIRONMENT_ALL.value, kuery: '', start: 0, @@ -37,12 +37,12 @@ describe('error queries', () => { }); it('fetches multiple error groups', async () => { - mock = await inspectSearchParams((setup) => + mock = await inspectSearchParams((setup, apmEventClient) => getErrorGroupMainStatistics({ sortDirection: 'asc', sortField: 'foo', serviceName: 'serviceName', - setup, + apmEventClient, environment: ENVIRONMENT_ALL.value, kuery: '', start: 0, @@ -54,12 +54,12 @@ describe('error queries', () => { }); it('fetches multiple error groups when sortField = lastSeen', async () => { - mock = await inspectSearchParams((setup) => + mock = await inspectSearchParams((setup, apmEventClient) => getErrorGroupMainStatistics({ sortDirection: 'asc', sortField: 'lastSeen', serviceName: 'serviceName', - setup, + apmEventClient, environment: ENVIRONMENT_ALL.value, kuery: '', start: 0, diff --git a/x-pack/plugins/apm/server/routes/errors/route.ts b/x-pack/plugins/apm/server/routes/errors/route.ts index 17faea2765da..5bb3bda954ee 100644 --- a/x-pack/plugins/apm/server/routes/errors/route.ts +++ b/x-pack/plugins/apm/server/routes/errors/route.ts @@ -9,13 +9,13 @@ import { jsonRt, toNumberRt } from '@kbn/io-ts-utils'; import * as t from 'io-ts'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { getErrorDistribution } from './distribution/get_distribution'; -import { setupRequest } from '../../lib/helpers/setup_request'; import { environmentRt, kueryRt, rangeRt } from '../default_api_types'; import { getErrorGroupMainStatistics } from './get_error_groups/get_error_group_main_statistics'; import { getErrorGroupPeriods } from './get_error_groups/get_error_group_detailed_statistics'; import { getErrorGroupSample } from './get_error_groups/get_error_group_sample'; import { offsetRt } from '../../../common/comparison_rt'; import { getTopErroneousTransactionsPeriods } from './erroneous_transactions/get_top_erroneous_transactions'; +import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; const errorsMainStatisticsRoute = createApmServerRoute({ endpoint: @@ -49,7 +49,7 @@ const errorsMainStatisticsRoute = createApmServerRoute({ }>; }> => { const { params } = resources; - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { serviceName } = params.path; const { environment, kuery, sortField, sortDirection, start, end } = params.query; @@ -60,7 +60,7 @@ const errorsMainStatisticsRoute = createApmServerRoute({ serviceName, sortField, sortDirection, - setup, + apmEventClient, start, end, }); @@ -102,7 +102,7 @@ const errorsMainStatisticsByTransactionNameRoute = createApmServerRoute({ }>; }> => { const { params } = resources; - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { serviceName } = params.path; const { environment, @@ -118,7 +118,7 @@ const errorsMainStatisticsByTransactionNameRoute = createApmServerRoute({ environment, kuery, serviceName, - setup, + apmEventClient, start, end, maxNumberOfErrorGroups, @@ -164,7 +164,7 @@ const errorsDetailedStatisticsRoute = createApmServerRoute({ groupId: string; }>; }> => { - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { params } = resources; const { @@ -177,7 +177,7 @@ const errorsDetailedStatisticsRoute = createApmServerRoute({ environment, kuery, serviceName, - setup, + apmEventClient, numBuckets, groupIds, start, @@ -207,7 +207,7 @@ const errorGroupsRoute = createApmServerRoute({ occurrencesCount: number; }> => { const { params } = resources; - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { serviceName, groupId } = params.path; const { environment, kuery, start, end } = params.query; @@ -216,7 +216,7 @@ const errorGroupsRoute = createApmServerRoute({ groupId, kuery, serviceName, - setup, + apmEventClient, start, end, }); @@ -250,7 +250,7 @@ const errorDistributionRoute = createApmServerRoute({ }>; bucketSize: number; }> => { - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { params } = resources; const { serviceName } = params.path; const { environment, kuery, groupId, start, end, offset } = params.query; @@ -259,7 +259,7 @@ const errorDistributionRoute = createApmServerRoute({ kuery, serviceName, groupId, - setup, + apmEventClient, start, end, offset, @@ -298,7 +298,7 @@ const topErroneousTransactionsRoute = createApmServerRoute({ }>; }> => { const { params } = resources; - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { path: { serviceName, groupId }, @@ -310,7 +310,7 @@ const topErroneousTransactionsRoute = createApmServerRoute({ groupId, kuery, serviceName, - setup, + apmEventClient, start, end, numBuckets, diff --git a/x-pack/plugins/apm/server/routes/event_metadata/route.ts b/x-pack/plugins/apm/server/routes/event_metadata/route.ts index a057be53ef43..02709d2c499e 100644 --- a/x-pack/plugins/apm/server/routes/event_metadata/route.ts +++ b/x-pack/plugins/apm/server/routes/event_metadata/route.ts @@ -9,7 +9,7 @@ import * as t from 'io-ts'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { getEventMetadata } from './get_event_metadata'; import { processorEventRt } from '../../../common/processor_event'; -import { setupRequest } from '../../lib/helpers/setup_request'; +import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; const eventMetadataRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/event_metadata/{processorEvent}/{id}', @@ -23,14 +23,14 @@ const eventMetadataRoute = createApmServerRoute({ handler: async ( resources ): Promise<{ metadata: Partial> }> => { - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { path: { processorEvent, id }, } = resources.params; const metadata = await getEventMetadata({ - apmEventClient: setup.apmEventClient, + apmEventClient, processorEvent, id, }); diff --git a/x-pack/plugins/apm/server/routes/fallback_to_transactions/route.ts b/x-pack/plugins/apm/server/routes/fallback_to_transactions/route.ts index 60355b5447b8..2e056df7ee89 100644 --- a/x-pack/plugins/apm/server/routes/fallback_to_transactions/route.ts +++ b/x-pack/plugins/apm/server/routes/fallback_to_transactions/route.ts @@ -10,6 +10,7 @@ import { getIsUsingTransactionEvents } from '../../lib/helpers/transactions/get_ import { setupRequest } from '../../lib/helpers/setup_request'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { kueryRt, rangeRt } from '../default_api_types'; +import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; const fallbackToTransactionsRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/fallback_to_transactions', @@ -18,7 +19,10 @@ const fallbackToTransactionsRoute = createApmServerRoute({ }), options: { tags: ['access:apm'] }, handler: async (resources): Promise<{ fallbackToTransactions: boolean }> => { - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { params: { query: { kuery, start, end }, @@ -26,7 +30,8 @@ const fallbackToTransactionsRoute = createApmServerRoute({ } = resources; return { fallbackToTransactions: await getIsUsingTransactionEvents({ - setup, + config: setup.config, + apmEventClient, kuery, start, end, diff --git a/x-pack/plugins/apm/server/routes/historical_data/has_historical_agent_data.ts b/x-pack/plugins/apm/server/routes/historical_data/has_historical_agent_data.ts index e269e036396f..befd25abb22f 100644 --- a/x-pack/plugins/apm/server/routes/historical_data/has_historical_agent_data.ts +++ b/x-pack/plugins/apm/server/routes/historical_data/has_historical_agent_data.ts @@ -6,12 +6,10 @@ */ import { ProcessorEvent } from '@kbn/observability-plugin/common'; -import { Setup } from '../../lib/helpers/setup_request'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; // Note: this logic is duplicated in tutorials/apm/envs/on_prem -export async function hasHistoricalAgentData(setup: Setup) { - const { apmEventClient } = setup; - +export async function hasHistoricalAgentData(apmEventClient: APMEventClient) { const params = { terminate_after: 1, apm: { diff --git a/x-pack/plugins/apm/server/routes/historical_data/route.ts b/x-pack/plugins/apm/server/routes/historical_data/route.ts index e9836df61acb..204af4bb05b0 100644 --- a/x-pack/plugins/apm/server/routes/historical_data/route.ts +++ b/x-pack/plugins/apm/server/routes/historical_data/route.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { setupRequest } from '../../lib/helpers/setup_request'; +import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { hasHistoricalAgentData } from './has_historical_agent_data'; @@ -13,8 +13,8 @@ const hasDataRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/has_data', options: { tags: ['access:apm'] }, handler: async (resources): Promise<{ hasData: boolean }> => { - const setup = await setupRequest(resources); - const hasData = await hasHistoricalAgentData(setup); + const apmEventClient = await getApmEventClient(resources); + const hasData = await hasHistoricalAgentData(apmEventClient); return { hasData }; }, }); diff --git a/x-pack/plugins/apm/server/routes/infrastructure/get_infrastructure_data.ts b/x-pack/plugins/apm/server/routes/infrastructure/get_infrastructure_data.ts index 0d79901efbc7..73c9732335c1 100644 --- a/x-pack/plugins/apm/server/routes/infrastructure/get_infrastructure_data.ts +++ b/x-pack/plugins/apm/server/routes/infrastructure/get_infrastructure_data.ts @@ -7,7 +7,6 @@ import { rangeQuery, kqlQuery } from '@kbn/observability-plugin/server'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; -import { Setup } from '../../lib/helpers/setup_request'; import { environmentQuery } from '../../../common/utils/environment_query'; import { SERVICE_NAME, @@ -15,24 +14,23 @@ import { HOST_HOSTNAME, KUBERNETES_POD_NAME, } from '../../../common/elasticsearch_fieldnames'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export const getInfrastructureData = async ({ kuery, serviceName, environment, - setup, + apmEventClient, start, end, }: { kuery: string; serviceName: string; environment: string; - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; }) => { - const { apmEventClient } = setup; - const response = await apmEventClient.search('get_service_infrastructure', { apm: { events: [ProcessorEvent.metric], diff --git a/x-pack/plugins/apm/server/routes/infrastructure/route.ts b/x-pack/plugins/apm/server/routes/infrastructure/route.ts index 678f380bc4fd..151ed589396c 100644 --- a/x-pack/plugins/apm/server/routes/infrastructure/route.ts +++ b/x-pack/plugins/apm/server/routes/infrastructure/route.ts @@ -6,7 +6,7 @@ */ import * as t from 'io-ts'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; -import { setupRequest } from '../../lib/helpers/setup_request'; +import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; import { environmentRt, kueryRt, rangeRt } from '../default_api_types'; import { getInfrastructureData } from './get_infrastructure_data'; import { getContainerHostNames } from './get_host_names'; @@ -29,7 +29,7 @@ const infrastructureRoute = createApmServerRoute({ hostNames: string[]; podNames: string[]; }> => { - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const infraMetricsClient = createInfraMetricsClient(resources); const { params } = resources; @@ -40,7 +40,7 @@ const infrastructureRoute = createApmServerRoute({ } = params; const infrastructureData = await getInfrastructureData({ - setup, + apmEventClient, serviceName, environment, kuery, diff --git a/x-pack/plugins/apm/server/routes/latency_distribution/get_overall_latency_distribution.ts b/x-pack/plugins/apm/server/routes/latency_distribution/get_overall_latency_distribution.ts index 206deb8a32d1..39ff3cf622df 100644 --- a/x-pack/plugins/apm/server/routes/latency_distribution/get_overall_latency_distribution.ts +++ b/x-pack/plugins/apm/server/routes/latency_distribution/get_overall_latency_distribution.ts @@ -7,21 +7,17 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { Environment } from '../../../common/environment_rt'; - -import { Setup } from '../../lib/helpers/setup_request'; - import { withApmSpan } from '../../utils/with_apm_span'; - import { fetchDurationRanges } from '../correlations/queries/fetch_duration_ranges'; import { fetchDurationHistogramRangeSteps } from '../correlations/queries/fetch_duration_histogram_range_steps'; - import { getPercentileThresholdValue } from './get_percentile_threshold_value'; import type { OverallLatencyDistributionResponse } from './types'; import { LatencyDistributionChartType } from '../../../common/latency_distribution_chart_types'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getOverallLatencyDistribution({ chartType, - setup, + apmEventClient, start, end, environment, @@ -33,7 +29,7 @@ export async function getOverallLatencyDistribution({ searchMetrics, }: { chartType: LatencyDistributionChartType; - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; environment: Environment; @@ -51,7 +47,7 @@ export async function getOverallLatencyDistribution({ overallLatencyDistribution.percentileThresholdValue = await getPercentileThresholdValue({ chartType, - setup, + apmEventClient, start, end, environment, @@ -70,7 +66,7 @@ export async function getOverallLatencyDistribution({ const { durationMin, durationMax, rangeSteps } = await fetchDurationHistogramRangeSteps({ chartType, - setup, + apmEventClient, start, end, environment, @@ -88,7 +84,7 @@ export async function getOverallLatencyDistribution({ // #3: get histogram chart data const { totalDocCount, durationRanges } = await fetchDurationRanges({ chartType, - setup, + apmEventClient, start, end, environment, diff --git a/x-pack/plugins/apm/server/routes/latency_distribution/get_percentile_threshold_value.ts b/x-pack/plugins/apm/server/routes/latency_distribution/get_percentile_threshold_value.ts index e24fefa57288..bb6b65c4a173 100644 --- a/x-pack/plugins/apm/server/routes/latency_distribution/get_percentile_threshold_value.ts +++ b/x-pack/plugins/apm/server/routes/latency_distribution/get_percentile_threshold_value.ts @@ -7,11 +7,11 @@ import { CommonCorrelationsQueryParams } from '../../../common/correlations/types'; import { LatencyDistributionChartType } from '../../../common/latency_distribution_chart_types'; -import { Setup } from '../../lib/helpers/setup_request'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; import { fetchDurationPercentiles } from '../correlations/queries/fetch_duration_percentiles'; export async function getPercentileThresholdValue({ - setup, + apmEventClient, chartType, start, end, @@ -21,13 +21,13 @@ export async function getPercentileThresholdValue({ percentileThreshold, searchMetrics, }: CommonCorrelationsQueryParams & { - setup: Setup; + apmEventClient: APMEventClient; chartType: LatencyDistributionChartType; percentileThreshold: number; searchMetrics: boolean; }) { const durationPercentiles = await fetchDurationPercentiles({ - setup, + apmEventClient, chartType, start, end, diff --git a/x-pack/plugins/apm/server/routes/latency_distribution/route.ts b/x-pack/plugins/apm/server/routes/latency_distribution/route.ts index ac0e65abe315..d2e6dee2795e 100644 --- a/x-pack/plugins/apm/server/routes/latency_distribution/route.ts +++ b/x-pack/plugins/apm/server/routes/latency_distribution/route.ts @@ -23,6 +23,7 @@ import { latencyDistributionChartTypeRt, LatencyDistributionChartType, } from '../../../common/latency_distribution_chart_types'; +import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; const latencyOverallTransactionDistributionRoute = createApmServerRoute({ endpoint: 'POST /internal/apm/latency/overall_distribution/transactions', @@ -54,7 +55,10 @@ const latencyOverallTransactionDistributionRoute = createApmServerRoute({ handler: async ( resources ): Promise => { - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { environment, @@ -75,7 +79,8 @@ const latencyOverallTransactionDistributionRoute = createApmServerRoute({ const searchAggregatedTransactions = chartType === LatencyDistributionChartType.transactionLatency ? await getSearchTransactionsEvents({ - ...setup, + config: setup.config, + apmEventClient, kuery, start, end, @@ -83,7 +88,7 @@ const latencyOverallTransactionDistributionRoute = createApmServerRoute({ : false; return getOverallLatencyDistribution({ - setup, + apmEventClient, chartType, environment, kuery, diff --git a/x-pack/plugins/apm/server/routes/metrics/by_agent/default.ts b/x-pack/plugins/apm/server/routes/metrics/by_agent/default.ts index b4e95d5217da..d228cdd35298 100644 --- a/x-pack/plugins/apm/server/routes/metrics/by_agent/default.ts +++ b/x-pack/plugins/apm/server/routes/metrics/by_agent/default.ts @@ -5,7 +5,8 @@ * 2.0. */ -import { Setup } from '../../../lib/helpers/setup_request'; +import { APMConfig } from '../../..'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; import { getCPUChartData } from './shared/cpu'; import { getMemoryChartData } from './shared/memory'; @@ -13,19 +14,37 @@ export function getDefaultMetricsCharts({ environment, kuery, serviceName, - setup, + config, + apmEventClient, start, end, }: { environment: string; kuery: string; serviceName: string; - setup: Setup; + config: APMConfig; + apmEventClient: APMEventClient; start: number; end: number; }) { return Promise.all([ - getCPUChartData({ environment, kuery, setup, serviceName, start, end }), - getMemoryChartData({ environment, kuery, setup, serviceName, start, end }), + getCPUChartData({ + environment, + kuery, + config, + apmEventClient, + serviceName, + start, + end, + }), + getMemoryChartData({ + environment, + kuery, + config, + apmEventClient, + serviceName, + start, + end, + }), ]); } diff --git a/x-pack/plugins/apm/server/routes/metrics/by_agent/java/gc/fetch_and_transform_gc_metrics.test.ts b/x-pack/plugins/apm/server/routes/metrics/by_agent/java/gc/fetch_and_transform_gc_metrics.test.ts index 8b3fcbfe5ce8..d6e60cf713d5 100644 --- a/x-pack/plugins/apm/server/routes/metrics/by_agent/java/gc/fetch_and_transform_gc_metrics.test.ts +++ b/x-pack/plugins/apm/server/routes/metrics/by_agent/java/gc/fetch_and_transform_gc_metrics.test.ts @@ -9,6 +9,7 @@ import { METRIC_JAVA_GC_COUNT, METRIC_JAVA_GC_TIME, } from '../../../../../../common/elasticsearch_fieldnames'; +import { APMEventClient } from '../../../../../lib/helpers/create_es_client/create_apm_event_client'; import { Setup } from '../../../../../lib/helpers/setup_request'; import { ChartBase } from '../../../types'; @@ -50,9 +51,11 @@ describe('fetchAndTransformGcMetrics', () => { }, }; const setup = { - apmEventClient: { search: () => Promise.resolve(response) }, config: { 'xpack.gc.metricsInterval': 0 }, } as unknown as Setup; + const apmEventClient = { + search: () => Promise.resolve(response), + } as unknown as APMEventClient; const fieldName = METRIC_JAVA_GC_TIME; const { series } = await fetchAndTransformGcMetrics({ @@ -61,7 +64,8 @@ describe('fetchAndTransformGcMetrics', () => { fieldName, kuery: '', operationName: 'test operation name', - setup, + config: setup.config, + apmEventClient, serviceName: 'test service name', start: 1633456140000, end: 1633457078105, @@ -110,9 +114,11 @@ describe('fetchAndTransformGcMetrics', () => { }, }; const setup = { - apmEventClient: { search: () => Promise.resolve(response) }, config: { 'xpack.gc.metricsInterval': 0 }, } as unknown as Setup; + const apmEventClient = { + search: () => Promise.resolve(response), + } as unknown as APMEventClient; const fieldName = METRIC_JAVA_GC_COUNT; const { series } = await fetchAndTransformGcMetrics({ @@ -121,7 +127,8 @@ describe('fetchAndTransformGcMetrics', () => { fieldName, kuery: '', operationName: 'test operation name', - setup, + config: setup.config, + apmEventClient, serviceName: 'test service name', start: 1633456140000, end: 1633457078105, diff --git a/x-pack/plugins/apm/server/routes/metrics/by_agent/java/gc/fetch_and_transform_gc_metrics.ts b/x-pack/plugins/apm/server/routes/metrics/by_agent/java/gc/fetch_and_transform_gc_metrics.ts index 08f0cf52d00c..cb97295bbca0 100644 --- a/x-pack/plugins/apm/server/routes/metrics/by_agent/java/gc/fetch_and_transform_gc_metrics.ts +++ b/x-pack/plugins/apm/server/routes/metrics/by_agent/java/gc/fetch_and_transform_gc_metrics.ts @@ -10,7 +10,6 @@ import { euiLightVars as theme } from '@kbn/ui-theme'; import { kqlQuery, rangeQuery } from '@kbn/observability-plugin/server'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; import { isFiniteNumber } from '../../../../../../common/utils/is_finite_number'; -import { Setup } from '../../../../../lib/helpers/setup_request'; import { getMetricsDateHistogramParams } from '../../../../../lib/helpers/metrics'; import { ChartBase } from '../../../types'; @@ -28,11 +27,14 @@ import { environmentQuery, serviceNodeNameQuery, } from '../../../../../../common/utils/environment_query'; +import { APMConfig } from '../../../../..'; +import { APMEventClient } from '../../../../../lib/helpers/create_es_client/create_apm_event_client'; export async function fetchAndTransformGcMetrics({ environment, kuery, - setup, + config, + apmEventClient, serviceName, serviceNodeName, chartBase, @@ -43,7 +45,8 @@ export async function fetchAndTransformGcMetrics({ }: { environment: string; kuery: string; - setup: Setup; + config: APMConfig; + apmEventClient: APMEventClient; serviceName: string; serviceNodeName?: string; start: number; @@ -52,8 +55,6 @@ export async function fetchAndTransformGcMetrics({ fieldName: typeof METRIC_JAVA_GC_COUNT | typeof METRIC_JAVA_GC_TIME; operationName: string; }) { - const { apmEventClient, config } = setup; - const { bucketSize } = getBucketSize({ start, end }); // GC rate and time are reported by the agents as monotonically diff --git a/x-pack/plugins/apm/server/routes/metrics/by_agent/java/gc/get_gc_rate_chart.ts b/x-pack/plugins/apm/server/routes/metrics/by_agent/java/gc/get_gc_rate_chart.ts index 0476025594b2..6fac756ccef5 100644 --- a/x-pack/plugins/apm/server/routes/metrics/by_agent/java/gc/get_gc_rate_chart.ts +++ b/x-pack/plugins/apm/server/routes/metrics/by_agent/java/gc/get_gc_rate_chart.ts @@ -8,9 +8,10 @@ import { euiLightVars as theme } from '@kbn/ui-theme'; import { i18n } from '@kbn/i18n'; import { METRIC_JAVA_GC_COUNT } from '../../../../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../../../../lib/helpers/setup_request'; import { fetchAndTransformGcMetrics } from './fetch_and_transform_gc_metrics'; import { ChartBase } from '../../../types'; +import { APMConfig } from '../../../../..'; +import { APMEventClient } from '../../../../../lib/helpers/create_es_client/create_apm_event_client'; const series = { [METRIC_JAVA_GC_COUNT]: { @@ -34,7 +35,8 @@ const chartBase: ChartBase = { function getGcRateChart({ environment, kuery, - setup, + config, + apmEventClient, serviceName, serviceNodeName, start, @@ -42,7 +44,8 @@ function getGcRateChart({ }: { environment: string; kuery: string; - setup: Setup; + config: APMConfig; + apmEventClient: APMEventClient; serviceName: string; serviceNodeName?: string; start: number; @@ -51,7 +54,8 @@ function getGcRateChart({ return fetchAndTransformGcMetrics({ environment, kuery, - setup, + config, + apmEventClient, serviceName, serviceNodeName, start, diff --git a/x-pack/plugins/apm/server/routes/metrics/by_agent/java/gc/get_gc_time_chart.ts b/x-pack/plugins/apm/server/routes/metrics/by_agent/java/gc/get_gc_time_chart.ts index b1ef7b5e106f..fed63945800d 100644 --- a/x-pack/plugins/apm/server/routes/metrics/by_agent/java/gc/get_gc_time_chart.ts +++ b/x-pack/plugins/apm/server/routes/metrics/by_agent/java/gc/get_gc_time_chart.ts @@ -8,9 +8,10 @@ import { euiLightVars as theme } from '@kbn/ui-theme'; import { i18n } from '@kbn/i18n'; import { METRIC_JAVA_GC_TIME } from '../../../../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../../../../lib/helpers/setup_request'; import { fetchAndTransformGcMetrics } from './fetch_and_transform_gc_metrics'; import { ChartBase } from '../../../types'; +import { APMEventClient } from '../../../../../lib/helpers/create_es_client/create_apm_event_client'; +import { APMConfig } from '../../../../..'; const series = { [METRIC_JAVA_GC_TIME]: { @@ -34,7 +35,8 @@ const chartBase: ChartBase = { function getGcTimeChart({ environment, kuery, - setup, + config, + apmEventClient, serviceName, serviceNodeName, start, @@ -42,7 +44,8 @@ function getGcTimeChart({ }: { environment: string; kuery: string; - setup: Setup; + config: APMConfig; + apmEventClient: APMEventClient; serviceName: string; serviceNodeName?: string; start: number; @@ -51,7 +54,8 @@ function getGcTimeChart({ return fetchAndTransformGcMetrics({ environment, kuery, - setup, + config, + apmEventClient, serviceName, serviceNodeName, start, diff --git a/x-pack/plugins/apm/server/routes/metrics/by_agent/java/heap_memory/index.ts b/x-pack/plugins/apm/server/routes/metrics/by_agent/java/heap_memory/index.ts index d57dfb184ca8..86c0b0af2605 100644 --- a/x-pack/plugins/apm/server/routes/metrics/by_agent/java/heap_memory/index.ts +++ b/x-pack/plugins/apm/server/routes/metrics/by_agent/java/heap_memory/index.ts @@ -13,10 +13,11 @@ import { METRIC_JAVA_HEAP_MEMORY_USED, AGENT_NAME, } from '../../../../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../../../../lib/helpers/setup_request'; import { fetchAndTransformMetrics } from '../../../fetch_and_transform_metrics'; import { ChartBase } from '../../../types'; import { JAVA_AGENT_NAMES } from '../../../../../../common/agent_name'; +import { APMConfig } from '../../../../..'; +import { APMEventClient } from '../../../../../lib/helpers/create_es_client/create_apm_event_client'; const series = { heapMemoryUsed: { @@ -55,7 +56,8 @@ const chartBase: ChartBase = { export function getHeapMemoryChart({ environment, kuery, - setup, + config, + apmEventClient, serviceName, serviceNodeName, start, @@ -63,7 +65,8 @@ export function getHeapMemoryChart({ }: { environment: string; kuery: string; - setup: Setup; + config: APMConfig; + apmEventClient: APMEventClient; serviceName: string; serviceNodeName?: string; start: number; @@ -72,7 +75,8 @@ export function getHeapMemoryChart({ return fetchAndTransformMetrics({ environment, kuery, - setup, + config, + apmEventClient, serviceName, serviceNodeName, start, diff --git a/x-pack/plugins/apm/server/routes/metrics/by_agent/java/index.ts b/x-pack/plugins/apm/server/routes/metrics/by_agent/java/index.ts index 5250884ed5e4..558e37af1cff 100644 --- a/x-pack/plugins/apm/server/routes/metrics/by_agent/java/index.ts +++ b/x-pack/plugins/apm/server/routes/metrics/by_agent/java/index.ts @@ -7,18 +7,20 @@ import { withApmSpan } from '../../../../utils/with_apm_span'; import { getHeapMemoryChart } from './heap_memory'; -import { Setup } from '../../../../lib/helpers/setup_request'; import { getNonHeapMemoryChart } from './non_heap_memory'; import { getThreadCountChart } from './thread_count'; import { getCPUChartData } from '../shared/cpu'; import { getMemoryChartData } from '../shared/memory'; import { getGcRateChart } from './gc/get_gc_rate_chart'; import { getGcTimeChart } from './gc/get_gc_time_chart'; +import { APMConfig } from '../../../..'; +import { APMEventClient } from '../../../../lib/helpers/create_es_client/create_apm_event_client'; export function getJavaMetricsCharts({ environment, kuery, - setup, + config, + apmEventClient, serviceName, serviceNodeName, start, @@ -26,7 +28,8 @@ export function getJavaMetricsCharts({ }: { environment: string; kuery: string; - setup: Setup; + config: APMConfig; + apmEventClient: APMEventClient; serviceName: string; serviceNodeName?: string; start: number; @@ -36,7 +39,8 @@ export function getJavaMetricsCharts({ const options = { environment, kuery, - setup, + config, + apmEventClient, serviceName, serviceNodeName, start, diff --git a/x-pack/plugins/apm/server/routes/metrics/by_agent/java/non_heap_memory/index.ts b/x-pack/plugins/apm/server/routes/metrics/by_agent/java/non_heap_memory/index.ts index 379962d928e2..888af8d795af 100644 --- a/x-pack/plugins/apm/server/routes/metrics/by_agent/java/non_heap_memory/index.ts +++ b/x-pack/plugins/apm/server/routes/metrics/by_agent/java/non_heap_memory/index.ts @@ -13,10 +13,11 @@ import { METRIC_JAVA_NON_HEAP_MEMORY_USED, AGENT_NAME, } from '../../../../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../../../../lib/helpers/setup_request'; import { ChartBase } from '../../../types'; import { fetchAndTransformMetrics } from '../../../fetch_and_transform_metrics'; import { JAVA_AGENT_NAMES } from '../../../../../../common/agent_name'; +import { APMConfig } from '../../../../..'; +import { APMEventClient } from '../../../../../lib/helpers/create_es_client/create_apm_event_client'; const series = { nonHeapMemoryUsed: { @@ -52,7 +53,8 @@ const chartBase: ChartBase = { export async function getNonHeapMemoryChart({ environment, kuery, - setup, + config, + apmEventClient, serviceName, serviceNodeName, start, @@ -60,7 +62,8 @@ export async function getNonHeapMemoryChart({ }: { environment: string; kuery: string; - setup: Setup; + config: APMConfig; + apmEventClient: APMEventClient; serviceName: string; serviceNodeName?: string; start: number; @@ -69,7 +72,8 @@ export async function getNonHeapMemoryChart({ return fetchAndTransformMetrics({ environment, kuery, - setup, + config, + apmEventClient, serviceName, serviceNodeName, start, diff --git a/x-pack/plugins/apm/server/routes/metrics/by_agent/java/thread_count/index.ts b/x-pack/plugins/apm/server/routes/metrics/by_agent/java/thread_count/index.ts index b9a49acb3d16..74daa14438a1 100644 --- a/x-pack/plugins/apm/server/routes/metrics/by_agent/java/thread_count/index.ts +++ b/x-pack/plugins/apm/server/routes/metrics/by_agent/java/thread_count/index.ts @@ -11,10 +11,11 @@ import { METRIC_JAVA_THREAD_COUNT, AGENT_NAME, } from '../../../../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../../../../lib/helpers/setup_request'; import { ChartBase } from '../../../types'; import { fetchAndTransformMetrics } from '../../../fetch_and_transform_metrics'; import { JAVA_AGENT_NAMES } from '../../../../../../common/agent_name'; +import { APMConfig } from '../../../../..'; +import { APMEventClient } from '../../../../../lib/helpers/create_es_client/create_apm_event_client'; const series = { threadCount: { @@ -44,7 +45,8 @@ const chartBase: ChartBase = { export async function getThreadCountChart({ environment, kuery, - setup, + config, + apmEventClient, serviceName, serviceNodeName, start, @@ -52,7 +54,8 @@ export async function getThreadCountChart({ }: { environment: string; kuery: string; - setup: Setup; + config: APMConfig; + apmEventClient: APMEventClient; serviceName: string; serviceNodeName?: string; start: number; @@ -61,7 +64,8 @@ export async function getThreadCountChart({ return fetchAndTransformMetrics({ environment, kuery, - setup, + config, + apmEventClient, serviceName, serviceNodeName, start, diff --git a/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/active_instances.ts b/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/active_instances.ts deleted file mode 100644 index 02cbfc3b5704..000000000000 --- a/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/active_instances.ts +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; -import { kqlQuery, rangeQuery } from '@kbn/observability-plugin/server'; -import { euiLightVars as theme } from '@kbn/ui-theme'; -import { - SERVICE_NAME, - SERVICE_NODE_NAME, -} from '../../../../../common/elasticsearch_fieldnames'; -import { environmentQuery } from '../../../../../common/utils/environment_query'; -import { getMetricsDateHistogramParams } from '../../../../lib/helpers/metrics'; -import { Setup } from '../../../../lib/helpers/setup_request'; -import { - getDocumentTypeFilterForTransactions, - getProcessorEventForTransactions, -} from '../../../../lib/helpers/transactions'; -import { GenericMetricsChart } from '../../fetch_and_transform_metrics'; - -export async function getActiveInstances({ - environment, - kuery, - setup, - serviceName, - start, - end, - searchAggregatedTransactions, -}: { - environment: string; - kuery: string; - setup: Setup; - serviceName: string; - start: number; - end: number; - searchAggregatedTransactions: boolean; -}): Promise { - const { apmEventClient, config } = setup; - - const aggs = { - activeInstances: { - cardinality: { - field: SERVICE_NODE_NAME, - }, - }, - }; - - const params = { - apm: { - events: [getProcessorEventForTransactions(searchAggregatedTransactions)], - }, - body: { - track_total_hits: false, - size: 0, - query: { - bool: { - filter: [ - { term: { [SERVICE_NAME]: serviceName } }, - ...rangeQuery(start, end), - ...environmentQuery(environment), - ...kqlQuery(kuery), - ...getDocumentTypeFilterForTransactions( - searchAggregatedTransactions - ), - ], - }, - }, - aggs: { - ...aggs, - timeseriesData: { - date_histogram: getMetricsDateHistogramParams({ - start, - end, - metricsInterval: config.metricsInterval, - }), - aggs, - }, - }, - }, - }; - - const { aggregations } = await apmEventClient.search( - 'get_active_instances', - params - ); - - return { - title: i18n.translate('xpack.apm.agentMetrics.serverless.activeInstances', { - defaultMessage: 'Active instances', - }), - key: 'active_instances', - yUnit: 'integer', - description: i18n.translate( - 'xpack.apm.agentMetrics.serverless.activeInstances.description', - { - defaultMessage: - 'This chart shows the number of active instances of your serverless function over time. Multiple active instances may be a result of provisioned concurrency for your function or an increase in concurrent load that scales your function on-demand. An increase in active instance can be an indicator for an increase in concurrent invocations.', - } - ), - series: [ - { - title: i18n.translate( - 'xpack.apm.agentMetrics.serverless.series.activeInstances', - { defaultMessage: 'Active instances' } - ), - key: 'active_instances', - type: 'bar', - color: theme.euiColorVis1, - overallValue: aggregations?.activeInstances.value ?? 0, - data: - aggregations?.timeseriesData.buckets.map((timeseriesBucket) => ({ - x: timeseriesBucket.key, - y: timeseriesBucket.activeInstances.value, - })) || [], - }, - ], - }; -} diff --git a/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/index.ts b/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/index.ts deleted file mode 100644 index 5635dd5c0e50..000000000000 --- a/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/index.ts +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { withApmSpan } from '../../../../utils/with_apm_span'; -import { Setup } from '../../../../lib/helpers/setup_request'; -import { getServerlessFunctionLatency } from './serverless_function_latency'; -import { getColdStartDuration } from './cold_start_duration'; -import { getMemoryChartData } from '../shared/memory'; -import { getComputeUsage } from './compute_usage'; -import { getActiveInstances } from './active_instances'; -import { getColdStartCount } from './cold_start_count'; -import { getSearchTransactionsEvents } from '../../../../lib/helpers/transactions'; - -export function getServerlessAgentMetricCharts({ - environment, - kuery, - setup, - serviceName, - start, - end, -}: { - environment: string; - kuery: string; - setup: Setup; - serviceName: string; - start: number; - end: number; -}) { - return withApmSpan('get_serverless_agent_metric_charts', async () => { - const searchAggregatedTransactions = await getSearchTransactionsEvents({ - ...setup, - kuery, - start, - end, - }); - - const options = { - environment, - kuery, - setup, - serviceName, - start, - end, - }; - return await Promise.all([ - getServerlessFunctionLatency({ - ...options, - searchAggregatedTransactions, - }), - getMemoryChartData(options), - getColdStartDuration(options), - getColdStartCount(options), - getComputeUsage(options), - getActiveInstances({ ...options, searchAggregatedTransactions }), - ]); - }); -} diff --git a/x-pack/plugins/apm/server/routes/metrics/by_agent/shared/cpu/index.ts b/x-pack/plugins/apm/server/routes/metrics/by_agent/shared/cpu/index.ts index d09b35e25e39..7da8e16bc28c 100644 --- a/x-pack/plugins/apm/server/routes/metrics/by_agent/shared/cpu/index.ts +++ b/x-pack/plugins/apm/server/routes/metrics/by_agent/shared/cpu/index.ts @@ -11,9 +11,10 @@ import { METRIC_SYSTEM_CPU_PERCENT, METRIC_PROCESS_CPU_PERCENT, } from '../../../../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../../../../lib/helpers/setup_request'; import { ChartBase } from '../../../types'; import { fetchAndTransformMetrics } from '../../../fetch_and_transform_metrics'; +import { APMConfig } from '../../../../..'; +import { APMEventClient } from '../../../../../lib/helpers/create_es_client/create_apm_event_client'; const series = { systemCPUMax: { @@ -55,7 +56,8 @@ const chartBase: ChartBase = { export function getCPUChartData({ environment, kuery, - setup, + config, + apmEventClient, serviceName, serviceNodeName, start, @@ -63,7 +65,8 @@ export function getCPUChartData({ }: { environment: string; kuery: string; - setup: Setup; + config: APMConfig; + apmEventClient: APMEventClient; serviceName: string; serviceNodeName?: string; start: number; @@ -72,7 +75,8 @@ export function getCPUChartData({ return fetchAndTransformMetrics({ environment, kuery, - setup, + config, + apmEventClient, serviceName, serviceNodeName, start, diff --git a/x-pack/plugins/apm/server/routes/metrics/by_agent/shared/memory/index.ts b/x-pack/plugins/apm/server/routes/metrics/by_agent/shared/memory/index.ts index a7e41ea71b72..c4aa4f82fe55 100644 --- a/x-pack/plugins/apm/server/routes/metrics/by_agent/shared/memory/index.ts +++ b/x-pack/plugins/apm/server/routes/metrics/by_agent/shared/memory/index.ts @@ -15,9 +15,10 @@ import { METRIC_SYSTEM_FREE_MEMORY, METRIC_SYSTEM_TOTAL_MEMORY, } from '../../../../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../../../../lib/helpers/setup_request'; import { fetchAndTransformMetrics } from '../../../fetch_and_transform_metrics'; import { ChartBase } from '../../../types'; +import { APMConfig } from '../../../../..'; +import { APMEventClient } from '../../../../../lib/helpers/create_es_client/create_apm_event_client'; const series = { memoryUsedMax: { @@ -83,19 +84,21 @@ export const percentCgroupMemoryUsedScript = { export async function getMemoryChartData({ environment, kuery, - setup, + config, + apmEventClient, serviceName, serviceNodeName, - faasId, + serverlessId, start, end, }: { environment: string; kuery: string; - setup: Setup; + config: APMConfig; + apmEventClient: APMEventClient; serviceName: string; serviceNodeName?: string; - faasId?: string; + serverlessId?: string; start: number; end: number; }) { @@ -103,7 +106,8 @@ export async function getMemoryChartData({ const cgroupResponse = await fetchAndTransformMetrics({ environment, kuery, - setup, + config, + apmEventClient, serviceName, serviceNodeName, start, @@ -115,7 +119,7 @@ export async function getMemoryChartData({ }, additionalFilters: [ { exists: { field: METRIC_CGROUP_MEMORY_USAGE_BYTES } }, - ...termQuery(FAAS_ID, faasId), + ...termQuery(FAAS_ID, serverlessId), ], operationName: 'get_cgroup_memory_metrics_charts', }); @@ -124,7 +128,8 @@ export async function getMemoryChartData({ return await fetchAndTransformMetrics({ environment, kuery, - setup, + config, + apmEventClient, serviceName, serviceNodeName, start, @@ -137,7 +142,7 @@ export async function getMemoryChartData({ additionalFilters: [ { exists: { field: METRIC_SYSTEM_FREE_MEMORY } }, { exists: { field: METRIC_SYSTEM_TOTAL_MEMORY } }, - ...termQuery(FAAS_ID, faasId), + ...termQuery(FAAS_ID, serverlessId), ], operationName: 'get_system_memory_metrics_charts', }); diff --git a/x-pack/plugins/apm/server/routes/metrics/fetch_and_transform_metrics.ts b/x-pack/plugins/apm/server/routes/metrics/fetch_and_transform_metrics.ts index 31ca4f54d932..c23950b311e2 100644 --- a/x-pack/plugins/apm/server/routes/metrics/fetch_and_transform_metrics.ts +++ b/x-pack/plugins/apm/server/routes/metrics/fetch_and_transform_metrics.ts @@ -12,9 +12,11 @@ import type { AggregationOptionsByType } from '@kbn/es-types'; import { kqlQuery, rangeQuery } from '@kbn/observability-plugin/server'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; import { getVizColorForIndex } from '../../../common/viz_colors'; -import { APMEventESSearchRequest } from '../../lib/helpers/create_es_client/create_apm_event_client'; +import { + APMEventClient, + APMEventESSearchRequest, +} from '../../lib/helpers/create_es_client/create_apm_event_client'; import { getMetricsDateHistogramParams } from '../../lib/helpers/metrics'; -import { Setup } from '../../lib/helpers/setup_request'; import { ChartBase } from './types'; import { environmentQuery, @@ -22,6 +24,7 @@ import { } from '../../../common/utils/environment_query'; import { SERVICE_NAME } from '../../../common/elasticsearch_fieldnames'; import { ChartType, Coordinate, YUnit } from '../../../typings/timeseries'; +import { APMConfig } from '../..'; type MetricsAggregationMap = Unionize<{ min: AggregationOptionsByType['min']; @@ -63,7 +66,8 @@ export interface FetchAndTransformMetrics { export async function fetchAndTransformMetrics({ environment, kuery, - setup, + config, + apmEventClient, serviceName, serviceNodeName, start, @@ -75,7 +79,8 @@ export async function fetchAndTransformMetrics({ }: { environment: string; kuery: string; - setup: Setup; + config: APMConfig; + apmEventClient: APMEventClient; serviceName: string; serviceNodeName?: string; start: number; @@ -85,8 +90,6 @@ export async function fetchAndTransformMetrics({ additionalFilters?: QueryDslQueryContainer[]; operationName: string; }): Promise { - const { apmEventClient, config } = setup; - const params: GenericMetricsRequest = { apm: { events: [ProcessorEvent.metric], diff --git a/x-pack/plugins/apm/server/routes/metrics/get_metrics_chart_data_by_agent.ts b/x-pack/plugins/apm/server/routes/metrics/get_metrics_chart_data_by_agent.ts index b5ae2bbe093a..afe1e6e919a2 100644 --- a/x-pack/plugins/apm/server/routes/metrics/get_metrics_chart_data_by_agent.ts +++ b/x-pack/plugins/apm/server/routes/metrics/get_metrics_chart_data_by_agent.ts @@ -5,54 +5,50 @@ * 2.0. */ -import { Setup } from '../../lib/helpers/setup_request'; import { getJavaMetricsCharts } from './by_agent/java'; import { getDefaultMetricsCharts } from './by_agent/default'; -import { isJavaAgentName, isServerlessAgent } from '../../../common/agent_name'; +import { isJavaAgentName } from '../../../common/agent_name'; import { GenericMetricsChart } from './fetch_and_transform_metrics'; -import { getServerlessAgentMetricCharts } from './by_agent/serverless'; +import { APMConfig } from '../..'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getMetricsChartDataByAgent({ environment, kuery, - setup, + config, + apmEventClient, serviceName, serviceNodeName, agentName, start, end, - serviceRuntimeName, }: { environment: string; kuery: string; - setup: Setup; + config: APMConfig; + apmEventClient: APMEventClient; serviceName: string; serviceNodeName?: string; agentName: string; start: number; end: number; - serviceRuntimeName?: string; }): Promise { const options = { environment, kuery, - setup, + config, + apmEventClient, serviceName, start, end, }; - const serverlessAgent = isServerlessAgent(serviceRuntimeName); - if (isJavaAgentName(agentName) && !serverlessAgent) { + if (isJavaAgentName(agentName)) { return getJavaMetricsCharts({ ...options, serviceNodeName, }); } - if (serverlessAgent) { - return getServerlessAgentMetricCharts(options); - } - return getDefaultMetricsCharts(options); } diff --git a/x-pack/plugins/apm/server/routes/metrics/get_service_nodes.ts b/x-pack/plugins/apm/server/routes/metrics/get_service_nodes.ts index e22ae93b25d5..2dd719717be0 100644 --- a/x-pack/plugins/apm/server/routes/metrics/get_service_nodes.ts +++ b/x-pack/plugins/apm/server/routes/metrics/get_service_nodes.ts @@ -21,25 +21,23 @@ import { SERVICE_NODE_NAME, } from '../../../common/elasticsearch_fieldnames'; import { environmentQuery } from '../../../common/utils/environment_query'; -import { Setup } from '../../lib/helpers/setup_request'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; const getServiceNodes = async ({ kuery, - setup, + apmEventClient, serviceName, environment, start, end, }: { kuery: string; - setup: Setup; + apmEventClient: APMEventClient; serviceName: string; environment: string; start: number; end: number; }) => { - const { apmEventClient } = setup; - const params = { apm: { events: [ProcessorEvent.metric], diff --git a/x-pack/plugins/apm/server/routes/metrics/queries.test.ts b/x-pack/plugins/apm/server/routes/metrics/queries.test.ts index af36a030157c..ea14dc208292 100644 --- a/x-pack/plugins/apm/server/routes/metrics/queries.test.ts +++ b/x-pack/plugins/apm/server/routes/metrics/queries.test.ts @@ -22,9 +22,10 @@ describe('metrics queries', () => { const createTests = (serviceNodeName?: string) => { it('fetches cpu chart data', async () => { - mock = await inspectSearchParams((setup) => + mock = await inspectSearchParams((setup, apmEventClient) => getCPUChartData({ - setup, + config: setup.config, + apmEventClient, serviceName: 'foo', serviceNodeName, environment: ENVIRONMENT_ALL.value, @@ -38,9 +39,10 @@ describe('metrics queries', () => { }); it('fetches memory chart data', async () => { - mock = await inspectSearchParams((setup) => + mock = await inspectSearchParams((setup, apmEventClient) => getMemoryChartData({ - setup, + config: setup.config, + apmEventClient, serviceName: 'foo', serviceNodeName, environment: ENVIRONMENT_ALL.value, @@ -54,9 +56,10 @@ describe('metrics queries', () => { }); it('fetches heap memory chart data', async () => { - mock = await inspectSearchParams((setup) => + mock = await inspectSearchParams((setup, apmEventClient) => getHeapMemoryChart({ - setup, + config: setup.config, + apmEventClient, serviceName: 'foo', serviceNodeName, environment: ENVIRONMENT_ALL.value, @@ -70,9 +73,10 @@ describe('metrics queries', () => { }); it('fetches non heap memory chart data', async () => { - mock = await inspectSearchParams((setup) => + mock = await inspectSearchParams((setup, apmEventClient) => getNonHeapMemoryChart({ - setup, + config: setup.config, + apmEventClient, serviceName: 'foo', serviceNodeName, environment: ENVIRONMENT_ALL.value, @@ -86,9 +90,10 @@ describe('metrics queries', () => { }); it('fetches thread count chart data', async () => { - mock = await inspectSearchParams((setup) => + mock = await inspectSearchParams((setup, apmEventClient) => getThreadCountChart({ - setup, + config: setup.config, + apmEventClient, serviceName: 'foo', serviceNodeName, environment: ENVIRONMENT_ALL.value, diff --git a/x-pack/plugins/apm/server/routes/metrics/route.ts b/x-pack/plugins/apm/server/routes/metrics/route.ts index 8aea9cc5a981..8fd878222fc5 100644 --- a/x-pack/plugins/apm/server/routes/metrics/route.ts +++ b/x-pack/plugins/apm/server/routes/metrics/route.ts @@ -6,12 +6,14 @@ */ import * as t from 'io-ts'; +import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; import { setupRequest } from '../../lib/helpers/setup_request'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { environmentRt, kueryRt, rangeRt } from '../default_api_types'; import { FetchAndTransformMetrics } from './fetch_and_transform_metrics'; import { getMetricsChartDataByAgent } from './get_metrics_chart_data_by_agent'; import { getServiceNodes } from './get_service_nodes'; +import { metricsServerlessRouteRepository } from './serverless/route'; const metricsChartsRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/services/{serviceName}/metrics/charts', @@ -25,7 +27,6 @@ const metricsChartsRoute = createApmServerRoute({ }), t.partial({ serviceNodeName: t.string, - serviceRuntimeName: t.string, }), environmentRt, kueryRt, @@ -39,28 +40,24 @@ const metricsChartsRoute = createApmServerRoute({ charts: FetchAndTransformMetrics[]; }> => { const { params } = resources; - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { serviceName } = params.path; - const { - agentName, - environment, - kuery, - serviceNodeName, - start, - end, - serviceRuntimeName, - } = params.query; + const { agentName, environment, kuery, serviceNodeName, start, end } = + params.query; const charts = await getMetricsChartDataByAgent({ environment, kuery, - setup, + config: setup.config, + apmEventClient, serviceName, agentName, serviceNodeName, start, end, - serviceRuntimeName, }); return { charts }; @@ -88,14 +85,14 @@ const serviceMetricsJvm = createApmServerRoute({ threadCount: number | null; }>; }> => { - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { params } = resources; const { serviceName } = params.path; const { kuery, environment, start, end } = params.query; const serviceNodes = await getServiceNodes({ kuery, - setup, + apmEventClient, serviceName, environment, start, @@ -108,4 +105,5 @@ const serviceMetricsJvm = createApmServerRoute({ export const metricsRouteRepository = { ...metricsChartsRoute, ...serviceMetricsJvm, + ...metricsServerlessRouteRepository, }; diff --git a/x-pack/plugins/apm/server/routes/metrics/serverless/get_active_instances_overview.ts b/x-pack/plugins/apm/server/routes/metrics/serverless/get_active_instances_overview.ts new file mode 100644 index 000000000000..5361070e50c8 --- /dev/null +++ b/x-pack/plugins/apm/server/routes/metrics/serverless/get_active_instances_overview.ts @@ -0,0 +1,191 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { ProcessorEvent } from '@kbn/observability-plugin/common'; +import { + kqlQuery, + rangeQuery, + termQuery, +} from '@kbn/observability-plugin/server'; +import { + FAAS_BILLED_DURATION, + FAAS_DURATION, + FAAS_ID, + METRICSET_NAME, + METRIC_SYSTEM_FREE_MEMORY, + METRIC_SYSTEM_TOTAL_MEMORY, + SERVICE_NAME, + SERVICE_NODE_NAME, +} from '../../../../common/elasticsearch_fieldnames'; +import { getServerlessFunctionNameFromId } from '../../../../common/serverless'; +import { environmentQuery } from '../../../../common/utils/environment_query'; +import { Coordinate } from '../../../../typings/timeseries'; +import { getBucketSize } from '../../../lib/helpers/get_bucket_size'; +import { calcMemoryUsed } from './helper'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; + +interface ActiveInstanceTimeseries { + serverlessDuration: Coordinate[]; + billedDuration: Coordinate[]; +} + +export interface ActiveInstanceOverview { + activeInstanceName: string; + serverlessId: string; + serverlessFunctionName: string; + timeseries: ActiveInstanceTimeseries; + serverlessDurationAvg: number | null; + billedDurationAvg: number | null; + avgMemoryUsed?: number | null; + memorySize: number | null; +} + +export async function getServerlessActiveInstancesOverview({ + end, + environment, + kuery, + serviceName, + start, + serverlessId, + apmEventClient, +}: { + environment: string; + kuery: string; + serviceName: string; + start: number; + end: number; + serverlessId?: string; + apmEventClient: APMEventClient; +}) { + const { intervalString } = getBucketSize({ + start, + end, + numBuckets: 20, + }); + + const aggs = { + faasDurationAvg: { avg: { field: FAAS_DURATION } }, + faasBilledDurationAvg: { avg: { field: FAAS_BILLED_DURATION } }, + }; + + const params = { + apm: { + events: [ProcessorEvent.metric], + }, + body: { + track_total_hits: 1, + size: 0, + query: { + bool: { + filter: [ + ...termQuery(METRICSET_NAME, 'app'), + { term: { [SERVICE_NAME]: serviceName } }, + ...rangeQuery(start, end), + ...environmentQuery(environment), + ...kqlQuery(kuery), + ...termQuery(FAAS_ID, serverlessId), + ], + }, + }, + aggs: { + activeInstances: { + terms: { field: SERVICE_NODE_NAME }, + aggs: { + serverlessFunctions: { + terms: { field: FAAS_ID }, + aggs: { + ...{ + ...aggs, + maxTotalMemory: { + max: { field: METRIC_SYSTEM_TOTAL_MEMORY }, + }, + avgTotalMemory: { + avg: { field: METRIC_SYSTEM_TOTAL_MEMORY }, + }, + avgFreeMemory: { avg: { field: METRIC_SYSTEM_FREE_MEMORY } }, + }, + timeseries: { + date_histogram: { + field: '@timestamp', + fixed_interval: intervalString, + min_doc_count: 0, + extended_bounds: { + min: start, + max: end, + }, + }, + aggs, + }, + }, + }, + }, + }, + }, + }, + }; + + const response = await apmEventClient.search( + 'ger_serverless_active_instances_overview', + params + ); + + return ( + response.aggregations?.activeInstances?.buckets?.flatMap((bucket) => { + const activeInstanceName = bucket.key as string; + const serverlessFunctionsDetails = + bucket.serverlessFunctions.buckets.reduce( + (acc, curr) => { + const currentServerlessId = curr.key as string; + + const timeseries = + curr.timeseries.buckets.reduce( + (timeseriesAcc, timeseriesCurr) => { + return { + serverlessDuration: [ + ...timeseriesAcc.serverlessDuration, + { + x: timeseriesCurr.key, + y: timeseriesCurr.faasDurationAvg.value, + }, + ], + billedDuration: [ + ...timeseriesAcc.billedDuration, + { + x: timeseriesCurr.key, + y: timeseriesCurr.faasBilledDurationAvg.value, + }, + ], + }; + }, + { + serverlessDuration: [], + billedDuration: [], + } + ); + return [ + ...acc, + { + activeInstanceName, + serverlessId: currentServerlessId, + serverlessFunctionName: + getServerlessFunctionNameFromId(currentServerlessId), + timeseries, + serverlessDurationAvg: curr.faasDurationAvg.value, + billedDurationAvg: curr.faasBilledDurationAvg.value, + avgMemoryUsed: calcMemoryUsed({ + memoryFree: curr.avgFreeMemory.value, + memoryTotal: curr.avgTotalMemory.value, + }), + memorySize: curr.avgTotalMemory.value, + }, + ]; + }, + [] + ); + return serverlessFunctionsDetails; + }) || [] + ); +} diff --git a/x-pack/plugins/apm/server/routes/metrics/serverless/get_active_instances_timeseries.ts b/x-pack/plugins/apm/server/routes/metrics/serverless/get_active_instances_timeseries.ts new file mode 100644 index 000000000000..facd270aec72 --- /dev/null +++ b/x-pack/plugins/apm/server/routes/metrics/serverless/get_active_instances_timeseries.ts @@ -0,0 +1,97 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + kqlQuery, + rangeQuery, + termQuery, +} from '@kbn/observability-plugin/server'; +import { ProcessorEvent } from '@kbn/observability-plugin/common'; +import { + FAAS_ID, + METRICSET_NAME, + SERVICE_NAME, + SERVICE_NODE_NAME, +} from '../../../../common/elasticsearch_fieldnames'; +import { environmentQuery } from '../../../../common/utils/environment_query'; +import { Coordinate } from '../../../../typings/timeseries'; +import { getMetricsDateHistogramParams } from '../../../lib/helpers/metrics'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; +import { APMConfig } from '../../..'; + +export async function getActiveInstancesTimeseries({ + environment, + kuery, + serviceName, + start, + end, + serverlessId, + config, + apmEventClient, +}: { + environment: string; + kuery: string; + serviceName: string; + start: number; + end: number; + serverlessId?: string; + config: APMConfig; + apmEventClient: APMEventClient; +}): Promise { + const aggs = { + activeInstances: { + cardinality: { + field: SERVICE_NODE_NAME, + }, + }, + }; + + const params = { + apm: { + events: [ProcessorEvent.metric], + }, + body: { + track_total_hits: false, + size: 0, + query: { + bool: { + filter: [ + ...termQuery(METRICSET_NAME, 'app'), + { term: { [SERVICE_NAME]: serviceName } }, + ...rangeQuery(start, end), + ...environmentQuery(environment), + ...kqlQuery(kuery), + ...termQuery(FAAS_ID, serverlessId), + ], + }, + }, + aggs: { + ...aggs, + timeseriesData: { + date_histogram: getMetricsDateHistogramParams({ + start, + end, + metricsInterval: config.metricsInterval, + }), + aggs, + }, + }, + }, + }; + + const { aggregations } = await apmEventClient.search( + 'get_active_instances', + params + ); + + return ( + aggregations?.timeseriesData?.buckets?.map((timeseriesBucket) => ({ + x: timeseriesBucket.key, + y: timeseriesBucket.activeInstances.value, + })) || [] + ); +} diff --git a/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/cold_start_count.ts b/x-pack/plugins/apm/server/routes/metrics/serverless/get_cold_start_count_chart.ts similarity index 66% rename from x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/cold_start_count.ts rename to x-pack/plugins/apm/server/routes/metrics/serverless/get_cold_start_count_chart.ts index d884aa8dce44..a56e6c4c8764 100644 --- a/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/cold_start_count.ts +++ b/x-pack/plugins/apm/server/routes/metrics/serverless/get_cold_start_count_chart.ts @@ -8,17 +8,19 @@ import { i18n } from '@kbn/i18n'; import { termQuery } from '@kbn/observability-plugin/server'; import { euiLightVars as theme } from '@kbn/ui-theme'; +import { APMConfig } from '../../..'; import { FAAS_COLDSTART, + FAAS_ID, METRICSET_NAME, -} from '../../../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../../../lib/helpers/setup_request'; -import { fetchAndTransformMetrics } from '../../fetch_and_transform_metrics'; -import { ChartBase } from '../../types'; +} from '../../../../common/elasticsearch_fieldnames'; +import { fetchAndTransformMetrics } from '../fetch_and_transform_metrics'; +import { ChartBase } from '../types'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; const chartBase: ChartBase = { - title: i18n.translate('xpack.apm.agentMetrics.serverless.coldStart', { - defaultMessage: 'Cold start', + title: i18n.translate('xpack.apm.agentMetrics.serverless.coldStart.title', { + defaultMessage: 'Cold starts', }), key: 'cold_start_count', type: 'bar', @@ -33,25 +35,30 @@ const chartBase: ChartBase = { }, }; -export function getColdStartCount({ +export function getColdStartCountChart({ environment, kuery, - setup, + config, + apmEventClient, serviceName, start, end, + serverlessId, }: { environment: string; kuery: string; - setup: Setup; + config: APMConfig; + apmEventClient: APMEventClient; serviceName: string; start: number; end: number; + serverlessId?: string; }) { return fetchAndTransformMetrics({ environment, kuery, - setup, + config, + apmEventClient, serviceName, start, end, @@ -59,7 +66,8 @@ export function getColdStartCount({ aggs: { coldStart: { sum: { field: FAAS_COLDSTART } } }, additionalFilters: [ ...termQuery(FAAS_COLDSTART, true), - ...termQuery(METRICSET_NAME, 'transaction'), + ...termQuery(FAAS_ID, serverlessId), + ...termQuery(METRICSET_NAME, 'app'), ], operationName: 'get_cold_start_count', }); diff --git a/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/cold_start_duration.ts b/x-pack/plugins/apm/server/routes/metrics/serverless/get_cold_start_duration_chart.ts similarity index 60% rename from x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/cold_start_duration.ts rename to x-pack/plugins/apm/server/routes/metrics/serverless/get_cold_start_duration_chart.ts index 5a78f5d97d5e..b1802edb5440 100644 --- a/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/cold_start_duration.ts +++ b/x-pack/plugins/apm/server/routes/metrics/serverless/get_cold_start_duration_chart.ts @@ -7,11 +7,17 @@ import { i18n } from '@kbn/i18n'; import { euiLightVars as theme } from '@kbn/ui-theme'; -import { FAAS_COLDSTART_DURATION } from '../../../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../../../lib/helpers/setup_request'; -import { fetchAndTransformMetrics } from '../../fetch_and_transform_metrics'; -import { ChartBase } from '../../types'; -import { isFiniteNumber } from '../../../../../common/utils/is_finite_number'; +import { termQuery } from '@kbn/observability-plugin/server'; +import { + FAAS_COLDSTART_DURATION, + FAAS_ID, + METRICSET_NAME, +} from '../../../../common/elasticsearch_fieldnames'; +import { fetchAndTransformMetrics } from '../fetch_and_transform_metrics'; +import { ChartBase } from '../types'; +import { isFiniteNumber } from '../../../../common/utils/is_finite_number'; +import { APMConfig } from '../../..'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; const chartBase: ChartBase = { title: i18n.translate('xpack.apm.agentMetrics.serverless.coldStartDuration', { @@ -38,37 +44,46 @@ const chartBase: ChartBase = { ), }; -export async function getColdStartDuration({ +export async function getColdStartDurationChart({ environment, kuery, - setup, + config, + apmEventClient, serviceName, start, end, + serverlessId, }: { environment: string; kuery: string; - setup: Setup; + config: APMConfig; + apmEventClient: APMEventClient; serviceName: string; start: number; end: number; + serverlessId?: string; }) { const coldStartDurationMetric = await fetchAndTransformMetrics({ environment, kuery, - setup, + config, + apmEventClient, serviceName, start, end, chartBase, aggs: { coldStart: { avg: { field: FAAS_COLDSTART_DURATION } } }, - additionalFilters: [{ exists: { field: FAAS_COLDSTART_DURATION } }], + additionalFilters: [ + { exists: { field: FAAS_COLDSTART_DURATION } }, + ...termQuery(FAAS_ID, serverlessId), + ...termQuery(METRICSET_NAME, 'app'), + ], operationName: 'get_cold_start_duration', }); const [series] = coldStartDurationMetric.series; - const data = series.data.map(({ x, y }) => ({ + const data = series?.data?.map(({ x, y }) => ({ x, // Cold start duration duration is stored in ms, convert it to microseconds so it uses the same unit as the other charts y: isFiniteNumber(y) ? y * 1000 : y, @@ -76,13 +91,15 @@ export async function getColdStartDuration({ return { ...coldStartDurationMetric, - series: [ - { - ...series, - // Cold start duration duration is stored in ms, convert it to microseconds - overallValue: series.overallValue * 1000, - data, - }, - ], + series: series + ? [ + { + ...series, + // Cold start duration duration is stored in ms, convert it to microseconds + overallValue: series.overallValue * 1000, + data, + }, + ] + : [], }; } diff --git a/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/compute_usage.ts b/x-pack/plugins/apm/server/routes/metrics/serverless/get_compute_usage_chart.ts similarity index 85% rename from x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/compute_usage.ts rename to x-pack/plugins/apm/server/routes/metrics/serverless/get_compute_usage_chart.ts index bb8d5023c9af..201961113cac 100644 --- a/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/compute_usage.ts +++ b/x-pack/plugins/apm/server/routes/metrics/serverless/get_compute_usage_chart.ts @@ -13,17 +13,19 @@ import { termQuery, } from '@kbn/observability-plugin/server'; import { euiLightVars as theme } from '@kbn/ui-theme'; +import { APMConfig } from '../../..'; import { FAAS_BILLED_DURATION, + FAAS_ID, METRICSET_NAME, METRIC_SYSTEM_TOTAL_MEMORY, SERVICE_NAME, -} from '../../../../../common/elasticsearch_fieldnames'; -import { environmentQuery } from '../../../../../common/utils/environment_query'; -import { isFiniteNumber } from '../../../../../common/utils/is_finite_number'; -import { getMetricsDateHistogramParams } from '../../../../lib/helpers/metrics'; -import { Setup } from '../../../../lib/helpers/setup_request'; -import { GenericMetricsChart } from '../../fetch_and_transform_metrics'; +} from '../../../../common/elasticsearch_fieldnames'; +import { environmentQuery } from '../../../../common/utils/environment_query'; +import { isFiniteNumber } from '../../../../common/utils/is_finite_number'; +import { getMetricsDateHistogramParams } from '../../../lib/helpers/metrics'; +import { GenericMetricsChart } from '../fetch_and_transform_metrics'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; /** * To calculate the compute usage we need to multiply the "system.memory.total" by "faas.billed_duration". @@ -47,23 +49,25 @@ function calculateComputeUsageGBSeconds({ return totalMemoryGB * faasBilledDurationSec; } -export async function getComputeUsage({ +export async function getComputeUsageChart({ environment, kuery, - setup, + config, + apmEventClient, serviceName, start, end, + serverlessId, }: { environment: string; kuery: string; - setup: Setup; + config: APMConfig; + apmEventClient: APMEventClient; serviceName: string; start: number; end: number; + serverlessId?: string; }): Promise { - const { apmEventClient, config } = setup; - const aggs = { avgFaasBilledDuration: { avg: { field: FAAS_BILLED_DURATION } }, avgTotalMemory: { avg: { field: METRIC_SYSTEM_TOTAL_MEMORY } }, @@ -85,6 +89,7 @@ export async function getComputeUsage({ ...kqlQuery(kuery), { exists: { field: FAAS_BILLED_DURATION } }, ...termQuery(METRICSET_NAME, 'app'), + ...termQuery(FAAS_ID, serverlessId), ], }, }, diff --git a/x-pack/plugins/apm/server/routes/metrics/serverless/get_serverless_agent_metrics_chart.ts b/x-pack/plugins/apm/server/routes/metrics/serverless/get_serverless_agent_metrics_chart.ts new file mode 100644 index 000000000000..1d1bf19b635b --- /dev/null +++ b/x-pack/plugins/apm/server/routes/metrics/serverless/get_serverless_agent_metrics_chart.ts @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getSearchTransactionsEvents } from '../../../lib/helpers/transactions'; +import { withApmSpan } from '../../../utils/with_apm_span'; +import { getMemoryChartData } from '../by_agent/shared/memory'; +import { getColdStartCountChart } from './get_cold_start_count_chart'; +import { getColdStartDurationChart } from './get_cold_start_duration_chart'; +import { getComputeUsageChart } from './get_compute_usage_chart'; +import { getServerlessFunctionLatencyChart } from './get_serverless_function_latency_chart'; +import { APMConfig } from '../../..'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; + +export function getServerlessAgentMetricsCharts({ + environment, + kuery, + config, + apmEventClient, + serviceName, + start, + end, + serverlessId, +}: { + environment: string; + kuery: string; + config: APMConfig; + apmEventClient: APMEventClient; + serviceName: string; + start: number; + end: number; + serverlessId?: string; +}) { + return withApmSpan('get_serverless_agent_metric_charts', async () => { + const searchAggregatedTransactions = await getSearchTransactionsEvents({ + config, + apmEventClient, + kuery, + start, + end, + }); + + const options = { + environment, + kuery, + apmEventClient, + config, + serviceName, + start, + end, + serverlessId, + }; + return await Promise.all([ + getServerlessFunctionLatencyChart({ + ...options, + searchAggregatedTransactions, + }), + getMemoryChartData(options), + getColdStartDurationChart(options), + getColdStartCountChart(options), + getComputeUsageChart(options), + ]); + }); +} diff --git a/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/serverless_function_latency.ts b/x-pack/plugins/apm/server/routes/metrics/serverless/get_serverless_function_latency_chart.ts similarity index 57% rename from x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/serverless_function_latency.ts rename to x-pack/plugins/apm/server/routes/metrics/serverless/get_serverless_function_latency_chart.ts index 0a27c66ef036..47593c3f4840 100644 --- a/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/serverless_function_latency.ts +++ b/x-pack/plugins/apm/server/routes/metrics/serverless/get_serverless_function_latency_chart.ts @@ -7,17 +7,24 @@ import { i18n } from '@kbn/i18n'; import { euiLightVars as theme } from '@kbn/ui-theme'; -import { FAAS_BILLED_DURATION } from '../../../../../common/elasticsearch_fieldnames'; -import { LatencyAggregationType } from '../../../../../common/latency_aggregation_types'; -import { isFiniteNumber } from '../../../../../common/utils/is_finite_number'; -import { getVizColorForIndex } from '../../../../../common/viz_colors'; -import { Setup } from '../../../../lib/helpers/setup_request'; -import { getLatencyTimeseries } from '../../../transactions/get_latency_charts'; +import { termQuery } from '@kbn/observability-plugin/server'; +import { isEmpty } from 'lodash'; +import { + FAAS_BILLED_DURATION, + FAAS_ID, + METRICSET_NAME, +} from '../../../../common/elasticsearch_fieldnames'; +import { LatencyAggregationType } from '../../../../common/latency_aggregation_types'; +import { isFiniteNumber } from '../../../../common/utils/is_finite_number'; +import { getVizColorForIndex } from '../../../../common/viz_colors'; +import { getLatencyTimeseries } from '../../transactions/get_latency_charts'; +import { APMConfig } from '../../..'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; import { fetchAndTransformMetrics, GenericMetricsChart, -} from '../../fetch_and_transform_metrics'; -import { ChartBase } from '../../types'; +} from '../fetch_and_transform_metrics'; +import { ChartBase } from '../types'; const billedDurationAvg = { title: i18n.translate('xpack.apm.agentMetrics.serverless.billedDurationAvg', { @@ -27,7 +34,7 @@ const billedDurationAvg = { const chartBase: ChartBase = { title: i18n.translate('xpack.apm.agentMetrics.serverless.avgDuration', { - defaultMessage: 'Avg. Duration', + defaultMessage: 'Lambda Duration', }), key: 'avg_duration', type: 'linemark', @@ -45,29 +52,32 @@ const chartBase: ChartBase = { async function getServerlessLantecySeries({ environment, kuery, - setup, + apmEventClient, serviceName, start, end, + serverlessId, searchAggregatedTransactions, }: { environment: string; kuery: string; - setup: Setup; + apmEventClient: APMEventClient; serviceName: string; start: number; end: number; + serverlessId?: string; searchAggregatedTransactions: boolean; }): Promise { const transactionLatency = await getLatencyTimeseries({ environment, kuery, serviceName, - setup, + apmEventClient, searchAggregatedTransactions, latencyAggregationType: LatencyAggregationType.avg, start, end, + serverlessId, }); return [ @@ -85,27 +95,32 @@ async function getServerlessLantecySeries({ ]; } -export async function getServerlessFunctionLatency({ +export async function getServerlessFunctionLatencyChart({ environment, kuery, - setup, + config, + apmEventClient, serviceName, start, end, + serverlessId, searchAggregatedTransactions, }: { environment: string; kuery: string; - setup: Setup; + config: APMConfig; + apmEventClient: APMEventClient; serviceName: string; start: number; end: number; + serverlessId?: string; searchAggregatedTransactions: boolean; }): Promise { const options = { environment, kuery, - setup, + config, + apmEventClient, serviceName, start, end, @@ -118,29 +133,43 @@ export async function getServerlessFunctionLatency({ aggs: { billedDurationAvg: { avg: { field: FAAS_BILLED_DURATION } }, }, - additionalFilters: [{ exists: { field: FAAS_BILLED_DURATION } }], + additionalFilters: [ + { exists: { field: FAAS_BILLED_DURATION } }, + ...termQuery(FAAS_ID, serverlessId), + ...termQuery(METRICSET_NAME, 'app'), + ], operationName: 'get_billed_duration', }), - getServerlessLantecySeries({ ...options, searchAggregatedTransactions }), + getServerlessLantecySeries({ + ...options, + serverlessId, + searchAggregatedTransactions, + }), ]); - const [series] = billedDurationMetrics.series; - const data = series.data.map(({ x, y }) => ({ - x, - // Billed duration is stored in ms, convert it to microseconds so it uses the same unit as the other chart - y: isFiniteNumber(y) ? y * 1000 : y, - })); + const series = []; + + const [billedDurationSeries] = billedDurationMetrics.series; + if (billedDurationSeries) { + const data = billedDurationSeries.data?.map(({ x, y }) => ({ + x, + // Billed duration is stored in ms, convert it to microseconds so it uses the same unit as the other chart + y: isFiniteNumber(y) ? y * 1000 : y, + })); + series.push({ + ...billedDurationSeries, + // Billed duration is stored in ms, convert it to microseconds + overallValue: billedDurationSeries.overallValue * 1000, + data: data || [], + }); + } + + if (!isEmpty(serverlessDurationSeries[0].data)) { + series.push(...serverlessDurationSeries); + } return { ...billedDurationMetrics, - series: [ - { - ...series, - // Billed duration is stored in ms, convert it to microseconds - overallValue: series.overallValue * 1000, - data, - }, - ...serverlessDurationSeries, - ], + series, }; } diff --git a/x-pack/plugins/apm/server/routes/metrics/serverless/get_serverless_functions_overview.ts b/x-pack/plugins/apm/server/routes/metrics/serverless/get_serverless_functions_overview.ts new file mode 100644 index 000000000000..236e950c1f13 --- /dev/null +++ b/x-pack/plugins/apm/server/routes/metrics/serverless/get_serverless_functions_overview.ts @@ -0,0 +1,100 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { ProcessorEvent } from '@kbn/observability-plugin/common'; +import { + kqlQuery, + rangeQuery, + termQuery, +} from '@kbn/observability-plugin/server'; +import { + FAAS_BILLED_DURATION, + FAAS_COLDSTART, + FAAS_DURATION, + FAAS_ID, + METRICSET_NAME, + METRIC_SYSTEM_FREE_MEMORY, + METRIC_SYSTEM_TOTAL_MEMORY, + SERVICE_NAME, +} from '../../../../common/elasticsearch_fieldnames'; +import { getServerlessFunctionNameFromId } from '../../../../common/serverless'; +import { environmentQuery } from '../../../../common/utils/environment_query'; +import { calcMemoryUsed } from './helper'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; + +export async function getServerlessFunctionsOverview({ + end, + environment, + kuery, + serviceName, + start, + apmEventClient, +}: { + environment: string; + kuery: string; + serviceName: string; + start: number; + end: number; + apmEventClient: APMEventClient; +}) { + const params = { + apm: { + events: [ProcessorEvent.metric], + }, + body: { + track_total_hits: false, + size: 0, + query: { + bool: { + filter: [ + ...termQuery(METRICSET_NAME, 'app'), + { term: { [SERVICE_NAME]: serviceName } }, + ...rangeQuery(start, end), + ...environmentQuery(environment), + ...kqlQuery(kuery), + ], + }, + }, + aggs: { + serverlessFunctions: { + terms: { field: FAAS_ID }, + aggs: { + faasDurationAvg: { avg: { field: FAAS_DURATION } }, + faasBilledDurationAvg: { avg: { field: FAAS_BILLED_DURATION } }, + coldStartCount: { sum: { field: FAAS_COLDSTART } }, + maxTotalMemory: { max: { field: METRIC_SYSTEM_TOTAL_MEMORY } }, + avgTotalMemory: { avg: { field: METRIC_SYSTEM_TOTAL_MEMORY } }, + avgFreeMemory: { avg: { field: METRIC_SYSTEM_FREE_MEMORY } }, + }, + }, + }, + }, + }; + + const response = await apmEventClient.search( + 'ger_serverless_functions_overview', + params + ); + + const serverlessFunctionsOverview = + response.aggregations?.serverlessFunctions?.buckets?.map((bucket) => { + const serverlessId = bucket.key as string; + return { + serverlessId, + serverlessFunctionName: getServerlessFunctionNameFromId(serverlessId), + serverlessDurationAvg: bucket.faasDurationAvg.value, + billedDurationAvg: bucket.faasBilledDurationAvg.value, + coldStartCount: bucket.coldStartCount.value, + avgMemoryUsed: calcMemoryUsed({ + memoryFree: bucket.avgFreeMemory.value, + memoryTotal: bucket.avgTotalMemory.value, + }), + memorySize: bucket.maxTotalMemory.value, + }; + }); + + return serverlessFunctionsOverview || []; +} diff --git a/x-pack/plugins/apm/server/routes/metrics/serverless/get_serverless_summary.ts b/x-pack/plugins/apm/server/routes/metrics/serverless/get_serverless_summary.ts new file mode 100644 index 000000000000..d3f292a11b87 --- /dev/null +++ b/x-pack/plugins/apm/server/routes/metrics/serverless/get_serverless_summary.ts @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { ProcessorEvent } from '@kbn/observability-plugin/common'; +import { + termQuery, + kqlQuery, + rangeQuery, +} from '@kbn/observability-plugin/server'; +import { + FAAS_BILLED_DURATION, + FAAS_DURATION, + FAAS_ID, + METRICSET_NAME, + METRIC_SYSTEM_FREE_MEMORY, + METRIC_SYSTEM_TOTAL_MEMORY, + SERVICE_NAME, +} from '../../../../common/elasticsearch_fieldnames'; +import { environmentQuery } from '../../../../common/utils/environment_query'; +import { calcMemoryUsedRate } from './helper'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; + +export async function getServerlessSummary({ + end, + environment, + kuery, + serviceName, + start, + serverlessId, + apmEventClient, +}: { + environment: string; + kuery: string; + serviceName: string; + start: number; + end: number; + serverlessId?: string; + apmEventClient: APMEventClient; +}) { + const params = { + apm: { + events: [ProcessorEvent.metric], + }, + body: { + track_total_hits: false, + size: 0, + query: { + bool: { + filter: [ + ...termQuery(METRICSET_NAME, 'app'), + { term: { [SERVICE_NAME]: serviceName } }, + ...rangeQuery(start, end), + ...environmentQuery(environment), + ...kqlQuery(kuery), + ...termQuery(FAAS_ID, serverlessId), + ], + }, + }, + aggs: { + totalFunctions: { cardinality: { field: FAAS_ID } }, + faasDurationAvg: { avg: { field: FAAS_DURATION } }, + faasBilledDurationAvg: { avg: { field: FAAS_BILLED_DURATION } }, + avgTotalMemory: { avg: { field: METRIC_SYSTEM_TOTAL_MEMORY } }, + avgFreeMemory: { avg: { field: METRIC_SYSTEM_FREE_MEMORY } }, + }, + }, + }; + + const response = await apmEventClient.search( + 'ger_serverless_summary', + params + ); + + return { + memoryUsageAvgRate: calcMemoryUsedRate({ + memoryFree: response.aggregations?.avgFreeMemory?.value, + memoryTotal: response.aggregations?.avgTotalMemory?.value, + }), + serverlessFunctionsTotal: response.aggregations?.totalFunctions?.value, + serverlessDurationAvg: response.aggregations?.faasDurationAvg?.value, + billedDurationAvg: response.aggregations?.faasBilledDurationAvg?.value, + }; +} diff --git a/x-pack/plugins/apm/server/routes/metrics/serverless/helper.test.ts b/x-pack/plugins/apm/server/routes/metrics/serverless/helper.test.ts new file mode 100644 index 000000000000..c6927f36a8eb --- /dev/null +++ b/x-pack/plugins/apm/server/routes/metrics/serverless/helper.test.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { calcMemoryUsed, calcMemoryUsedRate } from './helper'; +describe('calcMemoryUsed', () => { + it('returns undefined when memory values are no a number', () => { + [ + { memoryFree: null, memoryTotal: null }, + { memoryFree: undefined, memoryTotal: undefined }, + { memoryFree: 100, memoryTotal: undefined }, + { memoryFree: undefined, memoryTotal: 100 }, + ].forEach(({ memoryFree, memoryTotal }) => { + expect(calcMemoryUsed({ memoryFree, memoryTotal })).toBeUndefined(); + }); + }); + + it('returns correct memory used', () => { + expect(calcMemoryUsed({ memoryFree: 50, memoryTotal: 100 })).toBe(50); + }); +}); + +describe('calcMemoryUsedRate', () => { + it('returns undefined when memory values are no a number', () => { + [ + { memoryFree: null, memoryTotal: null }, + { memoryFree: undefined, memoryTotal: undefined }, + { memoryFree: 100, memoryTotal: undefined }, + { memoryFree: undefined, memoryTotal: 100 }, + ].forEach(({ memoryFree, memoryTotal }) => { + expect(calcMemoryUsedRate({ memoryFree, memoryTotal })).toBeUndefined(); + }); + }); + + it('returns correct memory used rate', () => { + expect(calcMemoryUsedRate({ memoryFree: 50, memoryTotal: 100 })).toBe(0.5); + }); +}); diff --git a/x-pack/plugins/apm/server/routes/metrics/serverless/helper.ts b/x-pack/plugins/apm/server/routes/metrics/serverless/helper.ts new file mode 100644 index 000000000000..0c16ee101c73 --- /dev/null +++ b/x-pack/plugins/apm/server/routes/metrics/serverless/helper.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { isFiniteNumber } from '../../../../common/utils/is_finite_number'; + +export function calcMemoryUsedRate({ + memoryFree, + memoryTotal, +}: { + memoryFree?: number | null; + memoryTotal?: number | null; +}) { + if (!isFiniteNumber(memoryFree) || !isFiniteNumber(memoryTotal)) { + return undefined; + } + + return (memoryTotal - memoryFree) / memoryTotal; +} + +export function calcMemoryUsed({ + memoryFree, + memoryTotal, +}: { + memoryFree?: number | null; + memoryTotal?: number | null; +}) { + if (!isFiniteNumber(memoryFree) || !isFiniteNumber(memoryTotal)) { + return undefined; + } + + return memoryTotal - memoryFree; +} diff --git a/x-pack/plugins/apm/server/routes/metrics/serverless/route.ts b/x-pack/plugins/apm/server/routes/metrics/serverless/route.ts new file mode 100644 index 000000000000..af2a0aa0834f --- /dev/null +++ b/x-pack/plugins/apm/server/routes/metrics/serverless/route.ts @@ -0,0 +1,189 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as t from 'io-ts'; +import { setupRequest } from '../../../lib/helpers/setup_request'; +import { createApmServerRoute } from '../../apm_routes/create_apm_server_route'; +import { environmentRt, kueryRt, rangeRt } from '../../default_api_types'; +import { getServerlessAgentMetricsCharts } from './get_serverless_agent_metrics_chart'; +import { getServerlessActiveInstancesOverview } from './get_active_instances_overview'; +import { getServerlessFunctionsOverview } from './get_serverless_functions_overview'; +import { getServerlessSummary } from './get_serverless_summary'; +import { getActiveInstancesTimeseries } from './get_active_instances_timeseries'; +import { getApmEventClient } from '../../../lib/helpers/get_apm_event_client'; + +const serverlessMetricsChartsRoute = createApmServerRoute({ + endpoint: + 'GET /internal/apm/services/{serviceName}/metrics/serverless/charts', + params: t.type({ + path: t.type({ + serviceName: t.string, + }), + query: t.intersection([ + environmentRt, + kueryRt, + rangeRt, + t.partial({ serverlessId: t.string }), + ]), + }), + options: { tags: ['access:apm'] }, + handler: async ( + resources + ): Promise<{ + charts: Awaited>; + }> => { + const { params } = resources; + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); + + const { serviceName } = params.path; + const { environment, kuery, start, end, serverlessId } = params.query; + + const charts = await getServerlessAgentMetricsCharts({ + environment, + start, + end, + kuery, + config: setup.config, + apmEventClient, + serviceName, + serverlessId, + }); + return { charts }; + }, +}); + +const serverlessMetricsActiveInstancesRoute = createApmServerRoute({ + endpoint: + 'GET /internal/apm/services/{serviceName}/metrics/serverless/active_instances', + params: t.type({ + path: t.type({ + serviceName: t.string, + }), + query: t.intersection([ + environmentRt, + kueryRt, + rangeRt, + t.partial({ serverlessId: t.string }), + ]), + }), + options: { tags: ['access:apm'] }, + handler: async ( + resources + ): Promise<{ + activeInstances: Awaited< + ReturnType + >; + timeseries: Awaited>; + }> => { + const { params } = resources; + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); + + const { serviceName } = params.path; + const { environment, kuery, start, end, serverlessId } = params.query; + + const options = { + environment, + start, + end, + kuery, + setup, + serviceName, + serverlessId, + apmEventClient, + }; + + const [activeInstances, timeseries] = await Promise.all([ + getServerlessActiveInstancesOverview(options), + getActiveInstancesTimeseries({ ...options, config: setup.config }), + ]); + return { activeInstances, timeseries }; + }, +}); + +const serverlessMetricsFunctionsOverviewRoute = createApmServerRoute({ + endpoint: + 'GET /internal/apm/services/{serviceName}/metrics/serverless/functions_overview', + params: t.type({ + path: t.type({ + serviceName: t.string, + }), + query: t.intersection([environmentRt, kueryRt, rangeRt]), + }), + options: { tags: ['access:apm'] }, + handler: async ( + resources + ): Promise<{ + serverlessFunctionsOverview: Awaited< + ReturnType + >; + }> => { + const { params } = resources; + const apmEventClient = await getApmEventClient(resources); + + const { serviceName } = params.path; + const { environment, kuery, start, end } = params.query; + + const serverlessFunctionsOverview = await getServerlessFunctionsOverview({ + environment, + start, + end, + kuery, + apmEventClient, + serviceName, + }); + return { serverlessFunctionsOverview }; + }, +}); + +const serverlessMetricsSummaryRoute = createApmServerRoute({ + endpoint: + 'GET /internal/apm/services/{serviceName}/metrics/serverless/summary', + params: t.type({ + path: t.type({ + serviceName: t.string, + }), + query: t.intersection([ + environmentRt, + kueryRt, + rangeRt, + t.partial({ serverlessId: t.string }), + ]), + }), + options: { tags: ['access:apm'] }, + handler: async ( + resources + ): Promise>> => { + const { params } = resources; + const apmEventClient = await getApmEventClient(resources); + + const { serviceName } = params.path; + const { environment, kuery, start, end, serverlessId } = params.query; + + return getServerlessSummary({ + environment, + start, + end, + kuery, + apmEventClient, + serviceName, + serverlessId, + }); + }, +}); + +export const metricsServerlessRouteRepository = { + ...serverlessMetricsChartsRoute, + ...serverlessMetricsSummaryRoute, + ...serverlessMetricsFunctionsOverviewRoute, + ...serverlessMetricsActiveInstancesRoute, +}; diff --git a/x-pack/plugins/apm/server/routes/observability_overview/get_service_count.ts b/x-pack/plugins/apm/server/routes/observability_overview/get_service_count.ts index ecdf1c792ffe..315f9345bff8 100644 --- a/x-pack/plugins/apm/server/routes/observability_overview/get_service_count.ts +++ b/x-pack/plugins/apm/server/routes/observability_overview/get_service_count.ts @@ -8,22 +8,20 @@ import { rangeQuery } from '@kbn/observability-plugin/server'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; import { SERVICE_NAME } from '../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../lib/helpers/setup_request'; import { getProcessorEventForTransactions } from '../../lib/helpers/transactions'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getServiceCount({ - setup, + apmEventClient, searchAggregatedTransactions, start, end, }: { - setup: Setup; + apmEventClient: APMEventClient; searchAggregatedTransactions: boolean; start: number; end: number; }) { - const { apmEventClient } = setup; - const params = { apm: { events: [ diff --git a/x-pack/plugins/apm/server/routes/observability_overview/get_transactions_per_minute.ts b/x-pack/plugins/apm/server/routes/observability_overview/get_transactions_per_minute.ts index cfc89a7589cd..8f99b6a08ae8 100644 --- a/x-pack/plugins/apm/server/routes/observability_overview/get_transactions_per_minute.ts +++ b/x-pack/plugins/apm/server/routes/observability_overview/get_transactions_per_minute.ts @@ -11,30 +11,28 @@ import { TRANSACTION_REQUEST, } from '../../../common/transaction_types'; import { TRANSACTION_TYPE } from '../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../lib/helpers/setup_request'; import { getDocumentTypeFilterForTransactions, getProcessorEventForTransactions, } from '../../lib/helpers/transactions'; import { calculateThroughputWithRange } from '../../lib/helpers/calculate_throughput'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getTransactionsPerMinute({ - setup, + apmEventClient, bucketSize, searchAggregatedTransactions, start, end, intervalString, }: { - setup: Setup; + apmEventClient: APMEventClient; bucketSize: number; intervalString: string; searchAggregatedTransactions: boolean; start: number; end: number; }) { - const { apmEventClient } = setup; - const { aggregations } = await apmEventClient.search( 'observability_overview_get_transactions_per_minute', { diff --git a/x-pack/plugins/apm/server/routes/observability_overview/has_data.ts b/x-pack/plugins/apm/server/routes/observability_overview/has_data.ts index 384f26b31e70..66436caa7a40 100644 --- a/x-pack/plugins/apm/server/routes/observability_overview/has_data.ts +++ b/x-pack/plugins/apm/server/routes/observability_overview/has_data.ts @@ -6,10 +6,16 @@ */ import { ProcessorEvent } from '@kbn/observability-plugin/common'; -import { Setup } from '../../lib/helpers/setup_request'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; +import { ApmIndicesConfig } from '../settings/apm_indices/get_apm_indices'; -export async function getHasData({ setup }: { setup: Setup }) { - const { apmEventClient } = setup; +export async function getHasData({ + indices, + apmEventClient, +}: { + indices: ApmIndicesConfig; + apmEventClient: APMEventClient; +}) { try { const params = { apm: { @@ -32,12 +38,12 @@ export async function getHasData({ setup }: { setup: Setup }) { ); return { hasData: response.hits.total.value > 0, - indices: setup.indices, + indices, }; } catch (e) { return { hasData: false, - indices: setup.indices, + indices, }; } } diff --git a/x-pack/plugins/apm/server/routes/observability_overview/route.ts b/x-pack/plugins/apm/server/routes/observability_overview/route.ts index 4ed5332801ef..7128d664b0ce 100644 --- a/x-pack/plugins/apm/server/routes/observability_overview/route.ts +++ b/x-pack/plugins/apm/server/routes/observability_overview/route.ts @@ -15,6 +15,7 @@ import { rangeRt } from '../default_api_types'; import { getSearchTransactionsEvents } from '../../lib/helpers/transactions'; import { withApmSpan } from '../../utils/with_apm_span'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; +import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; const observabilityOverviewHasDataRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/observability_overview/has_data', @@ -25,8 +26,11 @@ const observabilityOverviewHasDataRoute = createApmServerRoute({ hasData: boolean; indices: import('./../../../../observability/common/typings').ApmIndicesConfig; }> => { - const setup = await setupRequest(resources); - return await getHasData({ setup }); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); + return await getHasData({ indices: setup.indices, apmEventClient }); }, }); @@ -47,11 +51,14 @@ const observabilityOverviewRoute = createApmServerRoute({ | { value: undefined; timeseries: never[] } | { value: number; timeseries: Array<{ x: number; y: number | null }> }; }> => { - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { bucketSize, intervalString, start, end } = resources.params.query; const searchAggregatedTransactions = await getSearchTransactionsEvents({ - apmEventClient: setup.apmEventClient, + apmEventClient, config: setup.config, start, end, @@ -71,13 +78,13 @@ const observabilityOverviewRoute = createApmServerRoute({ }> => { const [serviceCount, transactionPerMinute] = await Promise.all([ getServiceCount({ - setup, + apmEventClient, searchAggregatedTransactions, start, end, }), getTransactionsPerMinute({ - setup, + apmEventClient, bucketSize, searchAggregatedTransactions, start, diff --git a/x-pack/plugins/apm/server/routes/service_groups/get_services_counts.ts b/x-pack/plugins/apm/server/routes/service_groups/get_services_counts.ts index c820cdd8445f..b261c2a4cfd4 100644 --- a/x-pack/plugins/apm/server/routes/service_groups/get_services_counts.ts +++ b/x-pack/plugins/apm/server/routes/service_groups/get_services_counts.ts @@ -8,23 +8,21 @@ import { ProcessorEvent } from '@kbn/observability-plugin/common'; import { rangeQuery, kqlQuery } from '@kbn/observability-plugin/server'; import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; -import { Setup } from '../../lib/helpers/setup_request'; import { SERVICE_NAME } from '../../../common/elasticsearch_fieldnames'; import { SavedServiceGroup } from '../../../common/service_groups'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getServicesCounts({ - setup, + apmEventClient, start, end, serviceGroups, }: { - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; serviceGroups: SavedServiceGroup[]; }) { - const { apmEventClient } = setup; - const serviceGroupsKueryMap: Record = serviceGroups.reduce((acc, sg) => { return { diff --git a/x-pack/plugins/apm/server/routes/service_groups/lookup_services.ts b/x-pack/plugins/apm/server/routes/service_groups/lookup_services.ts index 236b3e20222c..4acfb4976262 100644 --- a/x-pack/plugins/apm/server/routes/service_groups/lookup_services.ts +++ b/x-pack/plugins/apm/server/routes/service_groups/lookup_services.ts @@ -13,23 +13,21 @@ import { SERVICE_ENVIRONMENT, SERVICE_NAME, } from '../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../lib/helpers/setup_request'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function lookupServices({ - setup, + apmEventClient, kuery, start, end, maxNumberOfServices, }: { - setup: Setup; + apmEventClient: APMEventClient; kuery: string; start: number; end: number; maxNumberOfServices: number; }) { - const { apmEventClient } = setup; - const response = await apmEventClient.search('lookup_services', { apm: { events: [ diff --git a/x-pack/plugins/apm/server/routes/service_groups/route.ts b/x-pack/plugins/apm/server/routes/service_groups/route.ts index 4430aaae760e..4da84e684869 100644 --- a/x-pack/plugins/apm/server/routes/service_groups/route.ts +++ b/x-pack/plugins/apm/server/routes/service_groups/route.ts @@ -7,7 +7,6 @@ import * as t from 'io-ts'; import { apmServiceGroupMaxNumberOfServices } from '@kbn/observability-plugin/common'; -import { setupRequest } from '../../lib/helpers/setup_request'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { kueryRt, rangeRt } from '../default_api_types'; import { getServiceGroups } from './get_service_groups'; @@ -17,6 +16,7 @@ import { deleteServiceGroup } from './delete_service_group'; import { lookupServices } from './lookup_services'; import { SavedServiceGroup } from '../../../common/service_groups'; import { getServicesCounts } from './get_services_counts'; +import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; const serviceGroupsRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/service-groups', @@ -57,7 +57,7 @@ const serviceGroupsWithServiceCountRoute = createApmServerRoute({ query: { start, end }, } = params; - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const serviceGroups = await getServiceGroups({ savedObjectsClient, @@ -65,7 +65,7 @@ const serviceGroupsWithServiceCountRoute = createApmServerRoute({ return { servicesCounts: await getServicesCounts({ - setup, + apmEventClient, serviceGroups, start, end, @@ -164,12 +164,12 @@ const serviceGroupServicesRoute = createApmServerRoute({ const { uiSettings: { client: uiSettingsClient }, } = await context.core; - const [setup, maxNumberOfServices] = await Promise.all([ - setupRequest(resources), + const [apmEventClient, maxNumberOfServices] = await Promise.all([ + getApmEventClient(resources), uiSettingsClient.get(apmServiceGroupMaxNumberOfServices), ]); const items = await lookupServices({ - setup, + apmEventClient, kuery, start, end, diff --git a/x-pack/plugins/apm/server/routes/service_map/fetch_service_paths_from_trace_ids.ts b/x-pack/plugins/apm/server/routes/service_map/fetch_service_paths_from_trace_ids.ts index e5d97708ae17..5d044846da6e 100644 --- a/x-pack/plugins/apm/server/routes/service_map/fetch_service_paths_from_trace_ids.ts +++ b/x-pack/plugins/apm/server/routes/service_map/fetch_service_paths_from_trace_ids.ts @@ -13,16 +13,14 @@ import { ExternalConnectionNode, ServiceConnectionNode, } from '../../../common/service_map'; -import { Setup } from '../../lib/helpers/setup_request'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function fetchServicePathsFromTraceIds( - setup: Setup, + apmEventClient: APMEventClient, traceIds: string[], start: number, end: number ) { - const { apmEventClient } = setup; - // make sure there's a range so ES can skip shards const dayInMs = 24 * 60 * 60 * 1000; const startRange = start - dayInMs; diff --git a/x-pack/plugins/apm/server/routes/service_map/get_service_map.ts b/x-pack/plugins/apm/server/routes/service_map/get_service_map.ts index 05424b4805d8..36a4218ae6e4 100644 --- a/x-pack/plugins/apm/server/routes/service_map/get_service_map.ts +++ b/x-pack/plugins/apm/server/routes/service_map/get_service_map.ts @@ -28,9 +28,11 @@ import { ENVIRONMENT_ALL } from '../../../common/environment_filter_values'; import { getProcessorEventForTransactions } from '../../lib/helpers/transactions'; import { ServiceGroup } from '../../../common/service_groups'; import { serviceGroupQuery } from '../../lib/service_group_query'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export interface IEnvOptions { setup: Setup; + apmEventClient: APMEventClient; serviceNames?: string[]; environment: string; searchAggregatedTransactions: boolean; @@ -42,6 +44,7 @@ export interface IEnvOptions { async function getConnectionData({ setup, + apmEventClient, serviceNames, environment, start, @@ -50,7 +53,8 @@ async function getConnectionData({ }: IEnvOptions) { return withApmSpan('get_service_map_connections', async () => { const { traceIds } = await getTraceSampleIds({ - setup, + config: setup.config, + apmEventClient, serviceNames, environment, start, @@ -75,7 +79,7 @@ async function getConnectionData({ Promise.all( chunks.map((traceIdsChunk) => getServiceMapFromTraceIds({ - setup, + apmEventClient, traceIds: traceIdsChunk, start, end, @@ -100,7 +104,7 @@ async function getServicesData( ) { const { environment, - setup, + apmEventClient, searchAggregatedTransactions, start, end, @@ -146,8 +150,6 @@ async function getServicesData( }, }; - const { apmEventClient } = setup; - const response = await apmEventClient.search( 'get_service_stats_for_service_map', params diff --git a/x-pack/plugins/apm/server/routes/service_map/get_service_map_dependency_node_info.ts b/x-pack/plugins/apm/server/routes/service_map/get_service_map_dependency_node_info.ts index 8d2210827918..241672a7fa4d 100644 --- a/x-pack/plugins/apm/server/routes/service_map/get_service_map_dependency_node_info.ts +++ b/x-pack/plugins/apm/server/routes/service_map/get_service_map_dependency_node_info.ts @@ -17,14 +17,14 @@ import { EventOutcome } from '../../../common/event_outcome'; import { environmentQuery } from '../../../common/utils/environment_query'; import { withApmSpan } from '../../utils/with_apm_span'; import { calculateThroughputWithRange } from '../../lib/helpers/calculate_throughput'; -import { Setup } from '../../lib/helpers/setup_request'; import { getBucketSize } from '../../lib/helpers/get_bucket_size'; import { getFailedTransactionRateTimeSeries } from '../../lib/helpers/transaction_error_rate'; import { NodeStats } from '../../../common/service_map'; import { getOffsetInMs } from '../../../common/utils/get_offset_in_ms'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; interface Options { - setup: Setup; + apmEventClient: APMEventClient; environment: string; dependencyName: string; start: number; @@ -35,13 +35,12 @@ interface Options { export function getServiceMapDependencyNodeInfo({ environment, dependencyName, - setup, + apmEventClient, start, end, offset, }: Options): Promise { return withApmSpan('get_service_map_dependency_node_stats', async () => { - const { apmEventClient } = setup; const { offsetInMs, startWithOffset, endWithOffset } = getOffsetInMs({ start, end, diff --git a/x-pack/plugins/apm/server/routes/service_map/get_service_map_from_trace_ids.ts b/x-pack/plugins/apm/server/routes/service_map/get_service_map_from_trace_ids.ts index 6a61a514881b..21daa774152f 100644 --- a/x-pack/plugins/apm/server/routes/service_map/get_service_map_from_trace_ids.ts +++ b/x-pack/plugins/apm/server/routes/service_map/get_service_map_from_trace_ids.ts @@ -7,7 +7,7 @@ import { find, uniqBy } from 'lodash'; import { Connection, ConnectionNode } from '../../../common/service_map'; -import { Setup } from '../../lib/helpers/setup_request'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; import { fetchServicePathsFromTraceIds } from './fetch_service_paths_from_trace_ids'; export function getConnections({ @@ -40,18 +40,18 @@ export function getConnections({ } export async function getServiceMapFromTraceIds({ - setup, + apmEventClient, traceIds, start, end, }: { - setup: Setup; + apmEventClient: APMEventClient; traceIds: string[]; start: number; end: number; }) { const serviceMapFromTraceIdsScriptResponse = - await fetchServicePathsFromTraceIds(setup, traceIds, start, end); + await fetchServicePathsFromTraceIds(apmEventClient, traceIds, start, end); const serviceMapScriptedAggValue = serviceMapFromTraceIdsScriptResponse.aggregations?.service_map.value; diff --git a/x-pack/plugins/apm/server/routes/service_map/get_service_map_service_node_info.ts b/x-pack/plugins/apm/server/routes/service_map/get_service_map_service_node_info.ts index 72e8b3b267cf..7f4b260b8330 100644 --- a/x-pack/plugins/apm/server/routes/service_map/get_service_map_service_node_info.ts +++ b/x-pack/plugins/apm/server/routes/service_map/get_service_map_service_node_info.ts @@ -24,7 +24,6 @@ import { import { environmentQuery } from '../../../common/utils/environment_query'; import { getOffsetInMs } from '../../../common/utils/get_offset_in_ms'; import { getBucketSizeForAggregatedTransactions } from '../../lib/helpers/get_bucket_size_for_aggregated_transactions'; -import { Setup } from '../../lib/helpers/setup_request'; import { getDocumentTypeFilterForTransactions, getDurationFieldForTransactions, @@ -36,9 +35,10 @@ import { percentCgroupMemoryUsedScript, percentSystemMemoryUsedScript, } from '../metrics/by_agent/shared/memory'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; interface Options { - setup: Setup; + apmEventClient: APMEventClient; environment: string; serviceName: string; searchAggregatedTransactions: boolean; @@ -53,7 +53,7 @@ interface TaskParameters { searchAggregatedTransactions: boolean; minutes: number; serviceName: string; - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; intervalString: string; @@ -65,7 +65,7 @@ interface TaskParameters { export function getServiceMapServiceNodeInfo({ environment, serviceName, - setup, + apmEventClient, searchAggregatedTransactions, start, end, @@ -99,7 +99,7 @@ export function getServiceMapServiceNodeInfo({ searchAggregatedTransactions, minutes, serviceName, - setup, + apmEventClient, start: startWithOffset, end: endWithOffset, intervalString, @@ -125,7 +125,7 @@ export function getServiceMapServiceNodeInfo({ } async function getFailedTransactionsRateStats({ - setup, + apmEventClient, serviceName, environment, searchAggregatedTransactions, @@ -137,7 +137,7 @@ async function getFailedTransactionsRateStats({ return withApmSpan('get_error_rate_for_service_map_node', async () => { const { average, timeseries } = await getFailedTransactionRate({ environment, - setup, + apmEventClient, serviceName, searchAggregatedTransactions, start, @@ -154,7 +154,7 @@ async function getFailedTransactionsRateStats({ } async function getTransactionStats({ - setup, + apmEventClient, filter, minutes, searchAggregatedTransactions, @@ -163,8 +163,6 @@ async function getTransactionStats({ intervalString, offsetInMs, }: TaskParameters): Promise { - const { apmEventClient } = setup; - const durationField = getDurationFieldForTransactions( searchAggregatedTransactions ); @@ -241,15 +239,13 @@ async function getTransactionStats({ } async function getCpuStats({ - setup, + apmEventClient, filter, intervalString, start, end, offsetInMs, }: TaskParameters): Promise { - const { apmEventClient } = setup; - const response = await apmEventClient.search( 'get_avg_cpu_usage_for_service_map_node', { @@ -295,7 +291,7 @@ async function getCpuStats({ } function getMemoryStats({ - setup, + apmEventClient, filter, intervalString, start, @@ -303,8 +299,6 @@ function getMemoryStats({ offsetInMs, }: TaskParameters) { return withApmSpan('get_memory_stats_for_service_map_node', async () => { - const { apmEventClient } = setup; - const getMemoryUsage = async ({ additionalFilters, script, diff --git a/x-pack/plugins/apm/server/routes/service_map/get_trace_sample_ids.ts b/x-pack/plugins/apm/server/routes/service_map/get_trace_sample_ids.ts index 76d15d06da0b..2289d3df2648 100644 --- a/x-pack/plugins/apm/server/routes/service_map/get_trace_sample_ids.ts +++ b/x-pack/plugins/apm/server/routes/service_map/get_trace_sample_ids.ts @@ -18,29 +18,30 @@ import { } from '../../../common/elasticsearch_fieldnames'; import { SERVICE_MAP_TIMEOUT_ERROR } from '../../../common/service_map'; import { environmentQuery } from '../../../common/utils/environment_query'; -import { Setup } from '../../lib/helpers/setup_request'; import { serviceGroupQuery } from '../../lib/service_group_query'; import { ServiceGroup } from '../../../common/service_groups'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; +import { APMConfig } from '../..'; const MAX_TRACES_TO_INSPECT = 1000; export async function getTraceSampleIds({ serviceNames, environment, - setup, + config, + apmEventClient, start, end, serviceGroup, }: { serviceNames?: string[]; environment: string; - setup: Setup; + config: APMConfig; + apmEventClient: APMEventClient; start: number; end: number; serviceGroup: ServiceGroup | null; }) { - const { apmEventClient, config } = setup; - const query = { bool: { filter: [...rangeQuery(start, end), ...serviceGroupQuery(serviceGroup)], diff --git a/x-pack/plugins/apm/server/routes/service_map/route.ts b/x-pack/plugins/apm/server/routes/service_map/route.ts index c2de9bf956a8..f833ce195c44 100644 --- a/x-pack/plugins/apm/server/routes/service_map/route.ts +++ b/x-pack/plugins/apm/server/routes/service_map/route.ts @@ -21,6 +21,7 @@ import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { environmentRt, rangeRt } from '../default_api_types'; import { getServiceGroup } from '../service_groups/get_service_group'; import { offsetRt } from '../../../common/comparison_rt'; +import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; const serviceMapRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/service-map', @@ -115,21 +116,23 @@ const serviceMapRoute = createApmServerRoute({ savedObjects: { client: savedObjectsClient }, uiSettings: { client: uiSettingsClient }, } = await context.core; - const [setup, serviceGroup, maxNumberOfServices] = await Promise.all([ - setupRequest(resources), - serviceGroupId - ? getServiceGroup({ - savedObjectsClient, - serviceGroupId, - }) - : Promise.resolve(null), - uiSettingsClient.get(apmServiceGroupMaxNumberOfServices), - ]); + const [setup, apmEventClient, serviceGroup, maxNumberOfServices] = + await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + serviceGroupId + ? getServiceGroup({ + savedObjectsClient, + serviceGroupId, + }) + : Promise.resolve(null), + uiSettingsClient.get(apmServiceGroupMaxNumberOfServices), + ]); const serviceNames = compact([serviceName]); const searchAggregatedTransactions = await getSearchTransactionsEvents({ - apmEventClient: setup.apmEventClient, + apmEventClient, config: setup.config, start, end, @@ -137,6 +140,7 @@ const serviceMapRoute = createApmServerRoute({ }); return getServiceMap({ setup, + apmEventClient, serviceNames, environment, searchAggregatedTransactions, @@ -176,7 +180,10 @@ const serviceMapServiceNodeRoute = createApmServerRoute({ if (!isActivePlatinumLicense(licensingContext.license)) { throw Boom.forbidden(invalidLicenseMessage); } - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { path: { serviceName }, @@ -184,7 +191,7 @@ const serviceMapServiceNodeRoute = createApmServerRoute({ } = params; const searchAggregatedTransactions = await getSearchTransactionsEvents({ - apmEventClient: setup.apmEventClient, + apmEventClient, config: setup.config, start, end, @@ -193,7 +200,7 @@ const serviceMapServiceNodeRoute = createApmServerRoute({ const commonProps = { environment, - setup, + apmEventClient, serviceName, searchAggregatedTransactions, start, @@ -239,13 +246,19 @@ const serviceMapDependencyNodeRoute = createApmServerRoute({ if (!isActivePlatinumLicense(licensingContext.license)) { throw Boom.forbidden(invalidLicenseMessage); } - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { query: { dependencyName, environment, start, end, offset }, } = params; - const commonProps = { environment, setup, dependencyName, start, end }; + const commonProps = { + environment, + apmEventClient, + dependencyName, + start, + end, + }; const [currentPeriod, previousPeriod] = await Promise.all([ getServiceMapDependencyNodeInfo(commonProps), diff --git a/x-pack/plugins/apm/server/routes/services/annotations/get_derived_service_annotations.test.ts b/x-pack/plugins/apm/server/routes/services/annotations/get_derived_service_annotations.test.ts index d763f1f913c4..fef8a878f2e3 100644 --- a/x-pack/plugins/apm/server/routes/services/annotations/get_derived_service_annotations.test.ts +++ b/x-pack/plugins/apm/server/routes/services/annotations/get_derived_service_annotations.test.ts @@ -26,9 +26,9 @@ describe('getDerivedServiceAnnotations', () => { describe('with 0 versions', () => { it('returns no annotations', async () => { mock = await inspectSearchParams( - (setup) => + (setup, apmEventClient) => getDerivedServiceAnnotations({ - setup, + apmEventClient, serviceName: 'foo', environment: 'bar', searchAggregatedTransactions: false, @@ -54,9 +54,9 @@ describe('getDerivedServiceAnnotations', () => { describe('with 1 version', () => { it('returns no annotations', async () => { mock = await inspectSearchParams( - (setup) => + (setup, apmEventClient) => getDerivedServiceAnnotations({ - setup, + apmEventClient, serviceName: 'foo', environment: 'bar', searchAggregatedTransactions: false, @@ -87,9 +87,9 @@ describe('getDerivedServiceAnnotations', () => { versionsFirstSeen, ]; mock = await inspectSearchParams( - (setup) => + (setup, apmEventClient) => getDerivedServiceAnnotations({ - setup, + apmEventClient, serviceName: 'foo', environment: 'bar', searchAggregatedTransactions: false, diff --git a/x-pack/plugins/apm/server/routes/services/annotations/get_derived_service_annotations.ts b/x-pack/plugins/apm/server/routes/services/annotations/get_derived_service_annotations.ts index a718e9932661..c98bc9dbeb17 100644 --- a/x-pack/plugins/apm/server/routes/services/annotations/get_derived_service_annotations.ts +++ b/x-pack/plugins/apm/server/routes/services/annotations/get_derived_service_annotations.ts @@ -18,10 +18,10 @@ import { getDocumentTypeFilterForTransactions, getProcessorEventForTransactions, } from '../../../lib/helpers/transactions'; -import { Setup } from '../../../lib/helpers/setup_request'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; export async function getDerivedServiceAnnotations({ - setup, + apmEventClient, serviceName, environment, searchAggregatedTransactions, @@ -30,13 +30,11 @@ export async function getDerivedServiceAnnotations({ }: { serviceName: string; environment: string; - setup: Setup; + apmEventClient: APMEventClient; searchAggregatedTransactions: boolean; start: number; end: number; }) { - const { apmEventClient } = setup; - const filter: ESFilter[] = [ { term: { [SERVICE_NAME]: serviceName } }, ...getDocumentTypeFilterForTransactions(searchAggregatedTransactions), diff --git a/x-pack/plugins/apm/server/routes/services/annotations/index.test.ts b/x-pack/plugins/apm/server/routes/services/annotations/index.test.ts index a044648bd7a4..729a2c16dd65 100644 --- a/x-pack/plugins/apm/server/routes/services/annotations/index.test.ts +++ b/x-pack/plugins/apm/server/routes/services/annotations/index.test.ts @@ -10,11 +10,11 @@ import { } from '@kbn/observability-plugin/server'; import { ElasticsearchClient, Logger } from '@kbn/core/server'; import { getServiceAnnotations } from '.'; -import { Setup } from '../../../lib/helpers/setup_request'; import * as GetDerivedServiceAnnotations from './get_derived_service_annotations'; import * as GetStoredAnnotations from './get_stored_annotations'; import { Annotation, AnnotationType } from '../../../../common/annotations'; import { errors } from '@elastic/elasticsearch'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; describe('getServiceAnnotations', () => { const storedAnnotations = [ @@ -60,7 +60,7 @@ describe('getServiceAnnotations', () => { client: {} as ElasticsearchClient, logger: {} as Logger, annotationsClient: {} as ScopedAnnotationsClient, - setup: {} as Setup, + apmEventClient: {} as APMEventClient, }); expect(annotations).toEqual({ annotations: storedAnnotations, @@ -96,7 +96,7 @@ describe('getServiceAnnotations', () => { client: {} as ElasticsearchClient, logger: {} as Logger, annotationsClient: {} as ScopedAnnotationsClient, - setup: {} as Setup, + apmEventClient: {} as APMEventClient, }); expect(annotations).toEqual({ annotations: storedAnnotations, @@ -133,7 +133,7 @@ describe('getServiceAnnotations', () => { client: {} as ElasticsearchClient, logger: {} as Logger, annotationsClient: {} as ScopedAnnotationsClient, - setup: {} as Setup, + apmEventClient: {} as APMEventClient, }) ).rejects.toThrow('BOOM'); }); @@ -171,7 +171,7 @@ describe('getServiceAnnotations', () => { client: {} as ElasticsearchClient, logger: {} as Logger, annotationsClient: {} as ScopedAnnotationsClient, - setup: {} as Setup, + apmEventClient: {} as APMEventClient, }); expect(annotations).toEqual({ annotations: [] }); }); @@ -206,7 +206,7 @@ describe('getServiceAnnotations', () => { client: {} as ElasticsearchClient, logger: {} as Logger, annotationsClient: {} as ScopedAnnotationsClient, - setup: {} as Setup, + apmEventClient: {} as APMEventClient, }) ).rejects.toThrow('BOOM'); }); @@ -240,7 +240,7 @@ describe('getServiceAnnotations', () => { client: {} as ElasticsearchClient, logger: {} as Logger, annotationsClient: {} as ScopedAnnotationsClient, - setup: {} as Setup, + apmEventClient: {} as APMEventClient, }); expect(annotations).toEqual({ annotations: storedAnnotations, diff --git a/x-pack/plugins/apm/server/routes/services/annotations/index.ts b/x-pack/plugins/apm/server/routes/services/annotations/index.ts index cc103f2fe6ce..41f199f456d7 100644 --- a/x-pack/plugins/apm/server/routes/services/annotations/index.ts +++ b/x-pack/plugins/apm/server/routes/services/annotations/index.ts @@ -7,12 +7,12 @@ import { ElasticsearchClient, Logger } from '@kbn/core/server'; import { ScopedAnnotationsClient } from '@kbn/observability-plugin/server'; -import { Setup } from '../../../lib/helpers/setup_request'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; import { getDerivedServiceAnnotations } from './get_derived_service_annotations'; import { getStoredAnnotations } from './get_stored_annotations'; export async function getServiceAnnotations({ - setup, + apmEventClient, searchAggregatedTransactions, serviceName, environment, @@ -24,7 +24,7 @@ export async function getServiceAnnotations({ }: { serviceName: string; environment: string; - setup: Setup; + apmEventClient: APMEventClient; searchAggregatedTransactions: boolean; annotationsClient?: ScopedAnnotationsClient; client: ElasticsearchClient; @@ -38,7 +38,7 @@ export async function getServiceAnnotations({ // start fetching derived annotations (based on transactions), but don't wait on it // it will likely be significantly slower than the stored annotations const derivedAnnotationsPromise = getDerivedServiceAnnotations({ - setup, + apmEventClient, serviceName, environment, searchAggregatedTransactions, diff --git a/x-pack/plugins/apm/server/routes/services/get_service_agent.ts b/x-pack/plugins/apm/server/routes/services/get_service_agent.ts index 78b68f3a88ba..e848dd9befbf 100644 --- a/x-pack/plugins/apm/server/routes/services/get_service_agent.ts +++ b/x-pack/plugins/apm/server/routes/services/get_service_agent.ts @@ -12,7 +12,7 @@ import { SERVICE_NAME, SERVICE_RUNTIME_NAME, } from '../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../lib/helpers/setup_request'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; interface ServiceAgent { agent?: { @@ -27,17 +27,15 @@ interface ServiceAgent { export async function getServiceAgent({ serviceName, - setup, + apmEventClient, start, end, }: { serviceName: string; - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; }) { - const { apmEventClient } = setup; - const params = { terminate_after: 1, apm: { diff --git a/x-pack/plugins/apm/server/routes/services/get_service_dependencies.ts b/x-pack/plugins/apm/server/routes/services/get_service_dependencies.ts index cf60502e9861..e0878a9361f3 100644 --- a/x-pack/plugins/apm/server/routes/services/get_service_dependencies.ts +++ b/x-pack/plugins/apm/server/routes/services/get_service_dependencies.ts @@ -9,10 +9,10 @@ import { SERVICE_NAME } from '../../../common/elasticsearch_fieldnames'; import { environmentQuery } from '../../../common/utils/environment_query'; import { getConnectionStats } from '../../lib/connections/get_connection_stats'; import { getConnectionStatsItemsWithRelativeImpact } from '../../lib/connections/get_connection_stats/get_connection_stats_items_with_relative_impact'; -import { Setup } from '../../lib/helpers/setup_request'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getServiceDependencies({ - setup, + apmEventClient, start, end, serviceName, @@ -20,7 +20,7 @@ export async function getServiceDependencies({ environment, offset, }: { - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; serviceName: string; @@ -29,7 +29,7 @@ export async function getServiceDependencies({ offset?: string; }) { const statsItems = await getConnectionStats({ - setup, + apmEventClient, start, end, numBuckets, diff --git a/x-pack/plugins/apm/server/routes/services/get_service_dependencies_breakdown.ts b/x-pack/plugins/apm/server/routes/services/get_service_dependencies_breakdown.ts index 4fc7667fa234..8b940b8525b6 100644 --- a/x-pack/plugins/apm/server/routes/services/get_service_dependencies_breakdown.ts +++ b/x-pack/plugins/apm/server/routes/services/get_service_dependencies_breakdown.ts @@ -9,18 +9,18 @@ import { kqlQuery } from '@kbn/observability-plugin/server'; import { getNodeName } from '../../../common/connections'; import { SERVICE_NAME } from '../../../common/elasticsearch_fieldnames'; import { environmentQuery } from '../../../common/utils/environment_query'; -import { Setup } from '../../lib/helpers/setup_request'; import { getConnectionStats } from '../../lib/connections/get_connection_stats'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getServiceDependenciesBreakdown({ - setup, + apmEventClient, start, end, serviceName, environment, kuery, }: { - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; serviceName: string; @@ -28,7 +28,7 @@ export async function getServiceDependenciesBreakdown({ kuery: string; }) { const items = await getConnectionStats({ - setup, + apmEventClient, start, end, numBuckets: 100, diff --git a/x-pack/plugins/apm/server/routes/services/get_service_instance_metadata_details.ts b/x-pack/plugins/apm/server/routes/services/get_service_instance_metadata_details.ts index c69ea36d3f23..22893a5acefd 100644 --- a/x-pack/plugins/apm/server/routes/services/get_service_instance_metadata_details.ts +++ b/x-pack/plugins/apm/server/routes/services/get_service_instance_metadata_details.ts @@ -12,27 +12,26 @@ import { SERVICE_NAME, SERVICE_NODE_NAME, } from '../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../lib/helpers/setup_request'; import { maybe } from '../../../common/utils/maybe'; import { getDocumentTypeFilterForTransactions, getProcessorEventForTransactions, } from '../../lib/helpers/transactions'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getServiceInstanceMetadataDetails({ serviceName, serviceNodeName, - setup, + apmEventClient, start, end, }: { serviceName: string; serviceNodeName: string; - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; }) { - const { apmEventClient } = setup; const filter = [ { term: { [SERVICE_NAME]: serviceName } }, { term: { [SERVICE_NODE_NAME]: serviceNodeName } }, diff --git a/x-pack/plugins/apm/server/routes/services/get_service_instances/detailed_statistics.ts b/x-pack/plugins/apm/server/routes/services/get_service_instances/detailed_statistics.ts index 01e82c6d57c7..2d23345c3ba1 100644 --- a/x-pack/plugins/apm/server/routes/services/get_service_instances/detailed_statistics.ts +++ b/x-pack/plugins/apm/server/routes/services/get_service_instances/detailed_statistics.ts @@ -11,15 +11,15 @@ import { Coordinate } from '../../../../typings/timeseries'; import { LatencyAggregationType } from '../../../../common/latency_aggregation_types'; import { joinByKey } from '../../../../common/utils/join_by_key'; import { withApmSpan } from '../../../utils/with_apm_span'; -import { Setup } from '../../../lib/helpers/setup_request'; import { getServiceInstancesSystemMetricStatistics } from './get_service_instances_system_metric_statistics'; import { getServiceInstancesTransactionStatistics } from './get_service_instances_transaction_statistics'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; interface ServiceInstanceDetailedStatisticsParams { environment: string; kuery: string; latencyAggregationType: LatencyAggregationType; - setup: Setup; + apmEventClient: APMEventClient; serviceName: string; transactionType: string; searchAggregatedTransactions: boolean; @@ -67,7 +67,7 @@ export async function getServiceInstancesDetailedStatisticsPeriods({ environment, kuery, latencyAggregationType, - setup, + apmEventClient, serviceName, transactionType, searchAggregatedTransactions, @@ -80,7 +80,7 @@ export async function getServiceInstancesDetailedStatisticsPeriods({ environment: string; kuery: string; latencyAggregationType: LatencyAggregationType; - setup: Setup; + apmEventClient: APMEventClient; serviceName: string; transactionType: string; searchAggregatedTransactions: boolean; @@ -97,7 +97,7 @@ export async function getServiceInstancesDetailedStatisticsPeriods({ environment, kuery, latencyAggregationType, - setup, + apmEventClient, serviceName, transactionType, searchAggregatedTransactions, diff --git a/x-pack/plugins/apm/server/routes/services/get_service_instances/get_service_instances_system_metric_statistics.ts b/x-pack/plugins/apm/server/routes/services/get_service_instances/get_service_instances_system_metric_statistics.ts index 7e899f4140c1..71e1b2f7658c 100644 --- a/x-pack/plugins/apm/server/routes/services/get_service_instances/get_service_instances_system_metric_statistics.ts +++ b/x-pack/plugins/apm/server/routes/services/get_service_instances/get_service_instances_system_metric_statistics.ts @@ -20,7 +20,7 @@ import { SERVICE_NODE_NAME_MISSING } from '../../../../common/service_nodes'; import { Coordinate } from '../../../../typings/timeseries'; import { environmentQuery } from '../../../../common/utils/environment_query'; import { getBucketSize } from '../../../lib/helpers/get_bucket_size'; -import { Setup } from '../../../lib/helpers/setup_request'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; import { percentCgroupMemoryUsedScript, percentSystemMemoryUsedScript, @@ -48,7 +48,7 @@ export async function getServiceInstancesSystemMetricStatistics< >({ environment, kuery, - setup, + apmEventClient, serviceName, size, start, @@ -58,7 +58,7 @@ export async function getServiceInstancesSystemMetricStatistics< isComparisonSearch, offset, }: { - setup: Setup; + apmEventClient: APMEventClient; serviceName: string; start: number; end: number; @@ -70,8 +70,6 @@ export async function getServiceInstancesSystemMetricStatistics< isComparisonSearch: T; offset?: string; }): Promise>> { - const { apmEventClient } = setup; - const { startWithOffset, endWithOffset } = getOffsetInMs({ start, end, diff --git a/x-pack/plugins/apm/server/routes/services/get_service_instances/get_service_instances_transaction_statistics.ts b/x-pack/plugins/apm/server/routes/services/get_service_instances/get_service_instances_transaction_statistics.ts index 464b4c07ec58..8bff6b47308b 100644 --- a/x-pack/plugins/apm/server/routes/services/get_service_instances/get_service_instances_transaction_statistics.ts +++ b/x-pack/plugins/apm/server/routes/services/get_service_instances/get_service_instances_transaction_statistics.ts @@ -27,8 +27,8 @@ import { getLatencyAggregation, getLatencyValue, } from '../../../lib/helpers/latency_aggregation_type'; -import { Setup } from '../../../lib/helpers/setup_request'; import { getOffsetInMs } from '../../../../common/utils/get_offset_in_ms'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; interface ServiceInstanceTransactionPrimaryStatistics { serviceNodeName: string; @@ -54,7 +54,7 @@ export async function getServiceInstancesTransactionStatistics< environment, kuery, latencyAggregationType, - setup, + apmEventClient, transactionType, serviceName, size, @@ -67,7 +67,7 @@ export async function getServiceInstancesTransactionStatistics< offset, }: { latencyAggregationType: LatencyAggregationType; - setup: Setup; + apmEventClient: APMEventClient; serviceName: string; transactionType: string; searchAggregatedTransactions: boolean; @@ -81,8 +81,6 @@ export async function getServiceInstancesTransactionStatistics< numBuckets?: number; offset?: string; }): Promise>> { - const { apmEventClient } = setup; - const { startWithOffset, endWithOffset } = getOffsetInMs({ start, end, diff --git a/x-pack/plugins/apm/server/routes/services/get_service_instances/main_statistics.ts b/x-pack/plugins/apm/server/routes/services/get_service_instances/main_statistics.ts index a09b105cb5a7..095f3c981a68 100644 --- a/x-pack/plugins/apm/server/routes/services/get_service_instances/main_statistics.ts +++ b/x-pack/plugins/apm/server/routes/services/get_service_instances/main_statistics.ts @@ -8,7 +8,7 @@ import { LatencyAggregationType } from '../../../../common/latency_aggregation_types'; import { joinByKey } from '../../../../common/utils/join_by_key'; import { withApmSpan } from '../../../utils/with_apm_span'; -import { Setup } from '../../../lib/helpers/setup_request'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; import { getServiceInstancesSystemMetricStatistics } from './get_service_instances_system_metric_statistics'; import { getServiceInstancesTransactionStatistics } from './get_service_instances_transaction_statistics'; @@ -16,7 +16,7 @@ interface ServiceInstanceMainStatisticsParams { environment: string; kuery: string; latencyAggregationType: LatencyAggregationType; - setup: Setup; + apmEventClient: APMEventClient; serviceName: string; transactionType: string; searchAggregatedTransactions: boolean; diff --git a/x-pack/plugins/apm/server/routes/services/get_service_metadata_details.ts b/x-pack/plugins/apm/server/routes/services/get_service_metadata_details.ts index a7014d65b718..40e48101784c 100644 --- a/x-pack/plugins/apm/server/routes/services/get_service_metadata_details.ts +++ b/x-pack/plugins/apm/server/routes/services/get_service_metadata_details.ts @@ -28,7 +28,7 @@ import { import { ContainerType } from '../../../common/service_metadata'; import { TransactionRaw } from '../../../typings/es_schemas/raw/transaction_raw'; import { getProcessorEventForTransactions } from '../../lib/helpers/transactions'; -import { Setup } from '../../lib/helpers/setup_request'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; import { should } from './get_service_metadata_icons'; type ServiceMetadataDetailsRaw = Pick< @@ -78,19 +78,17 @@ export interface ServiceMetadataDetails { export async function getServiceMetadataDetails({ serviceName, - setup, + apmEventClient, searchAggregatedTransactions, start, end, }: { serviceName: string; - setup: Setup; + apmEventClient: APMEventClient; searchAggregatedTransactions: boolean; start: number; end: number; }): Promise { - const { apmEventClient } = setup; - const filter = [ { term: { [SERVICE_NAME]: serviceName } }, ...rangeQuery(start, end), diff --git a/x-pack/plugins/apm/server/routes/services/get_service_metadata_icons.ts b/x-pack/plugins/apm/server/routes/services/get_service_metadata_icons.ts index 7cf5304ded84..6b6ee0489ad1 100644 --- a/x-pack/plugins/apm/server/routes/services/get_service_metadata_icons.ts +++ b/x-pack/plugins/apm/server/routes/services/get_service_metadata_icons.ts @@ -20,7 +20,7 @@ import { import { ContainerType } from '../../../common/service_metadata'; import { TransactionRaw } from '../../../typings/es_schemas/raw/transaction_raw'; import { getProcessorEventForTransactions } from '../../lib/helpers/transactions'; -import { Setup } from '../../lib/helpers/setup_request'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; type ServiceMetadataIconsRaw = Pick< TransactionRaw, @@ -44,19 +44,17 @@ export const should = [ export async function getServiceMetadataIcons({ serviceName, - setup, + apmEventClient, searchAggregatedTransactions, start, end, }: { serviceName: string; - setup: Setup; + apmEventClient: APMEventClient; searchAggregatedTransactions: boolean; start: number; end: number; }): Promise { - const { apmEventClient } = setup; - const filter = [ { term: { [SERVICE_NAME]: serviceName } }, ...rangeQuery(start, end), diff --git a/x-pack/plugins/apm/server/routes/services/get_service_node_metadata.ts b/x-pack/plugins/apm/server/routes/services/get_service_node_metadata.ts index 7a8e786c2912..227dcab3deb4 100644 --- a/x-pack/plugins/apm/server/routes/services/get_service_node_metadata.ts +++ b/x-pack/plugins/apm/server/routes/services/get_service_node_metadata.ts @@ -7,7 +7,6 @@ import { kqlQuery, rangeQuery } from '@kbn/observability-plugin/server'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; -import { Setup } from '../../lib/helpers/setup_request'; import { HOST_NAME, CONTAINER_ID, @@ -22,24 +21,23 @@ import { serviceNodeNameQuery, } from '../../../common/utils/environment_query'; import { ENVIRONMENT_ALL } from '../../../common/environment_filter_values'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getServiceNodeMetadata({ kuery, serviceName, serviceNodeName, - setup, + apmEventClient, start, end, }: { kuery: string; serviceName: string; serviceNodeName: string; - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; }) { - const { apmEventClient } = setup; - const params = { apm: { events: [ProcessorEvent.metric], diff --git a/x-pack/plugins/apm/server/routes/services/get_service_transaction_group_detailed_statistics.ts b/x-pack/plugins/apm/server/routes/services/get_service_transaction_group_detailed_statistics.ts index df02ba064748..5e8c5b0cc1c5 100644 --- a/x-pack/plugins/apm/server/routes/services/get_service_transaction_group_detailed_statistics.ts +++ b/x-pack/plugins/apm/server/routes/services/get_service_transaction_group_detailed_statistics.ts @@ -28,16 +28,16 @@ import { getLatencyAggregation, getLatencyValue, } from '../../lib/helpers/latency_aggregation_type'; -import { Setup } from '../../lib/helpers/setup_request'; import { calculateFailedTransactionRate } from '../../lib/helpers/transaction_error_rate'; import { getOffsetInMs } from '../../../common/utils/get_offset_in_ms'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getServiceTransactionGroupDetailedStatistics({ environment, kuery, serviceName, transactionNames, - setup, + apmEventClient, numBuckets, searchAggregatedTransactions, transactionType, @@ -50,7 +50,7 @@ export async function getServiceTransactionGroupDetailedStatistics({ kuery: string; serviceName: string; transactionNames: string[]; - setup: Setup; + apmEventClient: APMEventClient; numBuckets: number; searchAggregatedTransactions: boolean; transactionType: string; @@ -67,8 +67,6 @@ export async function getServiceTransactionGroupDetailedStatistics({ impact: number; }> > { - const { apmEventClient } = setup; - const { startWithOffset, endWithOffset } = getOffsetInMs({ start, end, @@ -185,7 +183,7 @@ export async function getServiceTransactionGroupDetailedStatistics({ export async function getServiceTransactionGroupDetailedStatisticsPeriods({ serviceName, transactionNames, - setup, + apmEventClient, numBuckets, searchAggregatedTransactions, transactionType, @@ -198,7 +196,7 @@ export async function getServiceTransactionGroupDetailedStatisticsPeriods({ }: { serviceName: string; transactionNames: string[]; - setup: Setup; + apmEventClient: APMEventClient; numBuckets: number; searchAggregatedTransactions: boolean; transactionType: string; @@ -210,7 +208,7 @@ export async function getServiceTransactionGroupDetailedStatisticsPeriods({ offset?: string; }) { const commonProps = { - setup, + apmEventClient, serviceName, transactionNames, searchAggregatedTransactions, diff --git a/x-pack/plugins/apm/server/routes/services/get_service_transaction_groups.ts b/x-pack/plugins/apm/server/routes/services/get_service_transaction_groups.ts index 4a589f96b68f..7b828d91af9a 100644 --- a/x-pack/plugins/apm/server/routes/services/get_service_transaction_groups.ts +++ b/x-pack/plugins/apm/server/routes/services/get_service_transaction_groups.ts @@ -25,8 +25,9 @@ import { getLatencyAggregation, getLatencyValue, } from '../../lib/helpers/latency_aggregation_type'; -import { Setup } from '../../lib/helpers/setup_request'; import { calculateFailedTransactionRate } from '../../lib/helpers/transaction_error_rate'; +import { APMConfig } from '../..'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export type ServiceOverviewTransactionGroupSortField = | 'name' @@ -39,7 +40,8 @@ export async function getServiceTransactionGroups({ environment, kuery, serviceName, - setup, + config, + apmEventClient, searchAggregatedTransactions, transactionType, latencyAggregationType, @@ -49,14 +51,14 @@ export async function getServiceTransactionGroups({ environment: string; kuery: string; serviceName: string; - setup: Setup; + config: APMConfig; + apmEventClient: APMEventClient; searchAggregatedTransactions: boolean; transactionType: string; latencyAggregationType: LatencyAggregationType; start: number; end: number; }) { - const { apmEventClient, config } = setup; const bucketSize = config.ui.transactionGroupBucketSize; const field = getDurationFieldForTransactions(searchAggregatedTransactions); diff --git a/x-pack/plugins/apm/server/routes/services/get_service_transaction_types.ts b/x-pack/plugins/apm/server/routes/services/get_service_transaction_types.ts index 73d53a24d4f2..1081c35b7eb7 100644 --- a/x-pack/plugins/apm/server/routes/services/get_service_transaction_types.ts +++ b/x-pack/plugins/apm/server/routes/services/get_service_transaction_types.ts @@ -10,27 +10,25 @@ import { SERVICE_NAME, TRANSACTION_TYPE, } from '../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../lib/helpers/setup_request'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; import { getDocumentTypeFilterForTransactions, getProcessorEventForTransactions, } from '../../lib/helpers/transactions'; export async function getServiceTransactionTypes({ - setup, + apmEventClient, serviceName, searchAggregatedTransactions, start, end, }: { serviceName: string; - setup: Setup; + apmEventClient: APMEventClient; searchAggregatedTransactions: boolean; start: number; end: number; }) { - const { apmEventClient } = setup; - const params = { apm: { events: [getProcessorEventForTransactions(searchAggregatedTransactions)], diff --git a/x-pack/plugins/apm/server/routes/services/get_services/get_service_aggregated_transaction_stats.ts b/x-pack/plugins/apm/server/routes/services/get_services/get_service_aggregated_transaction_stats.ts index d7282dfd2db8..73436ad9afb6 100644 --- a/x-pack/plugins/apm/server/routes/services/get_services/get_service_aggregated_transaction_stats.ts +++ b/x-pack/plugins/apm/server/routes/services/get_services/get_service_aggregated_transaction_stats.ts @@ -24,15 +24,15 @@ import { environmentQuery } from '../../../../common/utils/environment_query'; import { AgentName } from '../../../../typings/es_schemas/ui/fields/agent'; import { calculateThroughputWithRange } from '../../../lib/helpers/calculate_throughput'; import { calculateFailedTransactionRateFromServiceMetrics } from '../../../lib/helpers/transaction_error_rate'; -import { ServicesItemsSetup } from './get_services_items'; import { serviceGroupQuery } from '../../../lib/service_group_query'; import { ServiceGroup } from '../../../../common/service_groups'; import { RandomSampler } from '../../../lib/helpers/get_random_sampler'; import { getDocumentTypeFilterForServiceMetrics } from '../../../lib/helpers/service_metrics'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; interface AggregationParams { environment: string; kuery: string; - setup: ServicesItemsSetup; + apmEventClient: APMEventClient; maxNumServices: number; start: number; end: number; @@ -43,15 +43,13 @@ interface AggregationParams { export async function getServiceAggregatedTransactionStats({ environment, kuery, - setup, + apmEventClient, maxNumServices, start, end, serviceGroup, randomSampler, }: AggregationParams) { - const { apmEventClient } = setup; - const response = await apmEventClient.search( 'get_service_aggregated_transaction_stats', { diff --git a/x-pack/plugins/apm/server/routes/services/get_services/get_service_transaction_stats.ts b/x-pack/plugins/apm/server/routes/services/get_services/get_service_transaction_stats.ts index 0d0d831b61a1..ba5903133e44 100644 --- a/x-pack/plugins/apm/server/routes/services/get_services/get_service_transaction_stats.ts +++ b/x-pack/plugins/apm/server/routes/services/get_services/get_service_transaction_stats.ts @@ -28,15 +28,15 @@ import { calculateFailedTransactionRate, getOutcomeAggregation, } from '../../../lib/helpers/transaction_error_rate'; -import { ServicesItemsSetup } from './get_services_items'; import { serviceGroupQuery } from '../../../lib/service_group_query'; import { ServiceGroup } from '../../../../common/service_groups'; import { RandomSampler } from '../../../lib/helpers/get_random_sampler'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; interface AggregationParams { environment: string; kuery: string; - setup: ServicesItemsSetup; + apmEventClient: APMEventClient; searchAggregatedTransactions: boolean; maxNumServices: number; start: number; @@ -48,7 +48,7 @@ interface AggregationParams { export async function getServiceTransactionStats({ environment, kuery, - setup, + apmEventClient, searchAggregatedTransactions, maxNumServices, start, @@ -56,8 +56,6 @@ export async function getServiceTransactionStats({ serviceGroup, randomSampler, }: AggregationParams) { - const { apmEventClient } = setup; - const outcomes = getOutcomeAggregation(); const metrics = { diff --git a/x-pack/plugins/apm/server/routes/services/get_services/get_services_from_error_and_metric_documents.ts b/x-pack/plugins/apm/server/routes/services/get_services/get_services_from_error_and_metric_documents.ts index 92cb6396856d..7ae1698b988d 100644 --- a/x-pack/plugins/apm/server/routes/services/get_services/get_services_from_error_and_metric_documents.ts +++ b/x-pack/plugins/apm/server/routes/services/get_services/get_services_from_error_and_metric_documents.ts @@ -14,14 +14,14 @@ import { SERVICE_NAME, } from '../../../../common/elasticsearch_fieldnames'; import { environmentQuery } from '../../../../common/utils/environment_query'; -import { Setup } from '../../../lib/helpers/setup_request'; import { serviceGroupQuery } from '../../../lib/service_group_query'; import { ServiceGroup } from '../../../../common/service_groups'; import { RandomSampler } from '../../../lib/helpers/get_random_sampler'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; export async function getServicesFromErrorAndMetricDocuments({ environment, - setup, + apmEventClient, maxNumServices, kuery, start, @@ -29,7 +29,7 @@ export async function getServicesFromErrorAndMetricDocuments({ serviceGroup, randomSampler, }: { - setup: Setup; + apmEventClient: APMEventClient; environment: string; maxNumServices: number; kuery: string; @@ -38,8 +38,6 @@ export async function getServicesFromErrorAndMetricDocuments({ serviceGroup: ServiceGroup | null; randomSampler: RandomSampler; }) { - const { apmEventClient } = setup; - const response = await apmEventClient.search( 'get_services_from_error_and_metric_documents', { diff --git a/x-pack/plugins/apm/server/routes/services/get_services/get_services_items.ts b/x-pack/plugins/apm/server/routes/services/get_services/get_services_items.ts index 7ee41a2bea0e..e3d2b72ec56b 100644 --- a/x-pack/plugins/apm/server/routes/services/get_services/get_services_items.ts +++ b/x-pack/plugins/apm/server/routes/services/get_services/get_services_items.ts @@ -15,6 +15,7 @@ import { getServiceAggregatedTransactionStats } from './get_service_aggregated_t import { mergeServiceStats } from './merge_service_stats'; import { ServiceGroup } from '../../../../common/service_groups'; import { RandomSampler } from '../../../lib/helpers/get_random_sampler'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; export type ServicesItemsSetup = Setup; @@ -24,6 +25,7 @@ export async function getServicesItems({ environment, kuery, setup, + apmEventClient, searchAggregatedTransactions, searchAggregatedServiceMetrics, logger, @@ -35,6 +37,7 @@ export async function getServicesItems({ environment: string; kuery: string; setup: ServicesItemsSetup; + apmEventClient: APMEventClient; searchAggregatedTransactions: boolean; searchAggregatedServiceMetrics: boolean; logger: Logger; @@ -44,10 +47,9 @@ export async function getServicesItems({ randomSampler: RandomSampler; }) { return withApmSpan('get_services_items', async () => { - const params = { + const commonParams = { environment, kuery, - setup, searchAggregatedTransactions, searchAggregatedServiceMetrics, maxNumServices: MAX_NUMBER_OF_SERVICES, @@ -63,10 +65,19 @@ export async function getServicesItems({ healthStatuses, ] = await Promise.all([ searchAggregatedServiceMetrics - ? getServiceAggregatedTransactionStats(params) - : getServiceTransactionStats(params), - getServicesFromErrorAndMetricDocuments(params), - getHealthStatuses(params).catch((err) => { + ? getServiceAggregatedTransactionStats({ + ...commonParams, + apmEventClient, + }) + : getServiceTransactionStats({ + ...commonParams, + apmEventClient, + }), + getServicesFromErrorAndMetricDocuments({ + ...commonParams, + apmEventClient, + }), + getHealthStatuses({ ...commonParams, setup }).catch((err) => { logger.error(err); return []; }), diff --git a/x-pack/plugins/apm/server/routes/services/get_services/get_sorted_and_filtered_services.ts b/x-pack/plugins/apm/server/routes/services/get_services/get_sorted_and_filtered_services.ts index a006c5b02509..62748c722ffe 100644 --- a/x-pack/plugins/apm/server/routes/services/get_services/get_sorted_and_filtered_services.ts +++ b/x-pack/plugins/apm/server/routes/services/get_services/get_sorted_and_filtered_services.ts @@ -7,6 +7,7 @@ import { Logger } from '@kbn/logging'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; import { SERVICE_NAME } from '../../../../common/elasticsearch_fieldnames'; import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; import { Environment } from '../../../../common/environment_rt'; @@ -16,8 +17,42 @@ import { Setup } from '../../../lib/helpers/setup_request'; import { getHealthStatuses } from './get_health_statuses'; import { lookupServices } from '../../service_groups/lookup_services'; +export async function getServiceNamesFromTermsEnum({ + apmEventClient, + environment, + maxNumberOfServices, +}: { + apmEventClient: APMEventClient; + environment: Environment; + maxNumberOfServices: number; +}) { + if (environment !== ENVIRONMENT_ALL.value) { + return []; + } + const response = await apmEventClient.termsEnum( + 'get_services_from_terms_enum', + { + apm: { + events: [ + ProcessorEvent.transaction, + ProcessorEvent.span, + ProcessorEvent.metric, + ProcessorEvent.error, + ], + }, + body: { + size: maxNumberOfServices, + field: SERVICE_NAME, + }, + } + ); + + return response.terms; +} + export async function getSortedAndFilteredServices({ setup, + apmEventClient, start, end, environment, @@ -26,6 +61,7 @@ export async function getSortedAndFilteredServices({ maxNumberOfServices, }: { setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; environment: Environment; @@ -33,33 +69,6 @@ export async function getSortedAndFilteredServices({ serviceGroup: ServiceGroup | null; maxNumberOfServices: number; }) { - const { apmEventClient } = setup; - - async function getServiceNamesFromTermsEnum() { - if (environment !== ENVIRONMENT_ALL.value) { - return []; - } - const response = await apmEventClient.termsEnum( - 'get_services_from_terms_enum', - { - apm: { - events: [ - ProcessorEvent.transaction, - ProcessorEvent.span, - ProcessorEvent.metric, - ProcessorEvent.error, - ], - }, - body: { - size: maxNumberOfServices, - field: SERVICE_NAME, - }, - } - ); - - return response.terms; - } - const [servicesWithHealthStatuses, selectedServices] = await Promise.all([ getHealthStatuses({ setup, @@ -72,13 +81,17 @@ export async function getSortedAndFilteredServices({ }), serviceGroup ? getServiceNamesFromServiceGroup({ - setup, + apmEventClient, start, end, maxNumberOfServices, serviceGroup, }) - : getServiceNamesFromTermsEnum(), + : getServiceNamesFromTermsEnum({ + apmEventClient, + environment, + maxNumberOfServices, + }), ]); const services = joinByKey( @@ -93,20 +106,20 @@ export async function getSortedAndFilteredServices({ } async function getServiceNamesFromServiceGroup({ - setup, + apmEventClient, start, end, maxNumberOfServices, serviceGroup: { kuery }, }: { - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; maxNumberOfServices: number; serviceGroup: ServiceGroup; }) { const services = await lookupServices({ - setup, + apmEventClient, kuery, start, end, diff --git a/x-pack/plugins/apm/server/routes/services/get_services/index.ts b/x-pack/plugins/apm/server/routes/services/get_services/index.ts index 83932f630357..d9eca887c8e0 100644 --- a/x-pack/plugins/apm/server/routes/services/get_services/index.ts +++ b/x-pack/plugins/apm/server/routes/services/get_services/index.ts @@ -11,11 +11,13 @@ import { Setup } from '../../../lib/helpers/setup_request'; import { getServicesItems } from './get_services_items'; import { ServiceGroup } from '../../../../common/service_groups'; import { RandomSampler } from '../../../lib/helpers/get_random_sampler'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; export async function getServices({ environment, kuery, setup, + apmEventClient, searchAggregatedTransactions, searchAggregatedServiceMetrics, logger, @@ -27,6 +29,7 @@ export async function getServices({ environment: string; kuery: string; setup: Setup; + apmEventClient: APMEventClient; searchAggregatedTransactions: boolean; searchAggregatedServiceMetrics: boolean; logger: Logger; @@ -40,6 +43,7 @@ export async function getServices({ environment, kuery, setup, + apmEventClient, searchAggregatedTransactions, searchAggregatedServiceMetrics, logger, diff --git a/x-pack/plugins/apm/server/routes/services/get_services_detailed_statistics/get_service_aggregated_transaction_detailed_statistics.ts b/x-pack/plugins/apm/server/routes/services/get_services_detailed_statistics/get_service_aggregated_transaction_detailed_statistics.ts index 83df6d0773d7..f6ae0b220ee4 100644 --- a/x-pack/plugins/apm/server/routes/services/get_services_detailed_statistics/get_service_aggregated_transaction_detailed_statistics.ts +++ b/x-pack/plugins/apm/server/routes/services/get_services_detailed_statistics/get_service_aggregated_transaction_detailed_statistics.ts @@ -24,16 +24,16 @@ import { environmentQuery } from '../../../../common/utils/environment_query'; import { getOffsetInMs } from '../../../../common/utils/get_offset_in_ms'; import { calculateThroughputWithRange } from '../../../lib/helpers/calculate_throughput'; import { getBucketSizeForAggregatedTransactions } from '../../../lib/helpers/get_bucket_size_for_aggregated_transactions'; -import { Setup } from '../../../lib/helpers/setup_request'; import { calculateFailedTransactionRateFromServiceMetrics } from '../../../lib/helpers/transaction_error_rate'; import { RandomSampler } from '../../../lib/helpers/get_random_sampler'; import { getDocumentTypeFilterForServiceMetrics } from '../../../lib/helpers/service_metrics'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; export async function getServiceAggregatedTransactionDetailedStats({ serviceNames, environment, kuery, - setup, + apmEventClient, searchAggregatedServiceMetrics, offset, start, @@ -43,14 +43,13 @@ export async function getServiceAggregatedTransactionDetailedStats({ serviceNames: string[]; environment: string; kuery: string; - setup: Setup; + apmEventClient: APMEventClient; searchAggregatedServiceMetrics: boolean; offset?: string; start: number; end: number; randomSampler: RandomSampler; }) { - const { apmEventClient } = setup; const { offsetInMs, startWithOffset, endWithOffset } = getOffsetInMs({ start, end, @@ -185,7 +184,7 @@ export async function getServiceAggregatedDetailedStatsPeriods({ serviceNames, environment, kuery, - setup, + apmEventClient, searchAggregatedServiceMetrics, offset, start, @@ -195,7 +194,7 @@ export async function getServiceAggregatedDetailedStatsPeriods({ serviceNames: string[]; environment: string; kuery: string; - setup: Setup; + apmEventClient: APMEventClient; searchAggregatedServiceMetrics: boolean; offset?: string; start: number; @@ -207,7 +206,7 @@ export async function getServiceAggregatedDetailedStatsPeriods({ serviceNames, environment, kuery, - setup, + apmEventClient, searchAggregatedServiceMetrics, start, end, diff --git a/x-pack/plugins/apm/server/routes/services/get_services_detailed_statistics/get_service_transaction_detailed_statistics.ts b/x-pack/plugins/apm/server/routes/services/get_services_detailed_statistics/get_service_transaction_detailed_statistics.ts index c587b3711e58..fbddad6aa1c4 100644 --- a/x-pack/plugins/apm/server/routes/services/get_services_detailed_statistics/get_service_transaction_detailed_statistics.ts +++ b/x-pack/plugins/apm/server/routes/services/get_services_detailed_statistics/get_service_transaction_detailed_statistics.ts @@ -25,18 +25,18 @@ import { } from '../../../lib/helpers/transactions'; import { calculateThroughputWithRange } from '../../../lib/helpers/calculate_throughput'; import { getBucketSizeForAggregatedTransactions } from '../../../lib/helpers/get_bucket_size_for_aggregated_transactions'; -import { Setup } from '../../../lib/helpers/setup_request'; import { calculateFailedTransactionRate, getOutcomeAggregation, } from '../../../lib/helpers/transaction_error_rate'; import { RandomSampler } from '../../../lib/helpers/get_random_sampler'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; export async function getServiceTransactionDetailedStats({ serviceNames, environment, kuery, - setup, + apmEventClient, searchAggregatedTransactions, offset, start, @@ -46,14 +46,13 @@ export async function getServiceTransactionDetailedStats({ serviceNames: string[]; environment: string; kuery: string; - setup: Setup; + apmEventClient: APMEventClient; searchAggregatedTransactions: boolean; offset?: string; start: number; end: number; randomSampler: RandomSampler; }) { - const { apmEventClient } = setup; const { offsetInMs, startWithOffset, endWithOffset } = getOffsetInMs({ start, end, @@ -182,7 +181,7 @@ export async function getServiceDetailedStatsPeriods({ serviceNames, environment, kuery, - setup, + apmEventClient, searchAggregatedTransactions, offset, start, @@ -192,7 +191,7 @@ export async function getServiceDetailedStatsPeriods({ serviceNames: string[]; environment: string; kuery: string; - setup: Setup; + apmEventClient: APMEventClient; searchAggregatedTransactions: boolean; offset?: string; start: number; @@ -204,7 +203,7 @@ export async function getServiceDetailedStatsPeriods({ serviceNames, environment, kuery, - setup, + apmEventClient, searchAggregatedTransactions, start, end, diff --git a/x-pack/plugins/apm/server/routes/services/get_services_detailed_statistics/index.ts b/x-pack/plugins/apm/server/routes/services/get_services_detailed_statistics/index.ts index b142b3484d55..583bb6e938aa 100644 --- a/x-pack/plugins/apm/server/routes/services/get_services_detailed_statistics/index.ts +++ b/x-pack/plugins/apm/server/routes/services/get_services_detailed_statistics/index.ts @@ -5,16 +5,16 @@ * 2.0. */ -import { Setup } from '../../../lib/helpers/setup_request'; import { getServiceDetailedStatsPeriods } from './get_service_transaction_detailed_statistics'; import { getServiceAggregatedDetailedStatsPeriods } from './get_service_aggregated_transaction_detailed_statistics'; import { RandomSampler } from '../../../lib/helpers/get_random_sampler'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; export async function getServicesDetailedStatistics({ serviceNames, environment, kuery, - setup, + apmEventClient, searchAggregatedTransactions, searchAggregatedServiceMetrics, offset, @@ -25,7 +25,7 @@ export async function getServicesDetailedStatistics({ serviceNames: string[]; environment: string; kuery: string; - setup: Setup; + apmEventClient: APMEventClient; searchAggregatedTransactions: boolean; searchAggregatedServiceMetrics: boolean; offset?: string; @@ -37,7 +37,7 @@ export async function getServicesDetailedStatistics({ serviceNames, environment, kuery, - setup, + apmEventClient, start, end, randomSampler, diff --git a/x-pack/plugins/apm/server/routes/services/get_throughput.ts b/x-pack/plugins/apm/server/routes/services/get_throughput.ts index a4312b6dca89..5413bcbf5632 100644 --- a/x-pack/plugins/apm/server/routes/services/get_throughput.ts +++ b/x-pack/plugins/apm/server/routes/services/get_throughput.ts @@ -20,16 +20,16 @@ import { getDocumentTypeFilterForTransactions, getProcessorEventForTransactions, } from '../../lib/helpers/transactions'; -import { Setup } from '../../lib/helpers/setup_request'; import { getOffsetInMs } from '../../../common/utils/get_offset_in_ms'; import { getBucketSizeForAggregatedTransactions } from '../../lib/helpers/get_bucket_size_for_aggregated_transactions'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; interface Options { environment: string; kuery: string; searchAggregatedTransactions: boolean; serviceName: string; - setup: Setup; + apmEventClient: APMEventClient; transactionType: string; transactionName?: string; start: number; @@ -42,15 +42,13 @@ export async function getThroughput({ kuery, searchAggregatedTransactions, serviceName, - setup, + apmEventClient, transactionType, transactionName, start, end, offset, }: Options) { - const { apmEventClient } = setup; - const { startWithOffset, endWithOffset } = getOffsetInMs({ start, end, diff --git a/x-pack/plugins/apm/server/routes/services/queries.test.ts b/x-pack/plugins/apm/server/routes/services/queries.test.ts index ff9f79867c2d..141077270464 100644 --- a/x-pack/plugins/apm/server/routes/services/queries.test.ts +++ b/x-pack/plugins/apm/server/routes/services/queries.test.ts @@ -23,10 +23,10 @@ describe('services queries', () => { }); it('fetches the service agent name', async () => { - mock = await inspectSearchParams((setup) => + mock = await inspectSearchParams((setup, apmEventClient) => getServiceAgent({ serviceName: 'foo', - setup, + apmEventClient, start: 0, end: 50000, }) @@ -36,10 +36,10 @@ describe('services queries', () => { }); it('fetches the service transaction types', async () => { - mock = await inspectSearchParams((setup) => + mock = await inspectSearchParams((setup, apmEventClient) => getServiceTransactionTypes({ serviceName: 'foo', - setup, + apmEventClient, searchAggregatedTransactions: false, start: 0, end: 50000, @@ -50,9 +50,10 @@ describe('services queries', () => { }); it('fetches the service items', async () => { - mock = await inspectSearchParams((setup) => + mock = await inspectSearchParams((setup, apmEventClient) => getServicesItems({ setup, + apmEventClient, searchAggregatedTransactions: false, searchAggregatedServiceMetrics: false, logger: {} as any, @@ -74,7 +75,9 @@ describe('services queries', () => { }); it('fetches the agent status', async () => { - mock = await inspectSearchParams((setup) => hasHistoricalAgentData(setup)); + mock = await inspectSearchParams((setup, apmEventClient) => + hasHistoricalAgentData(apmEventClient) + ); expect(mock.params).toMatchSnapshot(); }); diff --git a/x-pack/plugins/apm/server/routes/services/route.ts b/x-pack/plugins/apm/server/routes/services/route.ts index 1f1cac260331..0dc2de2d68b4 100644 --- a/x-pack/plugins/apm/server/routes/services/route.ts +++ b/x-pack/plugins/apm/server/routes/services/route.ts @@ -56,6 +56,7 @@ import { getServiceGroup } from '../service_groups/get_service_group'; import { offsetRt } from '../../../common/comparison_rt'; import { getRandomSampler } from '../../lib/helpers/get_random_sampler'; import { createInfraMetricsClient } from '../../lib/helpers/create_es_client/create_infra_metrics_client/create_infra_metrics_client'; +import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; const servicesRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/services', @@ -126,15 +127,17 @@ const servicesRoute = createApmServerRoute({ const savedObjectsClient = (await context.core).savedObjects.client; const coreContext = await resources.context.core; - const [setup, serviceGroup, randomSampler] = await Promise.all([ - setupRequest(resources), - serviceGroupId - ? getServiceGroup({ savedObjectsClient, serviceGroupId }) - : Promise.resolve(null), - getRandomSampler({ security, request, probability }), - ]); + const [setup, apmEventClient, serviceGroup, randomSampler] = + await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + serviceGroupId + ? getServiceGroup({ savedObjectsClient, serviceGroupId }) + : Promise.resolve(null), + getRandomSampler({ security, request, probability }), + ]); - const { apmEventClient, config } = setup; + const { config } = setup; const serviceMetricsEnabled = await coreContext.uiSettings.client.get(enableServiceMetrics); @@ -153,6 +156,7 @@ const servicesRoute = createApmServerRoute({ environment, kuery, setup, + apmEventClient, searchAggregatedTransactions, searchAggregatedServiceMetrics, logger, @@ -223,12 +227,13 @@ const servicesDetailedStatisticsRoute = createApmServerRoute({ const { serviceNames } = params.body; - const [setup, randomSampler] = await Promise.all([ + const [setup, apmEventClient, randomSampler] = await Promise.all([ setupRequest(resources), + getApmEventClient(resources), getRandomSampler({ security, request, probability }), ]); - const { apmEventClient, config } = setup; + const { config } = setup; const serviceMetricsEnabled = await coreContext.uiSettings.client.get(enableServiceMetrics); @@ -250,7 +255,7 @@ const servicesDetailedStatisticsRoute = createApmServerRoute({ return getServicesDetailedStatistics({ environment, kuery, - setup, + apmEventClient, searchAggregatedTransactions, searchAggregatedServiceMetrics, offset, @@ -274,14 +279,17 @@ const serviceMetadataDetailsRoute = createApmServerRoute({ ): Promise< import('./get_service_metadata_details').ServiceMetadataDetails > => { - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const infraMetricsClient = createInfraMetricsClient(resources); const { params } = resources; const { serviceName } = params.path; const { start, end } = params.query; const searchAggregatedTransactions = await getSearchTransactionsEvents({ - apmEventClient: setup.apmEventClient, + apmEventClient, config: setup.config, start, end, @@ -290,7 +298,7 @@ const serviceMetadataDetailsRoute = createApmServerRoute({ const serviceMetadataDetails = await getServiceMetadataDetails({ serviceName, - setup, + apmEventClient, searchAggregatedTransactions, start, end, @@ -321,13 +329,16 @@ const serviceMetadataIconsRoute = createApmServerRoute({ handler: async ( resources ): Promise => { - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { params } = resources; const { serviceName } = params.path; const { start, end } = params.query; const searchAggregatedTransactions = await getSearchTransactionsEvents({ - apmEventClient: setup.apmEventClient, + apmEventClient, config: setup.config, start, end, @@ -336,7 +347,7 @@ const serviceMetadataIconsRoute = createApmServerRoute({ return getServiceMetadataIcons({ serviceName, - setup, + apmEventClient, searchAggregatedTransactions, start, end, @@ -359,14 +370,14 @@ const serviceAgentRoute = createApmServerRoute({ | { agentName?: undefined; runtimeName?: undefined } | { agentName: string | undefined; runtimeName: string | undefined } > => { - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { params } = resources; const { serviceName } = params.path; const { start, end } = params.query; return getServiceAgent({ serviceName, - setup, + apmEventClient, start, end, }); @@ -383,16 +394,19 @@ const serviceTransactionTypesRoute = createApmServerRoute({ }), options: { tags: ['access:apm'] }, handler: async (resources): Promise<{ transactionTypes: string[] }> => { - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { params } = resources; const { serviceName } = params.path; const { start, end } = params.query; return getServiceTransactionTypes({ serviceName, - setup, + apmEventClient, searchAggregatedTransactions: await getSearchTransactionsEvents({ - apmEventClient: setup.apmEventClient, + apmEventClient, config: setup.config, start, end, @@ -418,14 +432,14 @@ const serviceNodeMetadataRoute = createApmServerRoute({ handler: async ( resources ): Promise<{ host: string | number; containerId: string | number }> => { - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { params } = resources; const { serviceName, serviceNodeName } = params.path; const { kuery, start, end } = params.query; return getServiceNodeMetadata({ kuery, - setup, + apmEventClient, serviceName, serviceNodeName, start, @@ -448,7 +462,10 @@ const serviceAnnotationsRoute = createApmServerRoute({ ): Promise<{ annotations: Array; }> => { - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { params, plugins, context, request, logger } = resources; const { serviceName } = params.path; const { environment, start, end } = params.query; @@ -466,7 +483,7 @@ const serviceAnnotationsRoute = createApmServerRoute({ ) : undefined, getSearchTransactionsEvents({ - apmEventClient: setup.apmEventClient, + apmEventClient, config: setup.config, start, end, @@ -477,7 +494,7 @@ const serviceAnnotationsRoute = createApmServerRoute({ return getServiceAnnotations({ environment, - setup, + apmEventClient, searchAggregatedTransactions, serviceName, annotationsClient, @@ -586,7 +603,10 @@ const serviceThroughputRoute = createApmServerRoute({ y: import('./../../../typings/common').Maybe; }>; }> => { - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { params } = resources; const { serviceName } = params.path; const { @@ -599,7 +619,8 @@ const serviceThroughputRoute = createApmServerRoute({ end, } = params.query; const searchAggregatedTransactions = await getSearchTransactionsEvents({ - ...setup, + config: setup.config, + apmEventClient, kuery, start, end, @@ -610,7 +631,7 @@ const serviceThroughputRoute = createApmServerRoute({ kuery, searchAggregatedTransactions, serviceName, - setup, + apmEventClient, transactionType, transactionName, }; @@ -680,7 +701,10 @@ const serviceInstancesMainStatisticsRoute = createApmServerRoute({ memoryUsage?: number | null | undefined; }>; }> => { - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { params } = resources; const { serviceName } = params.path; const { @@ -694,7 +718,8 @@ const serviceInstancesMainStatisticsRoute = createApmServerRoute({ } = params.query; const searchAggregatedTransactions = await getSearchTransactionsEvents({ - ...setup, + config: setup.config, + apmEventClient, kuery, start, end, @@ -706,7 +731,7 @@ const serviceInstancesMainStatisticsRoute = createApmServerRoute({ kuery, latencyAggregationType, serviceName, - setup, + apmEventClient, transactionType, searchAggregatedTransactions, start, @@ -719,7 +744,7 @@ const serviceInstancesMainStatisticsRoute = createApmServerRoute({ kuery, latencyAggregationType, serviceName, - setup, + apmEventClient, transactionType, searchAggregatedTransactions, start, @@ -800,7 +825,10 @@ const serviceInstancesDetailedStatisticsRoute = createApmServerRoute({ serviceNodeName: string; }>; }> => { - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { params } = resources; const { serviceName } = params.path; const { @@ -816,7 +844,8 @@ const serviceInstancesDetailedStatisticsRoute = createApmServerRoute({ } = params.query; const searchAggregatedTransactions = await getSearchTransactionsEvents({ - ...setup, + apmEventClient, + config: setup.config, kuery, start, end, @@ -827,7 +856,7 @@ const serviceInstancesDetailedStatisticsRoute = createApmServerRoute({ kuery, latencyAggregationType, serviceName, - setup, + apmEventClient, transactionType, searchAggregatedTransactions, numBuckets, @@ -901,7 +930,7 @@ export const serviceInstancesMetadataDetails = createApmServerRoute({ | import('./../../../typings/es_schemas/raw/fields/cloud').Cloud | undefined; }> => { - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const infraMetricsClient = createInfraMetricsClient(resources); const { params } = resources; const { serviceName, serviceNodeName } = params.path; @@ -909,7 +938,7 @@ export const serviceInstancesMetadataDetails = createApmServerRoute({ const serviceInstanceMetadataDetails = await getServiceInstanceMetadataDetails({ - setup, + apmEventClient, serviceName, serviceNodeName, start, @@ -1002,13 +1031,13 @@ export const serviceDependenciesRoute = createApmServerRoute({ location: import('./../../../common/connections').Node; }>; }> => { - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { params } = resources; const { serviceName } = params.path; const { environment, numBuckets, start, end, offset } = params.query; const opts = { - setup, + apmEventClient, start, end, serviceName, @@ -1061,13 +1090,13 @@ export const serviceDependenciesBreakdownRoute = createApmServerRoute({ ): Promise<{ breakdown: Array<{ title: string; data: Array<{ x: number; y: number }> }>; }> => { - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { params } = resources; const { serviceName } = params.path; const { environment, start, end, kuery } = params.query; const breakdown = await getServiceDependenciesBreakdown({ - setup, + apmEventClient, start, end, serviceName, @@ -1177,16 +1206,19 @@ const sortedAndFilteredServicesRoute = createApmServerRoute({ uiSettings: { client: uiSettingsClient }, } = await resources.context.core; - const [setup, serviceGroup, maxNumberOfServices] = await Promise.all([ - setupRequest(resources), - serviceGroupId - ? getServiceGroup({ savedObjectsClient, serviceGroupId }) - : Promise.resolve(null), - uiSettingsClient.get(apmServiceGroupMaxNumberOfServices), - ]); + const [setup, apmEventClient, serviceGroup, maxNumberOfServices] = + await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + serviceGroupId + ? getServiceGroup({ savedObjectsClient, serviceGroupId }) + : Promise.resolve(null), + uiSettingsClient.get(apmServiceGroupMaxNumberOfServices), + ]); return { services: await getSortedAndFilteredServices({ setup, + apmEventClient, start, end, environment, diff --git a/x-pack/plugins/apm/server/routes/settings/agent_configuration/get_agent_name_by_service.ts b/x-pack/plugins/apm/server/routes/settings/agent_configuration/get_agent_name_by_service.ts index f70bd330bfc3..1ebc98877941 100644 --- a/x-pack/plugins/apm/server/routes/settings/agent_configuration/get_agent_name_by_service.ts +++ b/x-pack/plugins/apm/server/routes/settings/agent_configuration/get_agent_name_by_service.ts @@ -6,19 +6,17 @@ */ import { ProcessorEvent } from '@kbn/observability-plugin/common'; -import { Setup } from '../../../lib/helpers/setup_request'; import { SERVICE_NAME } from '../../../../common/elasticsearch_fieldnames'; import { AGENT_NAME } from '../../../../common/elasticsearch_fieldnames'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; export async function getAgentNameByService({ serviceName, - setup, + apmEventClient, }: { serviceName: string; - setup: Setup; + apmEventClient: APMEventClient; }) { - const { apmEventClient } = setup; - const params = { terminate_after: 1, apm: { diff --git a/x-pack/plugins/apm/server/routes/settings/agent_configuration/get_environments/index.ts b/x-pack/plugins/apm/server/routes/settings/agent_configuration/get_environments/index.ts index 46ab82152caa..fb335fa231be 100644 --- a/x-pack/plugins/apm/server/routes/settings/agent_configuration/get_environments/index.ts +++ b/x-pack/plugins/apm/server/routes/settings/agent_configuration/get_environments/index.ts @@ -10,15 +10,18 @@ import { getAllEnvironments } from '../../../environments/get_all_environments'; import { Setup } from '../../../../lib/helpers/setup_request'; import { getExistingEnvironmentsForService } from './get_existing_environments_for_service'; import { ALL_OPTION_VALUE } from '../../../../../common/agent_configuration/all_option'; +import { APMEventClient } from '../../../../lib/helpers/create_es_client/create_apm_event_client'; export async function getEnvironments({ serviceName, setup, + apmEventClient, searchAggregatedTransactions, size, }: { serviceName: string | undefined; setup: Setup; + apmEventClient: APMEventClient; searchAggregatedTransactions: boolean; size: number; }) { @@ -27,7 +30,7 @@ export async function getEnvironments({ getAllEnvironments({ searchAggregatedTransactions, serviceName, - setup, + apmEventClient, size, }), getExistingEnvironmentsForService({ serviceName, setup, size }), diff --git a/x-pack/plugins/apm/server/routes/settings/agent_configuration/queries.test.ts b/x-pack/plugins/apm/server/routes/settings/agent_configuration/queries.test.ts index 49a97c1ca4f7..59c52c37601c 100644 --- a/x-pack/plugins/apm/server/routes/settings/agent_configuration/queries.test.ts +++ b/x-pack/plugins/apm/server/routes/settings/agent_configuration/queries.test.ts @@ -24,11 +24,11 @@ describe('agent configuration queries', () => { describe('getAllEnvironments', () => { it('fetches all environments', async () => { - mock = await inspectSearchParams((setup) => + mock = await inspectSearchParams((setup, apmEventClient) => getAllEnvironments({ searchAggregatedTransactions: false, serviceName: 'foo', - setup, + apmEventClient, size: 50, }) ); diff --git a/x-pack/plugins/apm/server/routes/settings/agent_configuration/route.ts b/x-pack/plugins/apm/server/routes/settings/agent_configuration/route.ts index eb04de443038..77eb625c0b01 100644 --- a/x-pack/plugins/apm/server/routes/settings/agent_configuration/route.ts +++ b/x-pack/plugins/apm/server/routes/settings/agent_configuration/route.ts @@ -25,6 +25,7 @@ import { } from '../../../../common/agent_configuration/runtime_types/agent_configuration_intake_rt'; import { getSearchTransactionsEvents } from '../../../lib/helpers/transactions'; import { syncAgentConfigsToApmPackagePolicies } from '../../fleet/sync_agent_configs_to_apm_package_policies'; +import { getApmEventClient } from '../../../lib/helpers/get_apm_event_client'; // get list of configurations const agentConfigurationRoute = createApmServerRoute({ @@ -269,13 +270,16 @@ const listAgentConfigurationEnvironmentsRoute = createApmServerRoute({ ): Promise<{ environments: Array<{ name: string; alreadyConfigured: boolean }>; }> => { - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { context, params } = resources; const coreContext = await context.core; const { serviceName, start, end } = params.query; const searchAggregatedTransactions = await getSearchTransactionsEvents({ - apmEventClient: setup.apmEventClient, + apmEventClient, config: setup.config, kuery: '', start, @@ -287,6 +291,7 @@ const listAgentConfigurationEnvironmentsRoute = createApmServerRoute({ const environments = await getEnvironments({ serviceName, setup, + apmEventClient, searchAggregatedTransactions, size, }); @@ -303,10 +308,13 @@ const agentConfigurationAgentNameRoute = createApmServerRoute({ }), options: { tags: ['access:apm'] }, handler: async (resources): Promise<{ agentName: string | undefined }> => { - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { params } = resources; const { serviceName } = params.query; - const agentName = await getAgentNameByService({ serviceName, setup }); + const agentName = await getAgentNameByService({ + serviceName, + apmEventClient, + }); return { agentName }; }, }); diff --git a/x-pack/plugins/apm/server/routes/settings/anomaly_detection/route.ts b/x-pack/plugins/apm/server/routes/settings/anomaly_detection/route.ts index e30f669b9cd7..68b4ab7e55c0 100644 --- a/x-pack/plugins/apm/server/routes/settings/anomaly_detection/route.ts +++ b/x-pack/plugins/apm/server/routes/settings/anomaly_detection/route.ts @@ -20,6 +20,7 @@ import { notifyFeatureUsage } from '../../../feature'; import { updateToV3 } from './update_to_v3'; import { environmentStringRt } from '../../../../common/environment_rt'; import { getMlJobsWithAPMGroup } from '../../../lib/anomaly_detection/get_ml_jobs_with_apm_group'; +import { getApmEventClient } from '../../../lib/helpers/get_apm_event_client'; // get ML anomaly detection jobs for each environment const anomalyDetectionJobsRoute = createApmServerRoute({ @@ -94,11 +95,14 @@ const anomalyDetectionEnvironmentsRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/settings/anomaly-detection/environments', options: { tags: ['access:apm'] }, handler: async (resources): Promise<{ environments: string[] }> => { - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const coreContext = await resources.context.core; const searchAggregatedTransactions = await getSearchTransactionsEvents({ - apmEventClient: setup.apmEventClient, + apmEventClient, config: setup.config, kuery: '', }); @@ -108,7 +112,7 @@ const anomalyDetectionEnvironmentsRoute = createApmServerRoute({ const environments = await getAllEnvironments({ includeMissing: true, searchAggregatedTransactions, - setup, + apmEventClient, size, }); diff --git a/x-pack/plugins/apm/server/routes/settings/custom_link/get_transaction.test.ts b/x-pack/plugins/apm/server/routes/settings/custom_link/get_transaction.test.ts index e6ba932ca58d..afbe4c1d7bbe 100644 --- a/x-pack/plugins/apm/server/routes/settings/custom_link/get_transaction.test.ts +++ b/x-pack/plugins/apm/server/routes/settings/custom_link/get_transaction.test.ts @@ -10,29 +10,29 @@ import { SearchParamsMock, } from '../../../utils/test_helpers'; import { getTransaction } from './get_transaction'; -import { Setup } from '../../../lib/helpers/setup_request'; import { SERVICE_NAME, TRANSACTION_TYPE, SERVICE_ENVIRONMENT, TRANSACTION_NAME, } from '../../../../common/elasticsearch_fieldnames'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; describe('custom link get transaction', () => { let mock: SearchParamsMock; it('fetches without filter', async () => { - mock = await inspectSearchParams((setup) => + mock = await inspectSearchParams((setup, apmEventClient) => getTransaction({ - setup: setup as unknown as Setup, + apmEventClient: apmEventClient as unknown as APMEventClient, }) ); expect(mock.params).toMatchSnapshot(); }); it('fetches with all filter', async () => { - mock = await inspectSearchParams((setup) => + mock = await inspectSearchParams((setup, apmEventClient) => getTransaction({ - setup: setup as unknown as Setup, + apmEventClient: apmEventClient as unknown as APMEventClient, filters: { [SERVICE_NAME]: 'foo', [SERVICE_ENVIRONMENT]: 'bar', diff --git a/x-pack/plugins/apm/server/routes/settings/custom_link/get_transaction.ts b/x-pack/plugins/apm/server/routes/settings/custom_link/get_transaction.ts index 0dfece7bdc34..d454b447b17f 100644 --- a/x-pack/plugins/apm/server/routes/settings/custom_link/get_transaction.ts +++ b/x-pack/plugins/apm/server/routes/settings/custom_link/get_transaction.ts @@ -8,19 +8,17 @@ import * as t from 'io-ts'; import { compact } from 'lodash'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; -import { Setup } from '../../../lib/helpers/setup_request'; import { filterOptionsRt } from './custom_link_types'; import { splitFilterValueByComma } from './helper'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; export async function getTransaction({ - setup, + apmEventClient, filters = {}, }: { - setup: Setup; + apmEventClient: APMEventClient; filters?: t.TypeOf; }) { - const { apmEventClient } = setup; - const esFilters = compact( Object.entries(filters) // loops through the filters splitting the value by comma and removing white spaces diff --git a/x-pack/plugins/apm/server/routes/settings/custom_link/route.ts b/x-pack/plugins/apm/server/routes/settings/custom_link/route.ts index 1bd6328bdcee..60d2642acfae 100644 --- a/x-pack/plugins/apm/server/routes/settings/custom_link/route.ts +++ b/x-pack/plugins/apm/server/routes/settings/custom_link/route.ts @@ -19,6 +19,7 @@ import { deleteCustomLink } from './delete_custom_link'; import { getTransaction } from './get_transaction'; import { listCustomLinks } from './list_custom_links'; import { createApmServerRoute } from '../../apm_routes/create_apm_server_route'; +import { getApmEventClient } from '../../../lib/helpers/get_apm_event_client'; const customLinkTransactionRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/settings/custom_links/transaction', @@ -31,12 +32,12 @@ const customLinkTransactionRoute = createApmServerRoute({ ): Promise< import('./../../../../typings/es_schemas/ui/transaction').Transaction > => { - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { params } = resources; const { query } = params; // picks only the items listed in FILTER_OPTIONS const filters = pick(query, FILTER_OPTIONS); - return await getTransaction({ setup, filters }); + return await getTransaction({ apmEventClient, filters }); }, }); diff --git a/x-pack/plugins/apm/server/routes/span_links/get_linked_children.ts b/x-pack/plugins/apm/server/routes/span_links/get_linked_children.ts index 172463ff7cf6..60dfe40f469f 100644 --- a/x-pack/plugins/apm/server/routes/span_links/get_linked_children.ts +++ b/x-pack/plugins/apm/server/routes/span_links/get_linked_children.ts @@ -18,24 +18,22 @@ import { } from '../../../common/elasticsearch_fieldnames'; import type { SpanRaw } from '../../../typings/es_schemas/raw/span_raw'; import type { TransactionRaw } from '../../../typings/es_schemas/raw/transaction_raw'; -import { Setup } from '../../lib/helpers/setup_request'; import { getBufferedTimerange } from './utils'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; async function fetchLinkedChildrenOfSpan({ traceId, - setup, + apmEventClient, start, end, spanId, }: { traceId: string; - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; spanId?: string; }) { - const { apmEventClient } = setup; - const { startWithBuffer, endWithBuffer } = getBufferedTimerange({ start, end, @@ -83,18 +81,18 @@ function getSpanId(source: TransactionRaw | SpanRaw) { export async function getLinkedChildrenCountBySpanId({ traceId, - setup, + apmEventClient, start, end, }: { traceId: string; - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; }) { const linkedChildren = await fetchLinkedChildrenOfSpan({ traceId, - setup, + apmEventClient, start, end, }); @@ -115,20 +113,20 @@ export async function getLinkedChildrenCountBySpanId({ export async function getLinkedChildrenOfSpan({ traceId, spanId, - setup, + apmEventClient, start, end, }: { traceId: string; spanId: string; - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; }) { const linkedChildren = await fetchLinkedChildrenOfSpan({ traceId, spanId, - setup, + apmEventClient, start, end, }); diff --git a/x-pack/plugins/apm/server/routes/span_links/get_linked_parents.ts b/x-pack/plugins/apm/server/routes/span_links/get_linked_parents.ts index 3bc8d9ef5941..76efb549bc22 100644 --- a/x-pack/plugins/apm/server/routes/span_links/get_linked_parents.ts +++ b/x-pack/plugins/apm/server/routes/span_links/get_linked_parents.ts @@ -15,10 +15,10 @@ import { } from '../../../common/elasticsearch_fieldnames'; import { SpanRaw } from '../../../typings/es_schemas/raw/span_raw'; import { TransactionRaw } from '../../../typings/es_schemas/raw/transaction_raw'; -import { Setup } from '../../lib/helpers/setup_request'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getLinkedParentsOfSpan({ - setup, + apmEventClient, traceId, spanId, start, @@ -27,13 +27,11 @@ export async function getLinkedParentsOfSpan({ }: { traceId: string; spanId: string; - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; processorEvent: ProcessorEvent; }) { - const { apmEventClient } = setup; - const response = await apmEventClient.search('get_linked_parents_of_span', { apm: { events: [processorEvent], diff --git a/x-pack/plugins/apm/server/routes/span_links/get_span_links_details.ts b/x-pack/plugins/apm/server/routes/span_links/get_span_links_details.ts index a09d10b42283..c27764e91fbc 100644 --- a/x-pack/plugins/apm/server/routes/span_links/get_span_links_details.ts +++ b/x-pack/plugins/apm/server/routes/span_links/get_span_links_details.ts @@ -26,24 +26,22 @@ import { SpanLinkDetails } from '../../../common/span_links'; import { SpanLink } from '../../../typings/es_schemas/raw/fields/span_links'; import { SpanRaw } from '../../../typings/es_schemas/raw/span_raw'; import { TransactionRaw } from '../../../typings/es_schemas/raw/transaction_raw'; -import { Setup } from '../../lib/helpers/setup_request'; import { getBufferedTimerange } from './utils'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; async function fetchSpanLinksDetails({ - setup, + apmEventClient, kuery, spanLinks, start, end, }: { - setup: Setup; + apmEventClient: APMEventClient; kuery: string; spanLinks: SpanLink[]; start: number; end: number; }) { - const { apmEventClient } = setup; - const { startWithBuffer, endWithBuffer } = getBufferedTimerange({ start, end, @@ -119,13 +117,13 @@ async function fetchSpanLinksDetails({ } export async function getSpanLinksDetails({ - setup, + apmEventClient, spanLinks, kuery, start, end, }: { - setup: Setup; + apmEventClient: APMEventClient; spanLinks: SpanLink[]; kuery: string; start: number; @@ -140,7 +138,7 @@ export async function getSpanLinksDetails({ const chunkedResponses = await Promise.all( spanLinksChunks.map((spanLinksChunk) => fetchSpanLinksDetails({ - setup, + apmEventClient, kuery, spanLinks: spanLinksChunk, start, diff --git a/x-pack/plugins/apm/server/routes/span_links/route.ts b/x-pack/plugins/apm/server/routes/span_links/route.ts index 34b586477814..18f361503d37 100644 --- a/x-pack/plugins/apm/server/routes/span_links/route.ts +++ b/x-pack/plugins/apm/server/routes/span_links/route.ts @@ -5,7 +5,6 @@ * 2.0. */ import * as t from 'io-ts'; -import { setupRequest } from '../../lib/helpers/setup_request'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { getSpanLinksDetails } from './get_span_links_details'; import { getLinkedChildrenOfSpan } from './get_linked_children'; @@ -13,6 +12,7 @@ import { kueryRt, rangeRt } from '../default_api_types'; import { SpanLinkDetails } from '../../../common/span_links'; import { processorEventRt } from '../../../common/processor_event'; import { getLinkedParentsOfSpan } from './get_linked_parents'; +import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; const linkedParentsRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/traces/{traceId}/span_links/{spanId}/parents', @@ -36,9 +36,9 @@ const linkedParentsRoute = createApmServerRoute({ const { params: { query, path }, } = resources; - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const linkedParents = await getLinkedParentsOfSpan({ - setup, + apmEventClient, traceId: path.traceId, spanId: path.spanId, start: query.start, @@ -48,7 +48,7 @@ const linkedParentsRoute = createApmServerRoute({ return { spanLinksDetails: await getSpanLinksDetails({ - setup, + apmEventClient, spanLinks: linkedParents, kuery: query.kuery, start: query.start, @@ -76,9 +76,9 @@ const linkedChildrenRoute = createApmServerRoute({ const { params: { query, path }, } = resources; - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const linkedChildren = await getLinkedChildrenOfSpan({ - setup, + apmEventClient, traceId: path.traceId, spanId: path.spanId, start: query.start, @@ -87,7 +87,7 @@ const linkedChildrenRoute = createApmServerRoute({ return { spanLinksDetails: await getSpanLinksDetails({ - setup, + apmEventClient, spanLinks: linkedChildren, kuery: query.kuery, start: query.start, diff --git a/x-pack/plugins/apm/server/routes/storage_explorer/get_service_statistics.ts b/x-pack/plugins/apm/server/routes/storage_explorer/get_service_statistics.ts index 169bff0cd711..95a74d13c131 100644 --- a/x-pack/plugins/apm/server/routes/storage_explorer/get_service_statistics.ts +++ b/x-pack/plugins/apm/server/routes/storage_explorer/get_service_statistics.ts @@ -4,7 +4,6 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { termQuery, kqlQuery, @@ -34,9 +33,11 @@ import { getEstimatedSizeForDocumentsInIndex, } from './indices_stats_helpers'; import { RandomSampler } from '../../lib/helpers/get_random_sampler'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; async function getMainServiceStatistics({ setup, + apmEventClient, context, indexLifecyclePhase, randomSampler, @@ -46,6 +47,7 @@ async function getMainServiceStatistics({ kuery, }: { setup: Setup; + apmEventClient: APMEventClient; context: ApmPluginRequestHandlerContext; indexLifecyclePhase: IndexLifecyclePhaseSelectOption; randomSampler: RandomSampler; @@ -54,8 +56,6 @@ async function getMainServiceStatistics({ environment: string; kuery: string; }) { - const { apmEventClient } = setup; - const [{ indices: allIndicesStats }, response] = await Promise.all([ getTotalIndicesStats({ context, setup }), apmEventClient.search('get_main_service_statistics', { @@ -82,7 +82,7 @@ async function getMainServiceStatistics({ indexLifeCyclePhaseToDataTier[indexLifecyclePhase] ) : []), - ] as QueryDslQueryContainer[], + ], }, }, aggs: { @@ -177,6 +177,7 @@ async function getMainServiceStatistics({ export async function getServiceStatistics({ setup, + apmEventClient, context, indexLifecyclePhase, randomSampler, @@ -187,6 +188,7 @@ export async function getServiceStatistics({ searchAggregatedTransactions, }: { setup: Setup; + apmEventClient: APMEventClient; context: ApmPluginRequestHandlerContext; indexLifecyclePhase: IndexLifecyclePhaseSelectOption; randomSampler: RandomSampler; @@ -200,6 +202,7 @@ export async function getServiceStatistics({ await Promise.all([ getMainServiceStatistics({ setup, + apmEventClient, context, indexLifecyclePhase, randomSampler, @@ -209,7 +212,7 @@ export async function getServiceStatistics({ end, }), getTotalTransactionsPerService({ - setup, + apmEventClient, searchAggregatedTransactions, indexLifecyclePhase, randomSampler, diff --git a/x-pack/plugins/apm/server/routes/storage_explorer/get_size_timeseries.ts b/x-pack/plugins/apm/server/routes/storage_explorer/get_size_timeseries.ts index f513efe059f7..2337243a6412 100644 --- a/x-pack/plugins/apm/server/routes/storage_explorer/get_size_timeseries.ts +++ b/x-pack/plugins/apm/server/routes/storage_explorer/get_size_timeseries.ts @@ -29,11 +29,13 @@ import { getTotalIndicesStats, getEstimatedSizeForDocumentsInIndex, } from './indices_stats_helpers'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getSizeTimeseries({ environment, kuery, setup, + apmEventClient, searchAggregatedTransactions, start, end, @@ -44,6 +46,7 @@ export async function getSizeTimeseries({ environment: string; kuery: string; setup: Setup; + apmEventClient: APMEventClient; searchAggregatedTransactions: boolean; start: number; end: number; @@ -51,8 +54,6 @@ export async function getSizeTimeseries({ randomSampler: RandomSampler; context: ApmPluginRequestHandlerContext; }) { - const { apmEventClient } = setup; - const { intervalString } = getBucketSizeForAggregatedTransactions({ start, end, diff --git a/x-pack/plugins/apm/server/routes/storage_explorer/get_storage_details_per_processor_event.ts b/x-pack/plugins/apm/server/routes/storage_explorer/get_storage_details_per_service.ts similarity index 55% rename from x-pack/plugins/apm/server/routes/storage_explorer/get_storage_details_per_processor_event.ts rename to x-pack/plugins/apm/server/routes/storage_explorer/get_storage_details_per_service.ts index 8f92ce9d9380..ae6b0a82ca0e 100644 --- a/x-pack/plugins/apm/server/routes/storage_explorer/get_storage_details_per_processor_event.ts +++ b/x-pack/plugins/apm/server/routes/storage_explorer/get_storage_details_per_service.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { termQuery, kqlQuery, @@ -28,11 +27,15 @@ import { ApmPluginRequestHandlerContext } from '../typings'; import { getTotalIndicesStats, getEstimatedSizeForDocumentsInIndex, + getIndicesLifecycleStatus, + getIndicesInfo, } from './indices_stats_helpers'; import { RandomSampler } from '../../lib/helpers/get_random_sampler'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getStorageDetailsPerProcessorEvent({ setup, + apmEventClient, context, indexLifecyclePhase, randomSampler, @@ -43,6 +46,7 @@ export async function getStorageDetailsPerProcessorEvent({ serviceName, }: { setup: Setup; + apmEventClient: APMEventClient; context: ApmPluginRequestHandlerContext; indexLifecyclePhase: IndexLifecyclePhaseSelectOption; randomSampler: RandomSampler; @@ -52,11 +56,9 @@ export async function getStorageDetailsPerProcessorEvent({ kuery: string; serviceName: string; }) { - const { apmEventClient } = setup; - const [{ indices: allIndicesStats }, response] = await Promise.all([ getTotalIndicesStats({ setup, context }), - apmEventClient.search('get_storage_details_per_processor_event', { + apmEventClient.search('get_storage_details_per_service', { apm: { events: [ ProcessorEvent.span, @@ -81,7 +83,7 @@ export async function getStorageDetailsPerProcessorEvent({ indexLifeCyclePhaseToDataTier[indexLifecyclePhase] ) : []), - ] as QueryDslQueryContainer[], + ], }, }, aggs: { @@ -153,3 +155,122 @@ export async function getStorageDetailsPerProcessorEvent({ }; }); } + +export async function getStorageDetailsPerIndex({ + apmEventClient, + setup, + context, + indexLifecyclePhase, + randomSampler, + start, + end, + environment, + kuery, + serviceName, +}: { + apmEventClient: APMEventClient; + setup: Setup; + context: ApmPluginRequestHandlerContext; + indexLifecyclePhase: IndexLifecyclePhaseSelectOption; + randomSampler: RandomSampler; + start: number; + end: number; + environment: string; + kuery: string; + serviceName: string; +}) { + const [ + { indices: allIndicesStats }, + indicesLifecycleStatus, + indicesInfo, + response, + ] = await Promise.all([ + getTotalIndicesStats({ setup, context }), + getIndicesLifecycleStatus({ setup, context }), + getIndicesInfo({ setup, context }), + apmEventClient.search('get_storage_details_per_index', { + apm: { + events: [ + ProcessorEvent.span, + ProcessorEvent.transaction, + ProcessorEvent.error, + ProcessorEvent.metric, + ], + }, + body: { + size: 0, + track_total_hits: false, + query: { + bool: { + filter: [ + ...environmentQuery(environment), + ...kqlQuery(kuery), + ...rangeQuery(start, end), + ...termQuery(SERVICE_NAME, serviceName), + ...(indexLifecyclePhase !== IndexLifecyclePhaseSelectOption.All + ? termQuery( + TIER, + indexLifeCyclePhaseToDataTier[indexLifecyclePhase] + ) + : []), + ], + }, + }, + aggs: { + sample: { + random_sampler: randomSampler, + aggs: { + indices: { + terms: { + field: INDEX, + size: 500, + }, + aggs: { + number_of_metric_docs_for_index: { + value_count: { + field: INDEX, + }, + }, + }, + }, + }, + }, + }, + }, + }), + ]); + + return ( + response.aggregations?.sample.indices.buckets.map((bucket) => { + const indexName = bucket.key as string; + const numberOfDocs = bucket.number_of_metric_docs_for_index.value; + const indexInfo = indicesInfo[indexName]; + const indexLifecycle = indicesLifecycleStatus[indexName]; + + const size = + allIndicesStats && + getEstimatedSizeForDocumentsInIndex({ + allIndicesStats, + indexName, + numberOfDocs, + }); + + return { + indexName, + numberOfDocs, + primary: indexInfo + ? indexInfo.settings?.index?.number_of_shards ?? 0 + : undefined, + replica: indexInfo + ? indexInfo.settings?.number_of_replicas ?? 0 + : undefined, + size, + dataStream: indexInfo?.data_stream, + lifecyclePhase: + indexLifecycle && 'phase' in indexLifecycle + ? indexLifecycle.phase + : undefined, + }; + }) ?? [] + ); +} diff --git a/x-pack/plugins/apm/server/routes/storage_explorer/get_summary_statistics.ts b/x-pack/plugins/apm/server/routes/storage_explorer/get_summary_statistics.ts index dfa510e8f089..2bb471751385 100644 --- a/x-pack/plugins/apm/server/routes/storage_explorer/get_summary_statistics.ts +++ b/x-pack/plugins/apm/server/routes/storage_explorer/get_summary_statistics.ts @@ -6,7 +6,6 @@ */ import { ProcessorEvent } from '@kbn/observability-plugin/common'; -import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { termQuery, kqlQuery, @@ -15,6 +14,7 @@ import { import { getTotalIndicesStats, getEstimatedSizeForDocumentsInIndex, + getApmDiskSpacedUsedPct, } from './indices_stats_helpers'; import { Setup } from '../../lib/helpers/setup_request'; import { ApmPluginRequestHandlerContext } from '../typings'; @@ -36,9 +36,10 @@ import { isRootTransaction, } from '../../lib/helpers/transactions'; import { calculateThroughputWithRange } from '../../lib/helpers/calculate_throughput'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getTracesPerMinute({ - setup, + apmEventClient, indexLifecyclePhase, start, end, @@ -46,7 +47,7 @@ export async function getTracesPerMinute({ kuery, searchAggregatedTransactions, }: { - setup: Setup; + apmEventClient: APMEventClient; indexLifecyclePhase: IndexLifecyclePhaseSelectOption; start: number; end: number; @@ -54,8 +55,6 @@ export async function getTracesPerMinute({ kuery: string; searchAggregatedTransactions: boolean; }) { - const { apmEventClient } = setup; - const response = await apmEventClient.search('get_traces_per_minute', { apm: { events: [getProcessorEventForTransactions(searchAggregatedTransactions)], @@ -103,6 +102,7 @@ export async function getTracesPerMinute({ export async function getMainSummaryStats({ setup, + apmEventClient, context, indexLifecyclePhase, randomSampler, @@ -112,6 +112,7 @@ export async function getMainSummaryStats({ kuery, }: { setup: Setup; + apmEventClient: APMEventClient; context: ApmPluginRequestHandlerContext; indexLifecyclePhase: IndexLifecyclePhaseSelectOption; randomSampler: RandomSampler; @@ -120,10 +121,9 @@ export async function getMainSummaryStats({ environment: string; kuery: string; }) { - const { apmEventClient } = setup; - - const [{ indices: allIndicesStats }, res] = await Promise.all([ + const [totalIndicesStats, totalDiskSpace, res] = await Promise.all([ getTotalIndicesStats({ context, setup }), + getApmDiskSpacedUsedPct(context), apmEventClient.search('get_storage_explorer_main_summary_stats', { apm: { events: [ @@ -148,7 +148,7 @@ export async function getMainSummaryStats({ indexLifeCyclePhaseToDataTier[indexLifecyclePhase] ) : []), - ] as QueryDslQueryContainer[], + ], }, }, aggs: { @@ -180,7 +180,8 @@ export async function getMainSummaryStats({ }), ]); - const estimatedSize = allIndicesStats + const { indices: allIndicesStats } = totalIndicesStats; + const estimatedIncrementalSize = allIndicesStats ? res.aggregations?.sample.indices.buckets.reduce((prev, curr) => { return ( prev + @@ -194,10 +195,13 @@ export async function getMainSummaryStats({ : 0; const durationAsDays = (end - start) / 1000 / 60 / 60 / 24; + const totalApmSize = totalIndicesStats._all.total?.store?.size_in_bytes ?? 0; return { + totalSize: totalApmSize, + diskSpaceUsedPct: totalApmSize / totalDiskSpace, numberOfServices: res.aggregations?.services_count.value ?? 0, - estimatedSize, - dailyDataGeneration: estimatedSize / durationAsDays, + estimatedIncrementalSize, + dailyDataGeneration: estimatedIncrementalSize / durationAsDays, }; } diff --git a/x-pack/plugins/apm/server/routes/storage_explorer/get_total_transactions_per_service.ts b/x-pack/plugins/apm/server/routes/storage_explorer/get_total_transactions_per_service.ts index 793c69ab71b4..1f883adee3f7 100644 --- a/x-pack/plugins/apm/server/routes/storage_explorer/get_total_transactions_per_service.ts +++ b/x-pack/plugins/apm/server/routes/storage_explorer/get_total_transactions_per_service.ts @@ -4,13 +4,11 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { termQuery, kqlQuery, rangeQuery, } from '@kbn/observability-plugin/server'; -import { Setup } from '../../lib/helpers/setup_request'; import { getProcessorEventForTransactions, getDocumentTypeFilterForTransactions, @@ -22,9 +20,10 @@ import { } from '../../../common/storage_explorer_types'; import { environmentQuery } from '../../../common/utils/environment_query'; import { RandomSampler } from '../../lib/helpers/get_random_sampler'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getTotalTransactionsPerService({ - setup, + apmEventClient, searchAggregatedTransactions, indexLifecyclePhase, randomSampler, @@ -33,7 +32,7 @@ export async function getTotalTransactionsPerService({ environment, kuery, }: { - setup: Setup; + apmEventClient: APMEventClient; searchAggregatedTransactions: boolean; indexLifecyclePhase: IndexLifecyclePhaseSelectOption; randomSampler: RandomSampler; @@ -42,8 +41,6 @@ export async function getTotalTransactionsPerService({ environment: string; kuery: string; }) { - const { apmEventClient } = setup; - const response = await apmEventClient.search( 'get_total_transactions_per_service', { @@ -70,7 +67,7 @@ export async function getTotalTransactionsPerService({ indexLifeCyclePhaseToDataTier[indexLifecyclePhase] ) : []), - ] as QueryDslQueryContainer[], + ], }, }, aggs: { diff --git a/x-pack/plugins/apm/server/routes/storage_explorer/has_storage_explorer_privileges.ts b/x-pack/plugins/apm/server/routes/storage_explorer/has_storage_explorer_privileges.ts index 714c0eee1c62..e69c3f33f8d9 100644 --- a/x-pack/plugins/apm/server/routes/storage_explorer/has_storage_explorer_privileges.ts +++ b/x-pack/plugins/apm/server/routes/storage_explorer/has_storage_explorer_privileges.ts @@ -28,17 +28,19 @@ export async function hasStorageExplorerPrivileges({ ); const esClient = (await context.core).elasticsearch.client; - const { index } = await esClient.asCurrentUser.security.hasPrivileges({ - body: { - index: [ - { - names, - privileges: ['monitor'], - }, - ], - }, - }); + const { index, cluster } = + await esClient.asCurrentUser.security.hasPrivileges({ + body: { + index: [ + { + names, + privileges: ['monitor'], + }, + ], + cluster: ['monitor'], + }, + }); - const hasPrivileges = every(index, 'monitor'); + const hasPrivileges = cluster.monitor && every(index, 'monitor'); return hasPrivileges; } diff --git a/x-pack/plugins/apm/server/routes/storage_explorer/indices_stats_helpers.ts b/x-pack/plugins/apm/server/routes/storage_explorer/indices_stats_helpers.ts index aa9e854cc4a2..d7ce952b2fd5 100644 --- a/x-pack/plugins/apm/server/routes/storage_explorer/indices_stats_helpers.ts +++ b/x-pack/plugins/apm/server/routes/storage_explorer/indices_stats_helpers.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { uniq } from 'lodash'; +import { uniq, values, sumBy } from 'lodash'; import { IndicesStatsIndicesStats } from '@elastic/elasticsearch/lib/api/types'; import { Setup } from '../../lib/helpers/setup_request'; import { ApmPluginRequestHandlerContext } from '../typings'; @@ -16,10 +16,7 @@ export async function getTotalIndicesStats({ context: ApmPluginRequestHandlerContext; setup: Setup; }) { - const { - indices: { transaction, span, metric, error }, - } = setup; - const index = uniq([transaction, span, metric, error]).join(); + const index = getApmIndicesCombined(setup); const esClient = (await context.core).elasticsearch.client; const totalStats = await esClient.asCurrentUser.indices.stats({ index }); return totalStats; @@ -44,3 +41,67 @@ export function getEstimatedSizeForDocumentsInIndex({ return estimatedSize; } + +export async function getApmDiskSpacedUsedPct( + context: ApmPluginRequestHandlerContext +) { + const esClient = (await context.core).elasticsearch.client; + const { nodes: diskSpacePerNode } = await esClient.asCurrentUser.nodes.stats({ + metric: 'fs', + filter_path: 'nodes.*.fs.total.total_in_bytes', + }); + + const totalDiskSpace = sumBy( + values(diskSpacePerNode), + (node) => node?.fs?.total?.total_in_bytes ?? 0 + ); + + return totalDiskSpace; +} + +export async function getIndicesLifecycleStatus({ + context, + setup, +}: { + context: ApmPluginRequestHandlerContext; + setup: Setup; +}) { + const index = getApmIndicesCombined(setup); + const esClient = (await context.core).elasticsearch.client; + const { indices } = await esClient.asCurrentUser.ilm.explainLifecycle({ + index, + filter_path: 'indices.*.phase', + }); + + return indices; +} + +export async function getIndicesInfo({ + context, + setup, +}: { + context: ApmPluginRequestHandlerContext; + setup: Setup; +}) { + const index = getApmIndicesCombined(setup); + const esClient = (await context.core).elasticsearch.client; + const indicesInfo = await esClient.asCurrentUser.indices.get({ + index, + filter_path: [ + '*.settings.index.number_of_shards', + '*.settings.index.number_of_replicas', + '*.data_stream', + ], + features: ['settings'], + }); + + return indicesInfo; +} + +export function getApmIndicesCombined(setup: Setup) { + const { + indices: { transaction, span, metric, error }, + } = setup; + + return uniq([transaction, span, metric, error]).join(); +} diff --git a/x-pack/plugins/apm/server/routes/storage_explorer/is_cross_cluster_search.test.ts b/x-pack/plugins/apm/server/routes/storage_explorer/is_cross_cluster_search.test.ts new file mode 100644 index 000000000000..d24ef3515817 --- /dev/null +++ b/x-pack/plugins/apm/server/routes/storage_explorer/is_cross_cluster_search.test.ts @@ -0,0 +1,105 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Setup } from '../../lib/helpers/setup_request'; +import { isCrossClusterSearch } from './is_cross_cluster_search'; +import { ApmIndicesConfig } from '@kbn/observability-plugin/common/typings'; + +describe('isCrossClusterSearch', () => { + it('returns false when there are no remote clusters in APM indices', () => { + const mockedSetup = { + indices: { + transaction: 'traces-apm*', + span: 'traces-apm*', + metric: 'metrics-apm*', + error: 'logs-apm*', + } as ApmIndicesConfig, + } as unknown as Setup; + + expect(isCrossClusterSearch(mockedSetup)).toBe(false); + }); + + it('returns false when there are multiple indices per type and no remote clusters in APM indices', () => { + const mockedSetup = { + indices: { + transaction: 'traces-apm*,test-apm*', + span: 'traces-apm*,test-apm*', + metric: 'metrics-apm*,test-apm*', + error: 'logs-apm*,test-apm*', + } as ApmIndicesConfig, + } as unknown as Setup; + + expect(isCrossClusterSearch(mockedSetup)).toBe(false); + }); + + it('returns false when there are remote clusters in onboarding and sourcemap indices', () => { + const mockedSetup = { + indices: { + transaction: '', + span: '', + metric: '', + error: '', + onboarding: 'apm-*,remote_cluster:apm-*', + sourcemap: 'apm-*,remote_cluster:apm-*', + } as ApmIndicesConfig, + } as unknown as Setup; + + expect(isCrossClusterSearch(mockedSetup)).toBe(false); + }); + + it('returns true when there are remote clusters in transaction indices', () => { + const mockedSetup = { + indices: { + transaction: 'traces-apm*,remote_cluster:traces-apm*', + span: '', + metric: '', + error: '', + } as ApmIndicesConfig, + } as unknown as Setup; + + expect(isCrossClusterSearch(mockedSetup)).toBe(true); + }); + + it('returns true when there are remote clusters in span indices', () => { + const mockedSetup = { + indices: { + transaction: '', + span: 'traces-apm*,remote_cluster:traces-apm*', + metric: '', + error: '', + } as ApmIndicesConfig, + } as unknown as Setup; + + expect(isCrossClusterSearch(mockedSetup)).toBe(true); + }); + + it('returns true when there are remote clusters in metrics indices', () => { + const mockedSetup = { + indices: { + transaction: '', + span: '', + metric: 'metrics-apm*,remote_cluster:metrics-apm*', + error: '', + } as ApmIndicesConfig, + } as unknown as Setup; + + expect(isCrossClusterSearch(mockedSetup)).toBe(true); + }); + + it('returns true when there are remote clusters in error indices', () => { + const mockedSetup = { + indices: { + transaction: '', + span: '', + metric: '', + error: 'logs-apm*,remote_cluster:logs-apm*', + } as ApmIndicesConfig, + } as unknown as Setup; + + expect(isCrossClusterSearch(mockedSetup)).toBe(true); + }); +}); diff --git a/x-pack/plugins/apm/server/routes/storage_explorer/is_cross_cluster_search.ts b/x-pack/plugins/apm/server/routes/storage_explorer/is_cross_cluster_search.ts new file mode 100644 index 000000000000..fbbe7a134793 --- /dev/null +++ b/x-pack/plugins/apm/server/routes/storage_explorer/is_cross_cluster_search.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Setup } from '../../lib/helpers/setup_request'; +import { getApmIndicesCombined } from './indices_stats_helpers'; + +export function isCrossClusterSearch(setup: Setup) { + // Check if a remote cluster is set in APM indices + return getApmIndicesCombined(setup).includes(':'); +} diff --git a/x-pack/plugins/apm/server/routes/storage_explorer/route.ts b/x-pack/plugins/apm/server/routes/storage_explorer/route.ts index 9d817efbf34f..6872bdd22a09 100644 --- a/x-pack/plugins/apm/server/routes/storage_explorer/route.ts +++ b/x-pack/plugins/apm/server/routes/storage_explorer/route.ts @@ -9,10 +9,14 @@ import * as t from 'io-ts'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; import Boom from '@hapi/boom'; import { i18n } from '@kbn/i18n'; +import { ENVIRONMENT_ALL } from '../../../common/environment_filter_values'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { getSearchTransactionsEvents } from '../../lib/helpers/transactions'; import { setupRequest } from '../../lib/helpers/setup_request'; -import { indexLifecyclePhaseRt } from '../../../common/storage_explorer_types'; +import { + indexLifecyclePhaseRt, + IndexLifecyclePhaseSelectOption, +} from '../../../common/storage_explorer_types'; import { getServiceStatistics } from './get_service_statistics'; import { probabilityRt, @@ -21,7 +25,10 @@ import { rangeRt, } from '../default_api_types'; import { AgentName } from '../../../typings/es_schemas/ui/fields/agent'; -import { getStorageDetailsPerProcessorEvent } from './get_storage_details_per_processor_event'; +import { + getStorageDetailsPerIndex, + getStorageDetailsPerProcessorEvent, +} from './get_storage_details_per_service'; import { getRandomSampler } from '../../lib/helpers/get_random_sampler'; import { getSizeTimeseries } from './get_size_timeseries'; import { hasStorageExplorerPrivileges } from './has_storage_explorer_privileges'; @@ -29,6 +36,9 @@ import { getMainSummaryStats, getTracesPerMinute, } from './get_summary_statistics'; +import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; +import { isCrossClusterSearch } from './is_cross_cluster_search'; +import { getServiceNamesFromTermsEnum } from '../services/get_services/get_sorted_and_filtered_services'; const storageExplorerRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/storage_explorer', @@ -71,19 +81,21 @@ const storageExplorerRoute = createApmServerRoute({ }, } = params; - const [setup, randomSampler] = await Promise.all([ + const [setup, apmEventClient, randomSampler] = await Promise.all([ setupRequest(resources), + getApmEventClient(resources), getRandomSampler({ security, request, probability }), ]); const searchAggregatedTransactions = await getSearchTransactionsEvents({ - apmEventClient: setup.apmEventClient, + apmEventClient, config: setup.config, kuery, }); const serviceStatistics = await getServiceStatistics({ setup, + apmEventClient, context, indexLifecyclePhase, randomSampler, @@ -127,6 +139,15 @@ const storageExplorerServiceDetailsRoute = createApmServerRoute({ docs: number; size: number; }>; + indicesStats: Array<{ + indexName: string; + numberOfDocs: number; + primary?: number | string; + replica?: number | string; + size?: number; + dataStream?: string; + lifecyclePhase?: string; + }>; }> => { const { params, @@ -147,24 +168,40 @@ const storageExplorerServiceDetailsRoute = createApmServerRoute({ }, } = params; - const [setup, randomSampler] = await Promise.all([ + const [setup, apmEventClient, randomSampler] = await Promise.all([ setupRequest(resources), + getApmEventClient(resources), getRandomSampler({ security, request, probability }), ]); - const processorEventStats = await getStorageDetailsPerProcessorEvent({ - setup, - context, - indexLifecyclePhase, - randomSampler, - environment, - kuery, - start, - end, - serviceName, - }); + const [processorEventStats, indicesStats] = await Promise.all([ + getStorageDetailsPerProcessorEvent({ + apmEventClient, + setup, + context, + indexLifecyclePhase, + randomSampler, + environment, + kuery, + start, + end, + serviceName, + }), + getStorageDetailsPerIndex({ + apmEventClient, + setup, + context, + indexLifecyclePhase, + randomSampler, + environment, + kuery, + start, + end, + serviceName, + }), + ]); - return { processorEventStats }; + return { processorEventStats, indicesStats }; }, }); @@ -206,13 +243,14 @@ const storageChartRoute = createApmServerRoute({ }, } = params; - const [setup, randomSampler] = await Promise.all([ + const [setup, apmEventClient, randomSampler] = await Promise.all([ setupRequest(resources), + getApmEventClient(resources), getRandomSampler({ security, request, probability }), ]); const searchAggregatedTransactions = await getSearchTransactionsEvents({ - apmEventClient: setup.apmEventClient, + apmEventClient, config: setup.config, kuery, }); @@ -226,6 +264,7 @@ const storageChartRoute = createApmServerRoute({ start, end, setup, + apmEventClient, context, }); @@ -274,7 +313,9 @@ const storageExplorerSummaryStatsRoute = createApmServerRoute({ ): Promise<{ tracesPerMinute: number; numberOfServices: number; - estimatedSize: number; + totalSize: number; + diskSpaceUsedPct: number; + estimatedIncrementalSize: number; dailyDataGeneration: number; }> => { const { @@ -295,13 +336,14 @@ const storageExplorerSummaryStatsRoute = createApmServerRoute({ }, } = params; - const [setup, randomSampler] = await Promise.all([ + const [setup, apmEventClient, randomSampler] = await Promise.all([ setupRequest(resources), + getApmEventClient(resources), getRandomSampler({ security, request, probability }), ]); const searchAggregatedTransactions = await getSearchTransactionsEvents({ - apmEventClient: setup.apmEventClient, + apmEventClient, config: setup.config, kuery, }); @@ -309,6 +351,7 @@ const storageExplorerSummaryStatsRoute = createApmServerRoute({ const [mainSummaryStats, tracesPerMinute] = await Promise.all([ getMainSummaryStats({ setup, + apmEventClient, context, indexLifecyclePhase, randomSampler, @@ -318,7 +361,7 @@ const storageExplorerSummaryStatsRoute = createApmServerRoute({ kuery, }), getTracesPerMinute({ - setup, + apmEventClient, indexLifecyclePhase, start, end, @@ -335,12 +378,68 @@ const storageExplorerSummaryStatsRoute = createApmServerRoute({ }, }); +const storageExplorerIsCrossClusterSearchRoute = createApmServerRoute({ + endpoint: 'GET /internal/apm/storage_explorer/is_cross_cluster_search', + options: { tags: ['access:apm'] }, + handler: async (resources): Promise<{ isCrossClusterSearch: boolean }> => { + const setup = await setupRequest(resources); + return { isCrossClusterSearch: isCrossClusterSearch(setup) }; + }, +}); + +const storageExplorerGetServices = createApmServerRoute({ + endpoint: 'GET /internal/apm/storage_explorer/get_services', + options: { + tags: ['access:apm'], + }, + params: t.type({ + query: t.intersection([indexLifecyclePhaseRt, environmentRt, kueryRt]), + }), + handler: async ( + resources + ): Promise<{ + services: Array<{ + serviceName: string; + }>; + }> => { + const { + query: { environment, kuery, indexLifecyclePhase }, + } = resources.params; + + if ( + kuery || + indexLifecyclePhase !== IndexLifecyclePhaseSelectOption.All || + environment !== ENVIRONMENT_ALL.value + ) { + return { + services: [], + }; + } + + const apmEventClient = await getApmEventClient(resources); + + const services = await getServiceNamesFromTermsEnum({ + apmEventClient, + environment, + maxNumberOfServices: 500, + }); + + return { + services: services.map((serviceName): { serviceName: string } => ({ + serviceName, + })), + }; + }, +}); + export const storageExplorerRouteRepository = { ...storageExplorerRoute, ...storageExplorerServiceDetailsRoute, ...storageChartRoute, ...storageExplorerPrivilegesRoute, ...storageExplorerSummaryStatsRoute, + ...storageExplorerIsCrossClusterSearchRoute, + ...storageExplorerGetServices, }; const SECURITY_REQUIRED_MESSAGE = i18n.translate( diff --git a/x-pack/plugins/apm/server/routes/suggestions/get_suggestions_with_terms_aggregation.ts b/x-pack/plugins/apm/server/routes/suggestions/get_suggestions_with_terms_aggregation.ts index 56ed34805c2f..856b6bea1b9a 100644 --- a/x-pack/plugins/apm/server/routes/suggestions/get_suggestions_with_terms_aggregation.ts +++ b/x-pack/plugins/apm/server/routes/suggestions/get_suggestions_with_terms_aggregation.ts @@ -8,14 +8,14 @@ import { rangeQuery, termQuery } from '@kbn/observability-plugin/server'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; import { getProcessorEventForTransactions } from '../../lib/helpers/transactions'; import { SERVICE_NAME } from '../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../lib/helpers/setup_request'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getSuggestionsWithTermsAggregation({ fieldName, fieldValue, searchAggregatedTransactions, serviceName, - setup, + apmEventClient, size, start, end, @@ -24,13 +24,11 @@ export async function getSuggestionsWithTermsAggregation({ fieldValue: string; searchAggregatedTransactions: boolean; serviceName?: string; - setup: Setup; + apmEventClient: APMEventClient; size: number; start: number; end: number; }) { - const { apmEventClient } = setup; - const response = await apmEventClient.search( 'get_suggestions_with_terms_aggregation', { diff --git a/x-pack/plugins/apm/server/routes/suggestions/get_suggestions_with_terms_enum.ts b/x-pack/plugins/apm/server/routes/suggestions/get_suggestions_with_terms_enum.ts index 4437a3615189..c945438ff01c 100644 --- a/x-pack/plugins/apm/server/routes/suggestions/get_suggestions_with_terms_enum.ts +++ b/x-pack/plugins/apm/server/routes/suggestions/get_suggestions_with_terms_enum.ts @@ -6,13 +6,13 @@ */ import { ProcessorEvent } from '@kbn/observability-plugin/common'; import { getProcessorEventForTransactions } from '../../lib/helpers/transactions'; -import { Setup } from '../../lib/helpers/setup_request'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getSuggestionsWithTermsEnum({ fieldName, fieldValue, searchAggregatedTransactions, - setup, + apmEventClient, size, start, end, @@ -20,40 +20,35 @@ export async function getSuggestionsWithTermsEnum({ fieldName: string; fieldValue: string; searchAggregatedTransactions: boolean; - setup: Setup; + apmEventClient: APMEventClient; size: number; start: number; end: number; }) { - const { apmEventClient } = setup; - - const response = await apmEventClient.termsEnum( - 'get_suggestions_with_terms_enum', - { - apm: { - events: [ - getProcessorEventForTransactions(searchAggregatedTransactions), - ProcessorEvent.error, - ProcessorEvent.metric, - ], - }, - body: { - case_insensitive: true, - field: fieldName, - size, - string: fieldValue, - index_filter: { - range: { - ['@timestamp']: { - gte: start, - lte: end, - format: 'epoch_millis', - }, + const response = await apmEventClient.termsEnum('get_suggestions', { + apm: { + events: [ + getProcessorEventForTransactions(searchAggregatedTransactions), + ProcessorEvent.error, + ProcessorEvent.metric, + ], + }, + body: { + case_insensitive: true, + field: fieldName, + size, + string: fieldValue, + index_filter: { + range: { + ['@timestamp']: { + gte: start, + lte: end, + format: 'epoch_millis', }, }, }, - } - ); + }, + }); return { terms: response.terms }; } diff --git a/x-pack/plugins/apm/server/routes/suggestions/route.ts b/x-pack/plugins/apm/server/routes/suggestions/route.ts index f0396ac62ca5..976779816f96 100644 --- a/x-pack/plugins/apm/server/routes/suggestions/route.ts +++ b/x-pack/plugins/apm/server/routes/suggestions/route.ts @@ -13,6 +13,7 @@ import { getSearchTransactionsEvents } from '../../lib/helpers/transactions'; import { setupRequest } from '../../lib/helpers/setup_request'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { rangeRt } from '../default_api_types'; +import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; const suggestionsRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/suggestions', @@ -28,11 +29,14 @@ const suggestionsRoute = createApmServerRoute({ }), options: { tags: ['access:apm'] }, handler: async (resources): Promise<{ terms: string[] }> => { - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { context, params } = resources; const { fieldName, fieldValue, serviceName, start, end } = params.query; const searchAggregatedTransactions = await getSearchTransactionsEvents({ - apmEventClient: setup.apmEventClient, + apmEventClient, config: setup.config, kuery: '', }); @@ -46,7 +50,7 @@ const suggestionsRoute = createApmServerRoute({ fieldName, fieldValue, searchAggregatedTransactions, - setup, + apmEventClient, size, start, end, @@ -65,7 +69,7 @@ const suggestionsRoute = createApmServerRoute({ fieldValue, searchAggregatedTransactions, serviceName, - setup, + apmEventClient, size, start, end, diff --git a/x-pack/plugins/apm/server/routes/time_range_metadata/route.ts b/x-pack/plugins/apm/server/routes/time_range_metadata/route.ts index f0321f4cfde4..bde0058b5756 100644 --- a/x-pack/plugins/apm/server/routes/time_range_metadata/route.ts +++ b/x-pack/plugins/apm/server/routes/time_range_metadata/route.ts @@ -7,7 +7,7 @@ import { toBooleanRt } from '@kbn/io-ts-utils'; import * as t from 'io-ts'; import { TimeRangeMetadata } from '../../../common/time_range_metadata'; -import { setupRequest } from '../../lib/helpers/setup_request'; +import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; import { getIsUsingServiceDestinationMetrics } from '../../lib/helpers/spans/get_is_using_service_destination_metrics'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { kueryRt, rangeRt } from '../default_api_types'; @@ -25,7 +25,7 @@ export const timeRangeMetadataRoute = createApmServerRoute({ tags: ['access:apm'], }, handler: async (resources): Promise => { - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { query: { useSpanName, start, end, kuery }, @@ -33,7 +33,7 @@ export const timeRangeMetadataRoute = createApmServerRoute({ const [isUsingServiceDestinationMetrics] = await Promise.all([ getIsUsingServiceDestinationMetrics({ - setup, + apmEventClient, useSpanName, start, end, diff --git a/x-pack/plugins/apm/server/routes/traces/get_top_traces_primary_stats.ts b/x-pack/plugins/apm/server/routes/traces/get_top_traces_primary_stats.ts index 92ff9c38260d..dc9143685902 100644 --- a/x-pack/plugins/apm/server/routes/traces/get_top_traces_primary_stats.ts +++ b/x-pack/plugins/apm/server/routes/traces/get_top_traces_primary_stats.ts @@ -13,7 +13,6 @@ import { } from '@kbn/observability-plugin/server'; import { AgentName } from '../../../typings/es_schemas/ui/fields/agent'; import { withApmSpan } from '../../utils/with_apm_span'; -import { Setup } from '../../lib/helpers/setup_request'; import { asMutableArray } from '../../../common/utils/as_mutable_array'; import { environmentQuery } from '../../../common/utils/environment_query'; import { calculateImpactBuilder } from './calculate_impact_builder'; @@ -31,6 +30,7 @@ import { TRANSACTION_NAME, } from '../../../common/elasticsearch_fieldnames'; import { RandomSampler } from '../../lib/helpers/get_random_sampler'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export type BucketKey = Record< typeof TRANSACTION_NAME | typeof SERVICE_NAME, @@ -44,7 +44,7 @@ interface TopTracesParams { searchAggregatedTransactions: boolean; start: number; end: number; - setup: Setup; + apmEventClient: APMEventClient; randomSampler: RandomSampler; } export async function getTopTracesPrimaryStats({ @@ -54,11 +54,11 @@ export async function getTopTracesPrimaryStats({ searchAggregatedTransactions, start, end, - setup, + apmEventClient, randomSampler, }: TopTracesParams) { return withApmSpan('get_top_traces_primary_stats', async () => { - const response = await setup.apmEventClient.search( + const response = await apmEventClient.search( 'get_transaction_group_stats', { apm: { diff --git a/x-pack/plugins/apm/server/routes/traces/get_trace_items.ts b/x-pack/plugins/apm/server/routes/traces/get_trace_items.ts index f1c1efc8664c..960924e1aa4b 100644 --- a/x-pack/plugins/apm/server/routes/traces/get_trace_items.ts +++ b/x-pack/plugins/apm/server/routes/traces/get_trace_items.ts @@ -18,16 +18,17 @@ import { TRACE_ID, TRANSACTION_DURATION, } from '../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../lib/helpers/setup_request'; import { getLinkedChildrenCountBySpanId } from '../span_links/get_linked_children'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; +import { APMConfig } from '../..'; export async function getTraceItems( traceId: string, - setup: Setup, + config: APMConfig, + apmEventClient: APMEventClient, start: number, end: number ) { - const { apmEventClient, config } = setup; const maxTraceItems = config.ui.maxTraceItems; const excludedLogLevels = ['debug', 'info', 'warning']; @@ -80,7 +81,7 @@ export async function getTraceItems( await Promise.all([ errorResponsePromise, traceResponsePromise, - getLinkedChildrenCountBySpanId({ traceId, setup, start, end }), + getLinkedChildrenCountBySpanId({ traceId, apmEventClient, start, end }), ]); const exceedsMax = traceResponse.hits.total.value > maxTraceItems; diff --git a/x-pack/plugins/apm/server/routes/traces/get_trace_samples_by_query.ts b/x-pack/plugins/apm/server/routes/traces/get_trace_samples_by_query.ts index 5f56d20dbbf9..2e74d104592d 100644 --- a/x-pack/plugins/apm/server/routes/traces/get_trace_samples_by_query.ts +++ b/x-pack/plugins/apm/server/routes/traces/get_trace_samples_by_query.ts @@ -11,7 +11,6 @@ import { } from '@kbn/observability-plugin/server'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; import { Environment } from '../../../common/environment_rt'; -import { Setup } from '../../lib/helpers/setup_request'; import { TraceSearchType } from '../../../common/trace_explorer'; import { environmentQuery } from '../../../common/utils/environment_query'; import { @@ -22,16 +21,17 @@ import { TRANSACTION_SAMPLED, } from '../../../common/elasticsearch_fieldnames'; import { asMutableArray } from '../../../common/utils/as_mutable_array'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getTraceSamplesByQuery({ - setup, + apmEventClient, start, end, environment, query, type, }: { - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; environment: Environment; @@ -45,7 +45,7 @@ export async function getTraceSamplesByQuery({ if (type === TraceSearchType.kql) { traceIds = ( - await setup.apmEventClient.search('get_trace_ids_by_kql_query', { + await apmEventClient.search('get_trace_ids_by_kql_query', { apm: { events: [ ProcessorEvent.transaction, @@ -81,7 +81,7 @@ export async function getTraceSamplesByQuery({ } else if (type === TraceSearchType.eql) { traceIds = ( - await setup.apmEventClient.eqlSearch('get_trace_ids_by_eql_query', { + await apmEventClient.eqlSearch('get_trace_ids_by_eql_query', { apm: { events: [ ProcessorEvent.transaction, @@ -115,7 +115,7 @@ export async function getTraceSamplesByQuery({ return []; } - const traceSamplesResponse = await setup.apmEventClient.search( + const traceSamplesResponse = await apmEventClient.search( 'get_trace_samples_by_trace_ids', { apm: { diff --git a/x-pack/plugins/apm/server/routes/traces/queries.test.ts b/x-pack/plugins/apm/server/routes/traces/queries.test.ts index f1bd97fd88eb..64e08d11acc7 100644 --- a/x-pack/plugins/apm/server/routes/traces/queries.test.ts +++ b/x-pack/plugins/apm/server/routes/traces/queries.test.ts @@ -19,8 +19,8 @@ describe('trace queries', () => { }); it('fetches a trace', async () => { - mock = await inspectSearchParams((setup) => - getTraceItems('foo', setup, 0, 50000) + mock = await inspectSearchParams((setup, apmEventClient) => + getTraceItems('foo', setup.config, apmEventClient, 0, 50000) ); expect(mock.params).toMatchSnapshot(); diff --git a/x-pack/plugins/apm/server/routes/traces/route.ts b/x-pack/plugins/apm/server/routes/traces/route.ts index 336e862bcd09..dc1408f9bdde 100644 --- a/x-pack/plugins/apm/server/routes/traces/route.ts +++ b/x-pack/plugins/apm/server/routes/traces/route.ts @@ -22,6 +22,7 @@ import { getTopTracesPrimaryStats } from './get_top_traces_primary_stats'; import { getTraceItems } from './get_trace_items'; import { getTraceSamplesByQuery } from './get_trace_samples_by_query'; import { getRandomSampler } from '../../lib/helpers/get_random_sampler'; +import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; const tracesRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/traces', @@ -51,13 +52,15 @@ const tracesRoute = createApmServerRoute({ const { environment, kuery, start, end, probability } = params.query; - const [setup, randomSampler] = await Promise.all([ + const [setup, apmEventClient, randomSampler] = await Promise.all([ setupRequest(resources), + getApmEventClient(resources), getRandomSampler({ security, request, probability }), ]); const searchAggregatedTransactions = await getSearchTransactionsEvents({ - ...setup, + apmEventClient, + config: setup.config, kuery, start, end, @@ -66,7 +69,7 @@ const tracesRoute = createApmServerRoute({ return await getTopTracesPrimaryStats({ environment, kuery, - setup, + apmEventClient, searchAggregatedTransactions, start, end, @@ -97,12 +100,15 @@ const tracesByIdRoute = createApmServerRoute({ >; linkedChildrenOfSpanCountBySpanId: Record; }> => { - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { params } = resources; const { traceId } = params.path; const { start, end } = params.query; - - return getTraceItems(traceId, setup, start, end); + const { config } = setup; + return getTraceItems(traceId, config, apmEventClient, start, end); }, }); @@ -121,8 +127,8 @@ const rootTransactionByTraceIdRoute = createApmServerRoute({ }> => { const { params } = resources; const { traceId } = params.path; - const setup = await setupRequest(resources); - return getRootTransactionByTraceId(traceId, setup); + const apmEventClient = await getApmEventClient(resources); + return getRootTransactionByTraceId(traceId, apmEventClient); }, }); @@ -141,9 +147,9 @@ const transactionByIdRoute = createApmServerRoute({ }> => { const { params } = resources; const { transactionId } = params.path; - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); return { - transaction: await getTransaction({ transactionId, setup }), + transaction: await getTransaction({ transactionId, apmEventClient }), }; }, }); @@ -173,11 +179,11 @@ const findTracesRoute = createApmServerRoute({ }> => { const { start, end, environment, query, type } = resources.params.query; - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); return { traceSamples: await getTraceSamplesByQuery({ - setup, + apmEventClient, start, end, environment, diff --git a/x-pack/plugins/apm/server/routes/transactions/breakdown/index.test.ts b/x-pack/plugins/apm/server/routes/transactions/breakdown/index.test.ts index 2df04fcdb054..96487f7a6be7 100644 --- a/x-pack/plugins/apm/server/routes/transactions/breakdown/index.test.ts +++ b/x-pack/plugins/apm/server/routes/transactions/breakdown/index.test.ts @@ -27,7 +27,6 @@ const mockIndices: ApmIndicesConfig = { function getMockSetup(esResponse: any) { const clientSpy = jest.fn().mockReturnValueOnce(esResponse); return { - apmEventClient: { search: clientSpy } as any, internalClient: { search: clientSpy } as any, config: new Proxy( {}, @@ -39,87 +38,107 @@ function getMockSetup(esResponse: any) { }; } +function getMockApmEventClient(esResponse: any) { + const apmEventClientSpy = jest.fn().mockReturnValueOnce(esResponse); + return { apmEventClient: { search: apmEventClientSpy } as any }; +} + describe('getTransactionBreakdown', () => { - it('returns an empty array if no data is available', async () => { - const response = await getTransactionBreakdown({ - serviceName: 'myServiceName', - transactionType: 'request', - setup: getMockSetup(noDataResponse), - environment: ENVIRONMENT_ALL.value, - kuery: '', - start: 0, - end: 500000, + describe('when no data is available', () => { + const { config } = getMockSetup(noDataResponse); + const { apmEventClient } = getMockApmEventClient(noDataResponse); + it('returns an empty array if no data is available', async () => { + const response = await getTransactionBreakdown({ + serviceName: 'myServiceName', + transactionType: 'request', + config, + apmEventClient, + environment: ENVIRONMENT_ALL.value, + kuery: '', + start: 0, + end: 500000, + }); + + expect(Object.keys(response.timeseries).length).toBe(0); }); - - expect(Object.keys(response.timeseries).length).toBe(0); }); - it('returns a timeseries grouped by type and subtype', async () => { - const response = await getTransactionBreakdown({ - serviceName: 'myServiceName', - transactionType: 'request', - setup: getMockSetup(dataResponse), - environment: ENVIRONMENT_ALL.value, - kuery: '', - start: 0, - end: 500000, + describe('when data is returned', () => { + it('returns a timeseries grouped by type and subtype', async () => { + const { config } = getMockSetup(dataResponse); + const { apmEventClient } = getMockApmEventClient(dataResponse); + const response = await getTransactionBreakdown({ + serviceName: 'myServiceName', + transactionType: 'request', + config, + apmEventClient, + environment: ENVIRONMENT_ALL.value, + kuery: '', + start: 0, + end: 500000, + }); + + const { timeseries } = response; + + expect(timeseries.length).toBe(4); + + const appTimeseries = timeseries[0]; + expect(appTimeseries.title).toBe('app'); + expect(appTimeseries.type).toBe('areaStacked'); + expect(appTimeseries.hideLegend).toBe(false); + + // empty buckets should result in null values for visible types + expect(appTimeseries.data.length).toBe(276); + expect(appTimeseries.data.length).not.toBe(257); + + expect(appTimeseries.data[0].x).toBe(1561102380000); + + expect(appTimeseries.data[0].y).toBeCloseTo(0.8689440187037277); }); - const { timeseries } = response; - - expect(timeseries.length).toBe(4); - - const appTimeseries = timeseries[0]; - expect(appTimeseries.title).toBe('app'); - expect(appTimeseries.type).toBe('areaStacked'); - expect(appTimeseries.hideLegend).toBe(false); - - // empty buckets should result in null values for visible types - expect(appTimeseries.data.length).toBe(276); - expect(appTimeseries.data.length).not.toBe(257); - - expect(appTimeseries.data[0].x).toBe(1561102380000); - - expect(appTimeseries.data[0].y).toBeCloseTo(0.8689440187037277); - }); - - it('should not include more KPIs than MAX_KPIs', async () => { - // @ts-expect-error - constants.MAX_KPIS = 2; - - const response = await getTransactionBreakdown({ - serviceName: 'myServiceName', - transactionType: 'request', - setup: getMockSetup(dataResponse), - environment: ENVIRONMENT_ALL.value, - kuery: '', - start: 0, - end: 500000, + it('should not include more KPIs than MAX_KPIs', async () => { + const { config } = getMockSetup(dataResponse); + const { apmEventClient } = getMockApmEventClient(dataResponse); + // @ts-expect-error + constants.MAX_KPIS = 2; + + const response = await getTransactionBreakdown({ + serviceName: 'myServiceName', + transactionType: 'request', + config, + apmEventClient, + environment: ENVIRONMENT_ALL.value, + kuery: '', + start: 0, + end: 500000, + }); + const { timeseries } = response; + + expect(timeseries.map((serie) => serie.title)).toEqual(['app', 'http']); }); - const { timeseries } = response; - - expect(timeseries.map((serie) => serie.title)).toEqual(['app', 'http']); - }); - - it('fills in gaps for a given timestamp', async () => { - const response = await getTransactionBreakdown({ - serviceName: 'myServiceName', - transactionType: 'request', - setup: getMockSetup(dataResponse), - environment: ENVIRONMENT_ALL.value, - kuery: '', - start: 0, - end: 500000, + it('fills in gaps for a given timestamp', async () => { + const { config } = getMockSetup(dataResponse); + const { apmEventClient } = getMockApmEventClient(dataResponse); + const response = await getTransactionBreakdown({ + serviceName: 'myServiceName', + transactionType: 'request', + config, + apmEventClient, + environment: ENVIRONMENT_ALL.value, + kuery: '', + start: 0, + end: 500000, + }); + + const { timeseries } = response; + + const appTimeseries = timeseries.find((series) => series.title === 'app'); + + // missing values should be 0 if other span types do have data for that timestamp + expect( + (appTimeseries as NonNullable).data[1].y + ).toBe(0); }); - - const { timeseries } = response; - - const appTimeseries = timeseries.find((series) => series.title === 'app'); - - // missing values should be 0 if other span types do have data for that timestamp - expect((appTimeseries as NonNullable).data[1].y).toBe( - 0 - ); }); }); diff --git a/x-pack/plugins/apm/server/routes/transactions/breakdown/index.ts b/x-pack/plugins/apm/server/routes/transactions/breakdown/index.ts index 0c2293ec980c..5630a4c0c475 100644 --- a/x-pack/plugins/apm/server/routes/transactions/breakdown/index.ts +++ b/x-pack/plugins/apm/server/routes/transactions/breakdown/index.ts @@ -17,16 +17,18 @@ import { TRANSACTION_TYPE, TRANSACTION_NAME, } from '../../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../../lib/helpers/setup_request'; import { environmentQuery } from '../../../../common/utils/environment_query'; import { getMetricsDateHistogramParams } from '../../../lib/helpers/metrics'; import { MAX_KPIS } from './constants'; import { getVizColorForIndex } from '../../../../common/viz_colors'; +import { APMConfig } from '../../..'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; export async function getTransactionBreakdown({ environment, kuery, - setup, + config, + apmEventClient, serviceName, transactionName, transactionType, @@ -35,15 +37,14 @@ export async function getTransactionBreakdown({ }: { environment: string; kuery: string; - setup: Setup; + config: APMConfig; + apmEventClient: APMEventClient; serviceName: string; transactionName?: string; transactionType: string; start: number; end: number; }) { - const { apmEventClient, config } = setup; - const subAggs = { sum_all_self_times: { sum: { diff --git a/x-pack/plugins/apm/server/routes/transactions/get_failed_transaction_rate_periods.ts b/x-pack/plugins/apm/server/routes/transactions/get_failed_transaction_rate_periods.ts index 0538832b6e84..8a5ec8c83814 100644 --- a/x-pack/plugins/apm/server/routes/transactions/get_failed_transaction_rate_periods.ts +++ b/x-pack/plugins/apm/server/routes/transactions/get_failed_transaction_rate_periods.ts @@ -4,9 +4,9 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { Setup } from '../../lib/helpers/setup_request'; import { getFailedTransactionRate } from '../../lib/transaction_groups/get_failed_transaction_rate'; import { offsetPreviousPeriodCoordinates } from '../../../common/utils/offset_previous_period_coordinate'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getFailedTransactionRatePeriods({ environment, @@ -14,7 +14,7 @@ export async function getFailedTransactionRatePeriods({ serviceName, transactionType, transactionName, - setup, + apmEventClient, searchAggregatedTransactions, start, end, @@ -25,7 +25,7 @@ export async function getFailedTransactionRatePeriods({ serviceName: string; transactionType: string; transactionName?: string; - setup: Setup; + apmEventClient: APMEventClient; searchAggregatedTransactions: boolean; start: number; end: number; @@ -37,7 +37,7 @@ export async function getFailedTransactionRatePeriods({ serviceName, transactionTypes: [transactionType], transactionName, - setup, + apmEventClient, searchAggregatedTransactions, }; diff --git a/x-pack/plugins/apm/server/routes/transactions/get_latency_charts/index.ts b/x-pack/plugins/apm/server/routes/transactions/get_latency_charts/index.ts index 47cc552fe4b5..be534d877d9f 100644 --- a/x-pack/plugins/apm/server/routes/transactions/get_latency_charts/index.ts +++ b/x-pack/plugins/apm/server/routes/transactions/get_latency_charts/index.ts @@ -11,6 +11,7 @@ import { termQuery, } from '@kbn/observability-plugin/server'; import { + FAAS_ID, SERVICE_NAME, TRANSACTION_NAME, TRANSACTION_TYPE, @@ -23,13 +24,13 @@ import { getDurationFieldForTransactions, getProcessorEventForTransactions, } from '../../../lib/helpers/transactions'; -import { Setup } from '../../../lib/helpers/setup_request'; import { getBucketSizeForAggregatedTransactions } from '../../../lib/helpers/get_bucket_size_for_aggregated_transactions'; import { getLatencyAggregation, getLatencyValue, } from '../../../lib/helpers/latency_aggregation_type'; import { getOffsetInMs } from '../../../../common/utils/get_offset_in_ms'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; export type LatencyChartsSearchResponse = Awaited< ReturnType @@ -41,27 +42,27 @@ function searchLatency({ serviceName, transactionType, transactionName, - setup, + apmEventClient, searchAggregatedTransactions, latencyAggregationType, start, end, offset, + serverlessId, }: { environment: string; kuery: string; serviceName: string; transactionType: string | undefined; transactionName: string | undefined; - setup: Setup; + apmEventClient: APMEventClient; searchAggregatedTransactions: boolean; latencyAggregationType: LatencyAggregationType; start: number; end: number; offset?: string; + serverlessId?: string; }) { - const { apmEventClient } = setup; - const { startWithOffset, endWithOffset } = getOffsetInMs({ start, end, @@ -97,6 +98,7 @@ function searchLatency({ ...kqlQuery(kuery), ...termQuery(TRANSACTION_NAME, transactionName), ...termQuery(TRANSACTION_TYPE, transactionType), + ...termQuery(FAAS_ID, serverlessId), ], }, }, @@ -127,24 +129,26 @@ export async function getLatencyTimeseries({ serviceName, transactionType, transactionName, - setup, + apmEventClient, searchAggregatedTransactions, latencyAggregationType, start, end, offset, + serverlessId, }: { environment: string; kuery: string; serviceName: string; transactionType?: string; transactionName?: string; - setup: Setup; + apmEventClient: APMEventClient; searchAggregatedTransactions: boolean; latencyAggregationType: LatencyAggregationType; start: number; end: number; offset?: string; + serverlessId?: string; }) { const response = await searchLatency({ environment, @@ -152,12 +156,13 @@ export async function getLatencyTimeseries({ serviceName, transactionType, transactionName, - setup, + apmEventClient, searchAggregatedTransactions, latencyAggregationType, start, end, offset, + serverlessId, }); if (!response.aggregations) { @@ -185,7 +190,7 @@ export async function getLatencyPeriods({ serviceName, transactionType, transactionName, - setup, + apmEventClient, searchAggregatedTransactions, latencyAggregationType, kuery, @@ -197,7 +202,7 @@ export async function getLatencyPeriods({ serviceName: string; transactionType: string | undefined; transactionName: string | undefined; - setup: Setup; + apmEventClient: APMEventClient; searchAggregatedTransactions: boolean; latencyAggregationType: LatencyAggregationType; kuery: string; @@ -210,7 +215,7 @@ export async function getLatencyPeriods({ serviceName, transactionType, transactionName, - setup, + apmEventClient, searchAggregatedTransactions, kuery, environment, diff --git a/x-pack/plugins/apm/server/routes/transactions/get_transaction/index.ts b/x-pack/plugins/apm/server/routes/transactions/get_transaction/index.ts index f3eab3707e1e..9ca077744899 100644 --- a/x-pack/plugins/apm/server/routes/transactions/get_transaction/index.ts +++ b/x-pack/plugins/apm/server/routes/transactions/get_transaction/index.ts @@ -11,24 +11,22 @@ import { TRACE_ID, TRANSACTION_ID, } from '../../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../../lib/helpers/setup_request'; import { asMutableArray } from '../../../../common/utils/as_mutable_array'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; export async function getTransaction({ transactionId, traceId, - setup, + apmEventClient, start, end, }: { transactionId: string; traceId?: string; - setup: Setup; + apmEventClient: APMEventClient; start?: number; end?: number; }) { - const { apmEventClient } = setup; - const resp = await apmEventClient.search('get_transaction', { apm: { events: [ProcessorEvent.transaction], diff --git a/x-pack/plugins/apm/server/routes/transactions/get_transaction_by_trace/index.ts b/x-pack/plugins/apm/server/routes/transactions/get_transaction_by_trace/index.ts index ca3b3bba1230..0f27d37c2b0a 100644 --- a/x-pack/plugins/apm/server/routes/transactions/get_transaction_by_trace/index.ts +++ b/x-pack/plugins/apm/server/routes/transactions/get_transaction_by_trace/index.ts @@ -10,14 +10,12 @@ import { TRACE_ID, PARENT_ID, } from '../../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../../lib/helpers/setup_request'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; export async function getRootTransactionByTraceId( traceId: string, - setup: Setup + apmEventClient: APMEventClient ) { - const { apmEventClient } = setup; - const params = { apm: { events: [ProcessorEvent.transaction as const], diff --git a/x-pack/plugins/apm/server/routes/transactions/queries.test.ts b/x-pack/plugins/apm/server/routes/transactions/queries.test.ts index 4770dce0a1cb..be11546ee20a 100644 --- a/x-pack/plugins/apm/server/routes/transactions/queries.test.ts +++ b/x-pack/plugins/apm/server/routes/transactions/queries.test.ts @@ -22,11 +22,12 @@ describe('transaction queries', () => { }); it('fetches breakdown data for transactions', async () => { - mock = await inspectSearchParams((setup) => + mock = await inspectSearchParams((setup, apmEventClient) => getTransactionBreakdown({ serviceName: 'foo', transactionType: 'bar', - setup, + config: setup.config, + apmEventClient, environment: ENVIRONMENT_ALL.value, kuery: '', start: 0, @@ -38,12 +39,13 @@ describe('transaction queries', () => { }); it('fetches breakdown data for transactions for a transaction name', async () => { - mock = await inspectSearchParams((setup) => + mock = await inspectSearchParams((setup, apmEventClient) => getTransactionBreakdown({ serviceName: 'foo', transactionType: 'bar', transactionName: 'baz', - setup, + config: setup.config, + apmEventClient, environment: ENVIRONMENT_ALL.value, kuery: '', start: 0, @@ -55,14 +57,14 @@ describe('transaction queries', () => { }); it('fetches transaction trace samples', async () => { - mock = await inspectSearchParams((setup) => + mock = await inspectSearchParams((setup, apmEventClient) => getTraceSamples({ serviceName: 'foo', transactionName: 'bar', transactionType: 'baz', traceId: 'qux', transactionId: 'quz', - setup, + apmEventClient, environment: ENVIRONMENT_ALL.value, kuery: '', start: 0, @@ -74,11 +76,11 @@ describe('transaction queries', () => { }); it('fetches a transaction', async () => { - mock = await inspectSearchParams((setup) => + mock = await inspectSearchParams((setup, apmEventClient) => getTransaction({ transactionId: 'foo', traceId: 'bar', - setup, + apmEventClient, start: 0, end: 50000, }) diff --git a/x-pack/plugins/apm/server/routes/transactions/route.ts b/x-pack/plugins/apm/server/routes/transactions/route.ts index 3bc4dbfe7aae..b862fdd37cb5 100644 --- a/x-pack/plugins/apm/server/routes/transactions/route.ts +++ b/x-pack/plugins/apm/server/routes/transactions/route.ts @@ -23,6 +23,7 @@ import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { environmentRt, kueryRt, rangeRt } from '../default_api_types'; import { offsetRt } from '../../../common/comparison_rt'; import { getTraceSamples } from './trace_samples'; +import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; const transactionGroupsMainStatisticsRoute = createApmServerRoute({ endpoint: @@ -57,7 +58,10 @@ const transactionGroupsMainStatisticsRoute = createApmServerRoute({ bucketSize: number; }> => { const { params } = resources; - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { path: { serviceName }, query: { @@ -69,9 +73,10 @@ const transactionGroupsMainStatisticsRoute = createApmServerRoute({ end, }, } = params; - + const { config } = setup; const searchAggregatedTransactions = await getSearchTransactionsEvents({ - ...setup, + apmEventClient, + config, kuery, start, end, @@ -80,7 +85,8 @@ const transactionGroupsMainStatisticsRoute = createApmServerRoute({ return getServiceTransactionGroups({ environment, kuery, - setup, + config, + apmEventClient, serviceName, searchAggregatedTransactions, transactionType, @@ -139,7 +145,10 @@ const transactionGroupsDetailedStatisticsRoute = createApmServerRoute({ impact: number; }>; }> => { - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { params } = resources; const { @@ -158,7 +167,8 @@ const transactionGroupsDetailedStatisticsRoute = createApmServerRoute({ } = params; const searchAggregatedTransactions = await getSearchTransactionsEvents({ - ...setup, + config: setup.config, + apmEventClient, kuery, start, end, @@ -167,7 +177,7 @@ const transactionGroupsDetailedStatisticsRoute = createApmServerRoute({ return await getServiceTransactionGroupDetailedStatisticsPeriods({ environment, kuery, - setup, + apmEventClient, serviceName, transactionNames, searchAggregatedTransactions, @@ -221,7 +231,10 @@ const transactionLatencyChartsRoute = createApmServerRoute({ overallAvgDuration: null; }; }> => { - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { params, logger } = resources; const { serviceName } = params.path; @@ -237,7 +250,8 @@ const transactionLatencyChartsRoute = createApmServerRoute({ } = params.query; const searchAggregatedTransactions = await getSearchTransactionsEvents({ - ...setup, + config: setup.config, + apmEventClient, kuery, start, end, @@ -249,7 +263,7 @@ const transactionLatencyChartsRoute = createApmServerRoute({ serviceName, transactionType, transactionName, - setup, + apmEventClient, searchAggregatedTransactions, logger, start, @@ -298,7 +312,7 @@ const transactionTraceSamplesRoute = createApmServerRoute({ ): Promise<{ traceSamples: Array<{ transactionId: string; traceId: string }>; }> => { - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { params } = resources; const { serviceName } = params.path; const { @@ -324,7 +338,7 @@ const transactionTraceSamplesRoute = createApmServerRoute({ traceId, sampleRangeFrom, sampleRangeTo, - setup, + apmEventClient, start, end, }); @@ -359,7 +373,10 @@ const transactionChartsBreakdownRoute = createApmServerRoute({ legendValue: string; }>; }> => { - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { params } = resources; const { serviceName } = params.path; @@ -372,7 +389,8 @@ const transactionChartsBreakdownRoute = createApmServerRoute({ serviceName, transactionName, transactionType, - setup, + config: setup.config, + apmEventClient, start, end, }); @@ -416,7 +434,10 @@ const transactionChartsErrorRateRoute = createApmServerRoute({ average: null; }; }> => { - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { params } = resources; const { serviceName } = params.path; @@ -431,7 +452,8 @@ const transactionChartsErrorRateRoute = createApmServerRoute({ } = params.query; const searchAggregatedTransactions = await getSearchTransactionsEvents({ - ...setup, + config: setup.config, + apmEventClient, kuery, start, end, @@ -443,7 +465,7 @@ const transactionChartsErrorRateRoute = createApmServerRoute({ serviceName, transactionType, transactionName, - setup, + apmEventClient, searchAggregatedTransactions, start, end, @@ -490,7 +512,10 @@ const transactionChartsColdstartRateRoute = createApmServerRoute({ average: null; }; }> => { - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { params } = resources; const { serviceName } = params.path; @@ -498,7 +523,8 @@ const transactionChartsColdstartRateRoute = createApmServerRoute({ params.query; const searchAggregatedTransactions = await getSearchTransactionsEvents({ - ...setup, + config: setup.config, + apmEventClient, kuery, start, end, @@ -509,7 +535,7 @@ const transactionChartsColdstartRateRoute = createApmServerRoute({ kuery, serviceName, transactionType, - setup, + apmEventClient, searchAggregatedTransactions, start, end, @@ -557,7 +583,10 @@ const transactionChartsColdstartRateByTransactionNameRoute = average: null; }; }> => { - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { params } = resources; const { serviceName } = params.path; @@ -572,7 +601,8 @@ const transactionChartsColdstartRateByTransactionNameRoute = } = params.query; const searchAggregatedTransactions = await getSearchTransactionsEvents({ - ...setup, + config: setup.config, + apmEventClient, kuery, start, end, @@ -584,7 +614,7 @@ const transactionChartsColdstartRateByTransactionNameRoute = serviceName, transactionType, transactionName, - setup, + apmEventClient, searchAggregatedTransactions, start, end, diff --git a/x-pack/plugins/apm/server/routes/transactions/trace_samples/index.ts b/x-pack/plugins/apm/server/routes/transactions/trace_samples/index.ts index 572fa1a1c6d1..9fe3e7ff7933 100644 --- a/x-pack/plugins/apm/server/routes/transactions/trace_samples/index.ts +++ b/x-pack/plugins/apm/server/routes/transactions/trace_samples/index.ts @@ -18,7 +18,7 @@ import { TRANSACTION_TYPE, } from '../../../../common/elasticsearch_fieldnames'; import { environmentQuery } from '../../../../common/utils/environment_query'; -import { Setup } from '../../../lib/helpers/setup_request'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; const TRACE_SAMPLES_SIZE = 500; @@ -32,7 +32,7 @@ export async function getTraceSamples({ traceId, sampleRangeFrom, sampleRangeTo, - setup, + apmEventClient, start, end, }: { @@ -45,13 +45,11 @@ export async function getTraceSamples({ traceId: string; sampleRangeFrom?: number; sampleRangeTo?: number; - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; }) { return withApmSpan('get_trace_samples', async () => { - const { apmEventClient } = setup; - const commonFilters = [ { term: { [SERVICE_NAME]: serviceName } }, { term: { [TRANSACTION_TYPE]: transactionType } }, diff --git a/x-pack/plugins/apm/server/test_helpers/create_apm_users/authentication.ts b/x-pack/plugins/apm/server/test_helpers/create_apm_users/authentication.ts index f3d15ee6db21..f4ea0629f848 100644 --- a/x-pack/plugins/apm/server/test_helpers/create_apm_users/authentication.ts +++ b/x-pack/plugins/apm/server/test_helpers/create_apm_users/authentication.ts @@ -15,7 +15,7 @@ export enum ApmUsername { apmReadUserWithoutMlAccess = 'apm_read_user_without_ml_access', apmManageOwnAgentKeys = 'apm_manage_own_agent_keys', apmManageOwnAndCreateAgentKeys = 'apm_manage_own_and_create_agent_keys', - apmMonitorIndices = 'apm_monitor_indices', + apmMonitorClusterAndIndices = 'apm_monitor_cluster_and_indices', } export enum ApmCustomRolename { @@ -23,7 +23,7 @@ export enum ApmCustomRolename { apmAnnotationsWriteUser = 'apm_annotations_write_user', apmManageOwnAgentKeys = 'apm_manage_own_agent_keys', apmManageOwnAndCreateAgentKeys = 'apm_manage_own_and_create_agent_keys', - apmMonitorIndices = 'apm_monitor_indices', + apmMonitorClusterAndIndices = 'apm_monitor_cluster_and_indices', } export const customRoles = { @@ -77,7 +77,7 @@ export const customRoles = { }, ], }, - [ApmCustomRolename.apmMonitorIndices]: { + [ApmCustomRolename.apmMonitorClusterAndIndices]: { elasticsearch: { indices: [ { @@ -85,6 +85,7 @@ export const customRoles = { privileges: ['monitor'], }, ], + cluster: ['monitor'], }, }, }; @@ -118,9 +119,9 @@ export const users: Record< ApmCustomRolename.apmManageOwnAndCreateAgentKeys, ], }, - [ApmUsername.apmMonitorIndices]: { + [ApmUsername.apmMonitorClusterAndIndices]: { builtInRoleNames: ['viewer'], - customRoleNames: [ApmCustomRolename.apmMonitorIndices], + customRoleNames: [ApmCustomRolename.apmMonitorClusterAndIndices], }, }; diff --git a/x-pack/plugins/apm/server/utils/test_helpers.tsx b/x-pack/plugins/apm/server/utils/test_helpers.tsx index c39e2b78d07c..db69e4d78ea4 100644 --- a/x-pack/plugins/apm/server/utils/test_helpers.tsx +++ b/x-pack/plugins/apm/server/utils/test_helpers.tsx @@ -7,6 +7,7 @@ import type { ESSearchRequest, ESSearchResponse } from '@kbn/es-types'; import { APMConfig } from '..'; +import { APMEventClient } from '../lib/helpers/create_es_client/create_apm_event_client'; import { ApmIndicesConfig } from '../routes/settings/apm_indices/get_apm_indices'; interface Options { @@ -17,14 +18,16 @@ interface Options { } interface MockSetup { - apmEventClient: any; internalClient: any; config: APMConfig; indices: ApmIndicesConfig; } export async function inspectSearchParams( - fn: (mockSetup: MockSetup) => Promise, + fn: ( + mockSetup: MockSetup, + mockApmEventClient: APMEventClient + ) => Promise, options: Options = {} ) { const spy = jest.fn().mockImplementation(async (request) => { @@ -43,7 +46,7 @@ export async function inspectSearchParams( let response; let error; - + const mockApmEventClient = { search: spy } as any; const mockApmIndices: { [Property in keyof APMConfig['indices']]: string; } = { @@ -55,7 +58,6 @@ export async function inspectSearchParams( metric: 'myIndex', }; const mockSetup = { - apmEventClient: { search: spy } as any, internalClient: { search: spy } as any, config: new Proxy( {}, @@ -90,7 +92,7 @@ export async function inspectSearchParams( }, }; try { - response = await fn(mockSetup); + response = await fn(mockSetup, mockApmEventClient); } catch (err) { error = err; // we're only extracting the search params diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/escount.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/escount.ts index 7bdb13cff93c..b324d2f11a0a 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/escount.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/escount.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { lastValueFrom } from 'rxjs'; + import { ExpressionFunctionDefinition, ExpressionValueFilter, @@ -12,10 +14,8 @@ import { // @ts-expect-error untyped local import { buildESRequest } from '../../../common/lib/request/build_es_request'; - -import { searchService } from '../../../public/services'; - import { getFunctionHelp } from '../../../i18n'; +import { searchService } from '../../../public/services'; interface Arguments { index: string | null; @@ -46,6 +46,7 @@ export function escount(): ExpressionFunctionDefinition< }, index: { types: ['string'], + aliases: ['dataView'], default: '_all', help: argHelp.index, }, @@ -83,12 +84,9 @@ export function escount(): ExpressionFunctionDefinition< }, }; - return search - .search(req) - .toPromise() - .then((resp: any) => { - return resp.rawResponse.hits.total; - }); + return lastValueFrom(search.search(req)).then((resp: any) => { + return resp.rawResponse.hits.total; + }); }, }; } diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/esdocs.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/esdocs.ts index 83225fcafb8d..763b1839ec3b 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/esdocs.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/esdocs.ts @@ -70,6 +70,7 @@ export function esdocs(): ExpressionFunctionDefinition< }, index: { types: ['string'], + aliases: ['dataView'], default: '_all', help: argHelp.index, }, diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/datasources/esdocs.js b/x-pack/plugins/canvas/canvas_plugin_src/uis/datasources/esdocs.js index 150b7c061688..ae0af7b2297f 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/datasources/esdocs.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/datasources/esdocs.js @@ -20,7 +20,7 @@ import { import { getSimpleArg, setSimpleArg } from '../../../public/lib/arg_helpers'; import { ESFieldsSelect } from '../../../public/components/es_fields_select'; import { ESFieldSelect } from '../../../public/components/es_field_select'; -import { ESIndexSelect } from '../../../public/components/es_index_select'; +import { ESDataViewSelect } from '../../../public/components/es_data_view_select'; import { templateFromReactComponent } from '../../../public/lib/template_from_react_component'; import { DataSourceStrings, LUCENE_QUERY_URL } from '../../../i18n'; @@ -29,6 +29,7 @@ const { ESDocs: strings } = DataSourceStrings; const EsdocsDatasource = ({ args, updateArgs, defaultIndex }) => { const setArg = useCallback( (name, value) => { + console.log({ name, value }); updateArgs && updateArgs({ ...args, @@ -94,7 +95,7 @@ const EsdocsDatasource = ({ args, updateArgs, defaultIndex }) => { helpText={strings.getIndexLabel()} display="rowCompressed" > - setArg('index', index)} /> + setArg('index', index)} /> i18n.translate('xpack.canvas.uis.dataSources.esdocs.indexTitle', { - defaultMessage: 'Index', + defaultMessage: 'Data view', }), getIndexLabel: () => i18n.translate('xpack.canvas.uis.dataSources.esdocs.indexLabel', { - defaultMessage: 'Enter an index name or select a data view', + defaultMessage: 'Select a data view or enter an index name.', }), getQueryTitle: () => i18n.translate('xpack.canvas.uis.dataSources.esdocs.queryTitle', { diff --git a/x-pack/plugins/canvas/kibana.json b/x-pack/plugins/canvas/kibana.json index 48f4d9a6b0d5..f63c3522a8df 100644 --- a/x-pack/plugins/canvas/kibana.json +++ b/x-pack/plugins/canvas/kibana.json @@ -14,6 +14,7 @@ "bfetch", "charts", "data", + "dataViews", "embeddable", "expressionError", "expressionImage", diff --git a/x-pack/plugins/canvas/public/components/datasource/datasource_component.js b/x-pack/plugins/canvas/public/components/datasource/datasource_component.js index 8e2176b845bf..1a357b6722c7 100644 --- a/x-pack/plugins/canvas/public/components/datasource/datasource_component.js +++ b/x-pack/plugins/canvas/public/components/datasource/datasource_component.js @@ -20,7 +20,7 @@ import { import { isEqual } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { getDefaultIndex } from '../../lib/es_service'; +import { pluginServices } from '../../services'; import { DatasourceSelector } from './datasource_selector'; import { DatasourcePreview } from './datasource_preview'; @@ -67,7 +67,12 @@ export class DatasourceComponent extends PureComponent { state = { defaultIndex: '' }; componentDidMount() { - getDefaultIndex().then((defaultIndex) => this.setState({ defaultIndex })); + pluginServices + .getServices() + .dataViews.getDefaultDataView() + .then((defaultDataView) => { + this.setState({ defaultIndex: defaultDataView.title }); + }); } componentDidUpdate(prevProps) { diff --git a/x-pack/plugins/canvas/public/components/es_index_select/es_index_select.component.tsx b/x-pack/plugins/canvas/public/components/es_data_view_select/es_data_view_select.component.tsx similarity index 50% rename from x-pack/plugins/canvas/public/components/es_index_select/es_index_select.component.tsx rename to x-pack/plugins/canvas/public/components/es_data_view_select/es_data_view_select.component.tsx index 636b19092123..da8b4cda4c17 100644 --- a/x-pack/plugins/canvas/public/components/es_index_select/es_index_select.component.tsx +++ b/x-pack/plugins/canvas/public/components/es_data_view_select/es_data_view_select.component.tsx @@ -7,37 +7,47 @@ import React, { FocusEventHandler } from 'react'; import { EuiComboBox } from '@elastic/eui'; +import { DataView } from '@kbn/data-views-plugin/common'; -export interface ESIndexSelectProps { +type DataViewOption = Pick; + +export interface ESDataViewSelectProps { loading: boolean; value: string; - indices: string[]; - onChange: (index: string) => void; + dataViews: DataViewOption[]; + onChange: (string: string) => void; onBlur: FocusEventHandler | undefined; onFocus: FocusEventHandler | undefined; } const defaultIndex = '_all'; +const defaultOption = { value: defaultIndex, label: defaultIndex }; -export const ESIndexSelect: React.FunctionComponent = ({ +export const ESDataViewSelect: React.FunctionComponent = ({ value = defaultIndex, loading, - indices, + dataViews, onChange, onFocus, onBlur, }) => { - const selectedOption = value !== defaultIndex ? [{ label: value }] : []; - const options = indices.map((index) => ({ label: index })); + const selectedDataView = dataViews.find((view) => value === view.title) as DataView; + + const selectedOption = selectedDataView + ? { value: selectedDataView.title, label: selectedDataView.name } + : { value, label: value }; + const options = dataViews.map(({ name, title }) => ({ value: title, label: name })); return ( onChange(index?.label ?? defaultIndex)} + selectedOptions={[selectedOption]} + onChange={([view]) => { + onChange(view.value || defaultOption.value); + }} onSearchChange={(searchValue) => { // resets input when user starts typing if (searchValue) { - onChange(defaultIndex); + onChange(defaultOption.value); } }} onBlur={onBlur} @@ -46,7 +56,7 @@ export const ESIndexSelect: React.FunctionComponent = ({ options={options} singleSelection={{ asPlainText: true }} isClearable={false} - onCreateOption={(input) => onChange(input || defaultIndex)} + onCreateOption={(input) => onChange(input || defaultOption.value)} compressed /> ); diff --git a/x-pack/plugins/canvas/public/components/es_data_view_select/es_data_view_select.tsx b/x-pack/plugins/canvas/public/components/es_data_view_select/es_data_view_select.tsx new file mode 100644 index 000000000000..83e21b8ba2e6 --- /dev/null +++ b/x-pack/plugins/canvas/public/components/es_data_view_select/es_data_view_select.tsx @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DataView } from '@kbn/data-views-plugin/common'; +import { sortBy } from 'lodash'; +import React, { FC, useRef, useState } from 'react'; +import useEffectOnce from 'react-use/lib/useEffectOnce'; +import { useDataViewsService } from '../../services'; +import { + ESDataViewSelect as Component, + ESDataViewSelectProps as Props, +} from './es_data_view_select.component'; + +type ESDataViewSelectProps = Omit; + +export const ESDataViewSelect: FC = (props) => { + const { value, onChange } = props; + + const [dataViews, setDataViews] = useState>>([]); + const [loading, setLoading] = useState(true); + const mounted = useRef(true); + const { getDataViews } = useDataViewsService(); + + useEffectOnce(() => { + getDataViews().then((newDataViews) => { + if (!mounted.current) { + return; + } + + if (!newDataViews) { + newDataViews = []; + } + + setLoading(false); + setDataViews(sortBy(newDataViews, 'name')); + if (!value && newDataViews.length) { + onChange(newDataViews[0].title); + } + }); + + return () => { + mounted.current = false; + }; + }); + + return ; +}; diff --git a/x-pack/plugins/canvas/public/components/es_data_view_select/index.tsx b/x-pack/plugins/canvas/public/components/es_data_view_select/index.tsx new file mode 100644 index 000000000000..870a6b3aff1a --- /dev/null +++ b/x-pack/plugins/canvas/public/components/es_data_view_select/index.tsx @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { ESDataViewSelect } from './es_data_view_select'; +export { ESDataViewSelect as ESDataViewSelectComponent } from './es_data_view_select.component'; diff --git a/x-pack/plugins/canvas/public/components/es_field_select/index.tsx b/x-pack/plugins/canvas/public/components/es_field_select/index.tsx index 8c0baea68173..653eec22d77d 100644 --- a/x-pack/plugins/canvas/public/components/es_field_select/index.tsx +++ b/x-pack/plugins/canvas/public/components/es_field_select/index.tsx @@ -6,7 +6,7 @@ */ import React, { useState, useEffect, useRef } from 'react'; -import { getFields } from '../../lib/es_service'; +import { useDataViewsService } from '../../services'; import { ESFieldSelect as Component, ESFieldSelectProps as Props } from './es_field_select'; type ESFieldSelectProps = Omit; @@ -15,15 +15,17 @@ export const ESFieldSelect: React.FunctionComponent = (props const { index, value, onChange } = props; const [fields, setFields] = useState([]); const loadingFields = useRef(false); + const { getFields } = useDataViewsService(); useEffect(() => { loadingFields.current = true; + getFields(index) .then((newFields) => setFields(newFields || [])) .finally(() => { loadingFields.current = false; }); - }, [index]); + }, [index, getFields]); useEffect(() => { if (!loadingFields.current && value && !fields.includes(value)) { diff --git a/x-pack/plugins/canvas/public/components/es_fields_select/es_fields_select.tsx b/x-pack/plugins/canvas/public/components/es_fields_select/es_fields_select.tsx index 2f94b154edab..c929203f9e09 100644 --- a/x-pack/plugins/canvas/public/components/es_fields_select/es_fields_select.tsx +++ b/x-pack/plugins/canvas/public/components/es_fields_select/es_fields_select.tsx @@ -8,7 +8,7 @@ import React, { useState, useEffect, useRef } from 'react'; import { isEqual } from 'lodash'; import usePrevious from 'react-use/lib/usePrevious'; -import { getFields } from '../../lib/es_service'; +import { useDataViewsService } from '../../services'; import { ESFieldsSelect as Component, ESFieldsSelectProps as Props, @@ -21,6 +21,7 @@ export const ESFieldsSelect: React.FunctionComponent = (pro const [fields, setFields] = useState([]); const prevIndex = usePrevious(index); const mounted = useRef(true); + const { getFields } = useDataViewsService(); useEffect(() => { if (prevIndex !== index) { @@ -36,7 +37,7 @@ export const ESFieldsSelect: React.FunctionComponent = (pro } }); } - }, [fields, index, onChange, prevIndex, selected]); + }, [fields, index, onChange, prevIndex, selected, getFields]); useEffect( () => () => { diff --git a/x-pack/plugins/canvas/public/components/es_index_select/es_index_select.tsx b/x-pack/plugins/canvas/public/components/es_index_select/es_index_select.tsx deleted file mode 100644 index 7d2e87902d2d..000000000000 --- a/x-pack/plugins/canvas/public/components/es_index_select/es_index_select.tsx +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { useRef, useState } from 'react'; -import useEffectOnce from 'react-use/lib/useEffectOnce'; -import { getIndices } from '../../lib/es_service'; -import { - ESIndexSelect as Component, - ESIndexSelectProps as Props, -} from './es_index_select.component'; - -type ESIndexSelectProps = Omit; - -export const ESIndexSelect: React.FunctionComponent = (props) => { - const { value, onChange } = props; - - const [indices, setIndices] = useState([]); - const [loading, setLoading] = useState(true); - const mounted = useRef(true); - - useEffectOnce(() => { - getIndices().then((newIndices) => { - if (!mounted.current) { - return; - } - - if (!newIndices) { - newIndices = []; - } - - setLoading(false); - setIndices(newIndices.sort()); - if (!value && newIndices.length) { - onChange(newIndices[0]); - } - }); - - return () => { - mounted.current = false; - }; - }); - - return ; -}; diff --git a/x-pack/plugins/canvas/public/lib/es_service.ts b/x-pack/plugins/canvas/public/lib/es_service.ts deleted file mode 100644 index 9d558243c942..000000000000 --- a/x-pack/plugins/canvas/public/lib/es_service.ts +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -// TODO - clint: convert to service abstraction - -import { API_ROUTE } from '../../common/lib/constants'; -import { fetch } from '../../common/lib/fetch'; -import { ErrorStrings } from '../../i18n'; -import { pluginServices } from '../services'; - -const { esService: strings } = ErrorStrings; - -const getApiPath = function () { - const platformService = pluginServices.getServices().platform; - const basePath = platformService.getBasePath(); - return basePath + API_ROUTE; -}; - -const getSavedObjectsClient = function () { - const platformService = pluginServices.getServices().platform; - return platformService.getSavedObjectsClient(); -}; - -const getAdvancedSettings = function () { - const platformService = pluginServices.getServices().platform; - return platformService.getUISettings(); -}; - -export const getFields = (index = '_all') => { - return fetch - .get(`${getApiPath()}/es_fields?index=${index}`) - .then(({ data: mapping }: { data: object }) => - Object.keys(mapping) - .filter((field) => !field.startsWith('_')) // filters out meta fields - .sort() - ) - .catch((err: Error) => { - const notifyService = pluginServices.getServices().notify; - notifyService.error(err, { - title: strings.getFieldsFetchErrorMessage(index), - }); - }); -}; - -export const getIndices = () => - getSavedObjectsClient() - .find<{ title: string }>({ - type: 'index-pattern', - fields: ['title'], - searchFields: ['title'], - perPage: 1000, - }) - .then((resp) => { - return resp.savedObjects.map((savedObject) => { - return savedObject.attributes.title; - }); - }) - .catch((err: Error) => { - const notifyService = pluginServices.getServices().notify; - notifyService.error(err, { title: strings.getIndicesFetchErrorMessage() }); - }); - -export const getDefaultIndex = () => { - const defaultIndexId = getAdvancedSettings().get('defaultIndex'); - - return defaultIndexId - ? getSavedObjectsClient() - .get<{ title: string }>('index-pattern', defaultIndexId) - .then((defaultIndex) => defaultIndex.attributes.title) - .catch((err) => { - const notifyService = pluginServices.getServices().notify; - notifyService.error(err, { title: strings.getDefaultIndexFetchErrorMessage() }); - }) - : Promise.resolve(''); -}; diff --git a/x-pack/plugins/canvas/public/routes/workpad/hooks/use_autoplay_helper.test.tsx b/x-pack/plugins/canvas/public/routes/workpad/hooks/use_autoplay_helper.test.tsx index 085a9093e8b8..acbd425e1d24 100644 --- a/x-pack/plugins/canvas/public/routes/workpad/hooks/use_autoplay_helper.test.tsx +++ b/x-pack/plugins/canvas/public/routes/workpad/hooks/use_autoplay_helper.test.tsx @@ -25,7 +25,7 @@ const getContextWrapper: (context: WorkpadRoutingContextType) => FC = {children}; describe('useAutoplayHelper', () => { - beforeEach(() => jest.useFakeTimers()); + beforeEach(() => jest.useFakeTimers('legacy')); test('starts the timer when fullscreen and autoplay is on', () => { const context = getMockedContext({ isFullscreen: true, @@ -47,7 +47,7 @@ describe('useAutoplayHelper', () => { const { rerender } = renderHook(useAutoplayHelper, { wrapper: getContextWrapper(context) }); - jest.runTimersToTime(context.autoplayInterval - 1); + jest.advanceTimersByTime(context.autoplayInterval - 1); context.isAutoplayPaused = true; diff --git a/x-pack/plugins/canvas/public/routes/workpad/hooks/use_refresh_helper.test.tsx b/x-pack/plugins/canvas/public/routes/workpad/hooks/use_refresh_helper.test.tsx index d502e634ede0..ac64f509b56b 100644 --- a/x-pack/plugins/canvas/public/routes/workpad/hooks/use_refresh_helper.test.tsx +++ b/x-pack/plugins/canvas/public/routes/workpad/hooks/use_refresh_helper.test.tsx @@ -37,7 +37,7 @@ const getContextWrapper: (context: WorkpadRoutingContextType) => FC = describe('useRefreshHelper', () => { beforeEach(() => { jest.resetAllMocks(); - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); test('starts a timer to refresh', () => { @@ -73,7 +73,7 @@ describe('useRefreshHelper', () => { mockGetState.mockReturnValue(state); const { rerender } = renderHook(useRefreshHelper, { wrapper: getContextWrapper(context) }); - jest.runTimersToTime(context.refreshInterval - 1); + jest.advanceTimersByTime(context.refreshInterval - 1); expect(mockDispatch).not.toHaveBeenCalledWith(refreshAction); state.transient.inFlight = true; diff --git a/x-pack/plugins/canvas/public/services/data_views.ts b/x-pack/plugins/canvas/public/services/data_views.ts new file mode 100644 index 000000000000..86faa87bfaa5 --- /dev/null +++ b/x-pack/plugins/canvas/public/services/data_views.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DataView } from '@kbn/data-views-plugin/common'; + +export interface CanvasDataViewsService { + getFields: (index: string) => Promise; + getDataViews: () => Promise>>; + getDefaultDataView: () => Promise | undefined>; +} diff --git a/x-pack/plugins/canvas/public/services/index.ts b/x-pack/plugins/canvas/public/services/index.ts index 2c01378851ee..a6ebc865fbd3 100644 --- a/x-pack/plugins/canvas/public/services/index.ts +++ b/x-pack/plugins/canvas/public/services/index.ts @@ -10,6 +10,7 @@ export * from './legacy'; import { PluginServices } from '@kbn/presentation-util-plugin/public'; import { CanvasCustomElementService } from './custom_element'; +import { CanvasDataViewsService } from './data_views'; import { CanvasEmbeddablesService } from './embeddables'; import { CanvasExpressionsService } from './expressions'; import { CanvasFiltersService } from './filters'; @@ -23,6 +24,7 @@ import { CanvasWorkpadService } from './workpad'; export interface CanvasPluginServices { customElement: CanvasCustomElementService; + dataViews: CanvasDataViewsService; embeddables: CanvasEmbeddablesService; expressions: CanvasExpressionsService; filters: CanvasFiltersService; @@ -39,6 +41,7 @@ export const pluginServices = new PluginServices(); export const useCustomElementService = () => (() => pluginServices.getHooks().customElement.useService())(); +export const useDataViewsService = () => (() => pluginServices.getHooks().dataViews.useService())(); export const useEmbeddablesService = () => (() => pluginServices.getHooks().embeddables.useService())(); export const useExpressionsService = () => diff --git a/x-pack/plugins/canvas/public/services/kibana/data_views.ts b/x-pack/plugins/canvas/public/services/kibana/data_views.ts new file mode 100644 index 000000000000..99e95c2e9dc4 --- /dev/null +++ b/x-pack/plugins/canvas/public/services/kibana/data_views.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DataView } from '@kbn/data-views-plugin/public'; +import { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import { ErrorStrings } from '../../../i18n'; +import { CanvasStartDeps } from '../../plugin'; +import { CanvasDataViewsService } from '../data_views'; +import { CanvasNotifyService } from '../notify'; + +const { esService: strings } = ErrorStrings; + +export type DataViewsServiceFactory = KibanaPluginServiceFactory< + CanvasDataViewsService, + CanvasStartDeps, + { + notify: CanvasNotifyService; + } +>; + +export const dataViewsServiceFactory: DataViewsServiceFactory = ({ startPlugins }, { notify }) => ({ + getDataViews: async () => { + try { + const dataViews = await startPlugins.data.dataViews.getIdsWithTitle(); + return dataViews.map(({ id, name, title }) => ({ id, name, title } as DataView)); + } catch (e) { + notify.error(e, { title: strings.getIndicesFetchErrorMessage() }); + } + + return []; + }, + getFields: async (dataViewTitle: string) => { + const dataView = await startPlugins.data.dataViews.create({ title: dataViewTitle }); + + return dataView.fields + .filter((field) => !field.name.startsWith('_')) + .map((field) => field.name); + }, + getDefaultDataView: async () => { + const dataView = await startPlugins.data.dataViews.getDefaultDataView(); + + return dataView + ? { id: dataView.id, name: dataView.name, title: dataView.getIndexPattern() } + : undefined; + }, +}); diff --git a/x-pack/plugins/canvas/public/services/kibana/index.ts b/x-pack/plugins/canvas/public/services/kibana/index.ts index cd5e70429640..0d4dd20a52fe 100644 --- a/x-pack/plugins/canvas/public/services/kibana/index.ts +++ b/x-pack/plugins/canvas/public/services/kibana/index.ts @@ -15,6 +15,7 @@ import { import { CanvasPluginServices } from '..'; import { CanvasStartDeps } from '../../plugin'; import { customElementServiceFactory } from './custom_element'; +import { dataViewsServiceFactory } from './data_views'; import { embeddablesServiceFactory } from './embeddables'; import { expressionsServiceFactory } from './expressions'; import { labsServiceFactory } from './labs'; @@ -27,6 +28,7 @@ import { workpadServiceFactory } from './workpad'; import { filtersServiceFactory } from './filters'; export { customElementServiceFactory } from './custom_element'; +export { dataViewsServiceFactory } from './data_views'; export { embeddablesServiceFactory } from './embeddables'; export { expressionsServiceFactory } from './expressions'; export { filtersServiceFactory } from './filters'; @@ -42,6 +44,7 @@ export const pluginServiceProviders: PluginServiceProviders< KibanaPluginServiceParams > = { customElement: new PluginServiceProvider(customElementServiceFactory), + dataViews: new PluginServiceProvider(dataViewsServiceFactory, ['notify']), embeddables: new PluginServiceProvider(embeddablesServiceFactory), expressions: new PluginServiceProvider(expressionsServiceFactory, ['filters', 'notify']), filters: new PluginServiceProvider(filtersServiceFactory), diff --git a/x-pack/plugins/canvas/public/services/stubs/data_views.ts b/x-pack/plugins/canvas/public/services/stubs/data_views.ts new file mode 100644 index 000000000000..1b1227dba4d3 --- /dev/null +++ b/x-pack/plugins/canvas/public/services/stubs/data_views.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import { CanvasDataViewsService } from '../data_views'; + +type DataViewsServiceFactory = PluginServiceFactory; + +export const dataViewsServiceFactory: DataViewsServiceFactory = () => ({ + getDataViews: () => + Promise.resolve([ + { id: 'dataview1', title: 'dataview1', name: 'Data view 1' }, + { id: 'dataview2', title: 'dataview2', name: 'Data view 2' }, + ]), + getFields: () => Promise.resolve(['field1', 'field2']), + getDefaultDataView: () => + Promise.resolve({ + id: 'defaultDataViewId', + title: 'defaultDataView', + name: 'Default data view', + }), +}); diff --git a/x-pack/plugins/canvas/public/services/stubs/index.ts b/x-pack/plugins/canvas/public/services/stubs/index.ts index bb47f0506ce8..7a6abd470c00 100644 --- a/x-pack/plugins/canvas/public/services/stubs/index.ts +++ b/x-pack/plugins/canvas/public/services/stubs/index.ts @@ -15,6 +15,7 @@ import { import { CanvasPluginServices } from '..'; import { customElementServiceFactory } from './custom_element'; +import { dataViewsServiceFactory } from './data_views'; import { embeddablesServiceFactory } from './embeddables'; import { expressionsServiceFactory } from './expressions'; import { labsServiceFactory } from './labs'; @@ -27,6 +28,7 @@ import { workpadServiceFactory } from './workpad'; import { filtersServiceFactory } from './filters'; export { customElementServiceFactory } from './custom_element'; +export { dataViewsServiceFactory } from './data_views'; export { expressionsServiceFactory } from './expressions'; export { filtersServiceFactory } from './filters'; export { labsServiceFactory } from './labs'; @@ -39,6 +41,7 @@ export { workpadServiceFactory } from './workpad'; export const pluginServiceProviders: PluginServiceProviders = { customElement: new PluginServiceProvider(customElementServiceFactory), + dataViews: new PluginServiceProvider(dataViewsServiceFactory), embeddables: new PluginServiceProvider(embeddablesServiceFactory), expressions: new PluginServiceProvider(expressionsServiceFactory, ['filters', 'notify']), filters: new PluginServiceProvider(filtersServiceFactory), diff --git a/x-pack/plugins/canvas/storybook/canvas_webpack.ts b/x-pack/plugins/canvas/storybook/canvas_webpack.ts index 502e175b91b0..946b6c5b78ce 100644 --- a/x-pack/plugins/canvas/storybook/canvas_webpack.ts +++ b/x-pack/plugins/canvas/storybook/canvas_webpack.ts @@ -56,10 +56,6 @@ export const canvasWebpack = { resolve: { alias: { 'src/plugins': resolve(KIBANA_ROOT, 'src/plugins'), - '../../lib/es_service': resolve( - KIBANA_ROOT, - 'x-pack/plugins/canvas/storybook/__mocks__/es_service.ts' - ), }, }, }; diff --git a/x-pack/plugins/canvas/storybook/storyshots.test.tsx b/x-pack/plugins/canvas/storybook/storyshots.skipped_test.tsx similarity index 96% rename from x-pack/plugins/canvas/storybook/storyshots.test.tsx rename to x-pack/plugins/canvas/storybook/storyshots.skipped_test.tsx index 39dde64283ba..108811312457 100644 --- a/x-pack/plugins/canvas/storybook/storyshots.test.tsx +++ b/x-pack/plugins/canvas/storybook/storyshots.skipped_test.tsx @@ -5,6 +5,9 @@ * 2.0. */ +// This file is skipped +// @storybook/addon-storyshots is not supported in Jest 27+ https://github.com/storybookjs/storybook/issues/15916 + import fs from 'fs'; import { ReactChildren, createElement } from 'react'; import path from 'path'; @@ -85,11 +88,6 @@ if (!fs.existsSync(cssDir)) { fs.mkdirSync(cssDir, { recursive: true }); } -// Mock index for datasource stories -jest.mock('../public/lib/es_service', () => ({ - getDefaultIndex: () => Promise.resolve('test index'), -})); - addSerializer(styleSheetSerializer); const emotionSerializer = createSerializer({ diff --git a/x-pack/plugins/cases/public/components/links/index.test.tsx b/x-pack/plugins/cases/public/components/links/index.test.tsx index e2da1a28261e..2fd05e45ade2 100644 --- a/x-pack/plugins/cases/public/components/links/index.test.tsx +++ b/x-pack/plugins/cases/public/components/links/index.test.tsx @@ -74,7 +74,7 @@ describe('Configuration button', () => { test('it shows the tooltip when hovering the button', () => { // Use fake timers so we don't have to wait for the EuiToolTip timeout - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); const msgTooltip = 'My message tooltip'; const titleTooltip = 'My title'; diff --git a/x-pack/plugins/cases/public/components/user_actions/use_user_actions_handler.test.tsx b/x-pack/plugins/cases/public/components/user_actions/use_user_actions_handler.test.tsx index 510be840d543..bb663dd89bd1 100644 --- a/x-pack/plugins/cases/public/components/user_actions/use_user_actions_handler.test.tsx +++ b/x-pack/plugins/cases/public/components/user_actions/use_user_actions_handler.test.tsx @@ -28,7 +28,7 @@ const openLensModal = jest.fn(); describe('useUserActionsHandler', () => { beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); jest.spyOn(global, 'setTimeout'); }); diff --git a/x-pack/plugins/cloud_integrations/cloud_experiments/common/metadata_service/metadata_service.test.ts b/x-pack/plugins/cloud_integrations/cloud_experiments/common/metadata_service/metadata_service.test.ts index 8764eb434213..6e79b5e4ebf0 100644 --- a/x-pack/plugins/cloud_integrations/cloud_experiments/common/metadata_service/metadata_service.test.ts +++ b/x-pack/plugins/cloud_integrations/cloud_experiments/common/metadata_service/metadata_service.test.ts @@ -20,7 +20,7 @@ jest.mock('rxjs', () => { }); describe('MetadataService', () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); let metadataService: MetadataService; diff --git a/x-pack/plugins/cloud_integrations/cloud_links/public/plugin.test.ts b/x-pack/plugins/cloud_integrations/cloud_links/public/plugin.test.ts index e6107874b6d5..f086f2095677 100644 --- a/x-pack/plugins/cloud_integrations/cloud_links/public/plugin.test.ts +++ b/x-pack/plugins/cloud_integrations/cloud_links/public/plugin.test.ts @@ -31,47 +31,74 @@ describe('Cloud Links Plugin - public', () => { plugin.stop(); }); - test('calls maybeAddCloudLinks when cloud and security are enabled and it is an authenticated page', () => { - const coreStart = coreMock.createStart(); - coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(false); - const cloud = { ...cloudMock.createStart(), isCloudEnabled: true }; - const security = securityMock.createStart(); - plugin.start(coreStart, { cloud, security }); - expect(maybeAddCloudLinksMock).toHaveBeenCalledTimes(1); - }); + describe('Onboarding Setup Guide link registration', () => { + test('registers the Onboarding Setup Guide link when cloud is enabled and it is an authenticated page', () => { + const coreStart = coreMock.createStart(); + coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(false); + const cloud = { ...cloudMock.createStart(), isCloudEnabled: true }; + plugin.start(coreStart, { cloud }); + expect(coreStart.chrome.registerGlobalHelpExtensionMenuLink).toHaveBeenCalledTimes(1); + }); - test('does not call maybeAddCloudLinks when security is disabled', () => { - const coreStart = coreMock.createStart(); - coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(false); - const cloud = { ...cloudMock.createStart(), isCloudEnabled: true }; - plugin.start(coreStart, { cloud }); - expect(maybeAddCloudLinksMock).toHaveBeenCalledTimes(0); - }); + test('does not register the Onboarding Setup Guide link when cloud is enabled but it is an unauthenticated page', () => { + const coreStart = coreMock.createStart(); + coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(true); + const cloud = { ...cloudMock.createStart(), isCloudEnabled: true }; + plugin.start(coreStart, { cloud }); + expect(coreStart.chrome.registerGlobalHelpExtensionMenuLink).not.toHaveBeenCalled(); + }); - test('does not call maybeAddCloudLinks when the page is anonymous', () => { - const coreStart = coreMock.createStart(); - coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(true); - const cloud = { ...cloudMock.createStart(), isCloudEnabled: true }; - const security = securityMock.createStart(); - plugin.start(coreStart, { cloud, security }); - expect(maybeAddCloudLinksMock).toHaveBeenCalledTimes(0); + test('does not register the Onboarding Setup Guide link when cloud is not enabled', () => { + const coreStart = coreMock.createStart(); + const cloud = { ...cloudMock.createStart(), isCloudEnabled: false }; + plugin.start(coreStart, { cloud }); + expect(coreStart.chrome.registerGlobalHelpExtensionMenuLink).not.toHaveBeenCalled(); + }); }); - test('does not call maybeAddCloudLinks when cloud is disabled', () => { - const coreStart = coreMock.createStart(); - coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(false); - const security = securityMock.createStart(); - plugin.start(coreStart, { security }); - expect(maybeAddCloudLinksMock).toHaveBeenCalledTimes(0); - }); + describe('maybeAddCloudLinks', () => { + test('calls maybeAddCloudLinks when cloud and security are enabled and it is an authenticated page', () => { + const coreStart = coreMock.createStart(); + coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(false); + const cloud = { ...cloudMock.createStart(), isCloudEnabled: true }; + const security = securityMock.createStart(); + plugin.start(coreStart, { cloud, security }); + expect(maybeAddCloudLinksMock).toHaveBeenCalledTimes(1); + }); + + test('does not call maybeAddCloudLinks when security is disabled', () => { + const coreStart = coreMock.createStart(); + coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(false); + const cloud = { ...cloudMock.createStart(), isCloudEnabled: true }; + plugin.start(coreStart, { cloud }); + expect(maybeAddCloudLinksMock).toHaveBeenCalledTimes(0); + }); + + test('does not call maybeAddCloudLinks when the page is anonymous', () => { + const coreStart = coreMock.createStart(); + coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(true); + const cloud = { ...cloudMock.createStart(), isCloudEnabled: true }; + const security = securityMock.createStart(); + plugin.start(coreStart, { cloud, security }); + expect(maybeAddCloudLinksMock).toHaveBeenCalledTimes(0); + }); + + test('does not call maybeAddCloudLinks when cloud is disabled', () => { + const coreStart = coreMock.createStart(); + coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(false); + const security = securityMock.createStart(); + plugin.start(coreStart, { security }); + expect(maybeAddCloudLinksMock).toHaveBeenCalledTimes(0); + }); - test('does not call maybeAddCloudLinks when isCloudEnabled is false', () => { - const coreStart = coreMock.createStart(); - coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(false); - const cloud = { ...cloudMock.createStart(), isCloudEnabled: false }; - const security = securityMock.createStart(); - plugin.start(coreStart, { cloud, security }); - expect(maybeAddCloudLinksMock).toHaveBeenCalledTimes(0); + test('does not call maybeAddCloudLinks when isCloudEnabled is false', () => { + const coreStart = coreMock.createStart(); + coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(false); + const cloud = { ...cloudMock.createStart(), isCloudEnabled: false }; + const security = securityMock.createStart(); + plugin.start(coreStart, { cloud, security }); + expect(maybeAddCloudLinksMock).toHaveBeenCalledTimes(0); + }); }); }); }); diff --git a/x-pack/plugins/cloud_integrations/cloud_links/public/plugin.ts b/x-pack/plugins/cloud_integrations/cloud_links/public/plugin.tsx similarity index 58% rename from x-pack/plugins/cloud_integrations/cloud_links/public/plugin.ts rename to x-pack/plugins/cloud_integrations/cloud_links/public/plugin.tsx index 7fbf7a8a6506..0d64ed13cde9 100755 --- a/x-pack/plugins/cloud_integrations/cloud_links/public/plugin.ts +++ b/x-pack/plugins/cloud_integrations/cloud_links/public/plugin.tsx @@ -5,6 +5,8 @@ * 2.0. */ +import React from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; import type { CoreStart, Plugin } from '@kbn/core/public'; import type { CloudSetup, CloudStart } from '@kbn/cloud-plugin/public'; import type { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/public'; @@ -26,12 +28,18 @@ export class CloudLinksPlugin public setup() {} public start(core: CoreStart, { cloud, security }: CloudLinksDepsStart) { - if ( - cloud?.isCloudEnabled && - security && - !core.http.anonymousPaths.isAnonymous(window.location.pathname) - ) { - maybeAddCloudLinks({ security, chrome: core.chrome, cloud }); + if (cloud?.isCloudEnabled && !core.http.anonymousPaths.isAnonymous(window.location.pathname)) { + core.chrome.registerGlobalHelpExtensionMenuLink({ + linkType: 'custom', + href: core.http.basePath.prepend('/app/home#/getting_started'), + content: , + 'data-test-subj': 'cloudOnboardingSetupGuideLink', + priority: 1000, // We want this link to be at the very top. + }); + + if (security) { + maybeAddCloudLinks({ security, chrome: core.chrome, cloud }); + } } } diff --git a/x-pack/plugins/cloud_security_posture/common/constants.ts b/x-pack/plugins/cloud_security_posture/common/constants.ts index 764cb49dae8d..a95680013648 100644 --- a/x-pack/plugins/cloud_security_posture/common/constants.ts +++ b/x-pack/plugins/cloud_security_posture/common/constants.ts @@ -49,3 +49,11 @@ export const CSP_RULE_TEMPLATE_SAVED_OBJECT_TYPE = 'csp-rule-template'; export const CLOUDBEAT_VANILLA = 'cloudbeat/cis_k8s'; // Integration input export const CLOUDBEAT_EKS = 'cloudbeat/cis_eks'; // Integration input + +export const LOCAL_STORAGE_PAGE_SIZE_LATEST_FINDINGS_KEY = 'cloudPosture:latestFindings:pageSize'; +export const LOCAL_STORAGE_PAGE_SIZE_RESOURCE_FINDINGS_KEY = + 'cloudPosture:resourceFindings:pageSize'; +export const LOCAL_STORAGE_PAGE_SIZE_FINDINGS_BY_RESOURCE_KEY = + 'cloudPosture:findingsByResource:pageSize'; +export const LOCAL_STORAGE_PAGE_SIZE_BENCHMARK_KEY = 'cloudPosture:benchmark:pageSize'; +export const LOCAL_STORAGE_PAGE_SIZE_RULES_KEY = 'cloudPosture:rules:pageSize'; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks.tsx b/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks.tsx index fce512d95315..ce615733e4b9 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks.tsx @@ -6,6 +6,7 @@ */ import React, { useState } from 'react'; +import useLocalStorage from 'react-use/lib/useLocalStorage'; import { EuiFieldSearch, EuiFieldSearchProps, @@ -30,6 +31,7 @@ import { } from './use_csp_benchmark_integrations'; import { extractErrorMessage } from '../../../common/utils/helpers'; import * as TEST_SUBJ from './test_subjects'; +import { LOCAL_STORAGE_PAGE_SIZE_BENCHMARK_KEY } from '../../../common/constants'; const SEARCH_DEBOUNCE_MS = 300; @@ -126,10 +128,14 @@ const BenchmarkSearchField = ({ }; export const Benchmarks = () => { + const [pageSize, setPageSize] = useLocalStorage( + LOCAL_STORAGE_PAGE_SIZE_BENCHMARK_KEY, + 10 + ); const [query, setQuery] = useState({ name: '', page: 1, - perPage: 10, + perPage: pageSize || 10, sortField: 'package_policy.name', sortOrder: 'asc', }); @@ -169,7 +175,7 @@ export const Benchmarks = () => { error={queryResult.error ? extractErrorMessage(queryResult.error) : undefined} loading={queryResult.isFetching} pageIndex={query.page} - pageSize={query.perPage} + pageSize={pageSize || query.perPage} sorting={{ // @ts-expect-error - EUI types currently do not support sorting by nested fields sort: { field: query.sortField, direction: query.sortOrder }, @@ -177,6 +183,7 @@ export const Benchmarks = () => { }} totalItemCount={totalItemCount} setQuery={({ page, sort }) => { + setPageSize(page.size); setQuery((current) => ({ ...current, page: page.index, diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_container.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_container.tsx index 3294ee8ec496..92b6ca6dcc68 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_container.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_container.tsx @@ -8,6 +8,7 @@ import React, { useMemo } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiBottomBar, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import useLocalStorage from 'react-use/lib/useLocalStorage'; import type { Evaluation } from '../../../../common/types'; import { CloudPosturePageTitle } from '../../../components/cloud_posture_page_title'; import type { FindingsBaseProps } from '../types'; @@ -31,6 +32,7 @@ import { FindingsGroupBySelector } from '../layout/findings_group_by_selector'; import { useUrlQuery } from '../../../common/hooks/use_url_query'; import { ErrorCallout } from '../layout/error_callout'; import { getLimitProperties } from '../utils/get_limit_properties'; +import { LOCAL_STORAGE_PAGE_SIZE_LATEST_FINDINGS_KEY } from '../../../../common/constants'; export const getDefaultQuery = ({ query, @@ -48,7 +50,10 @@ const MAX_ITEMS = 500; export const LatestFindingsContainer = ({ dataView }: FindingsBaseProps) => { const getPersistedDefaultQuery = usePersistedQuery(getDefaultQuery); const { urlQuery, setUrlQuery } = useUrlQuery(getPersistedDefaultQuery); - + const [pageSize, setPageSize] = useLocalStorage( + LOCAL_STORAGE_PAGE_SIZE_LATEST_FINDINGS_KEY, + urlQuery.pageSize + ); /** * Page URL query to ES query */ @@ -62,7 +67,10 @@ export const LatestFindingsContainer = ({ dataView }: FindingsBaseProps) => { * Page ES query result */ const findingsGroupByNone = useLatestFindings({ - ...getPaginationQuery({ pageIndex: urlQuery.pageIndex, pageSize: urlQuery.pageSize }), + ...getPaginationQuery({ + pageIndex: urlQuery.pageIndex, + pageSize: pageSize || urlQuery.pageSize, + }), query: baseEsQuery.query, sort: urlQuery.sort, enabled: !baseEsQuery.error, @@ -137,20 +145,21 @@ export const LatestFindingsContainer = ({ dataView }: FindingsBaseProps) => { loading={findingsGroupByNone.isFetching} items={findingsGroupByNone.data?.page || []} pagination={getPaginationTableParams({ - pageSize: urlQuery.pageSize, + pageSize: pageSize || urlQuery.pageSize, pageIndex: urlQuery.pageIndex, totalItemCount: limitedTotalItemCount, })} sorting={{ sort: { field: urlQuery.sort.field, direction: urlQuery.sort.direction }, }} - setTableOptions={({ page, sort }) => + setTableOptions={({ page, sort }) => { + setPageSize(page.size); setUrlQuery({ sort, pageIndex: page.index, pageSize: page.size, - }) - } + }); + }} onAddFilter={(field, value, negate) => setUrlQuery({ pageIndex: 0, diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/findings_by_resource_container.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/findings_by_resource_container.tsx index e9ce59fe9f0c..982fd12fbc06 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/findings_by_resource_container.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/findings_by_resource_container.tsx @@ -8,6 +8,7 @@ import React from 'react'; import { Route, Switch } from 'react-router-dom'; import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import useLocalStorage from 'react-use/lib/useLocalStorage'; import type { Evaluation } from '../../../../common/types'; import { CloudPosturePageTitle } from '../../../components/cloud_posture_page_title'; import { FindingsSearchBar } from '../layout/findings_search_bar'; @@ -30,6 +31,7 @@ import { findingsNavigation } from '../../../common/navigation/constants'; import { ResourceFindings } from './resource_findings/resource_findings_container'; import { ErrorCallout } from '../layout/error_callout'; import { FindingsDistributionBar } from '../layout/findings_distribution_bar'; +import { LOCAL_STORAGE_PAGE_SIZE_FINDINGS_BY_RESOURCE_KEY } from '../../../../common/constants'; const getDefaultQuery = ({ query, @@ -59,6 +61,10 @@ export const FindingsByResourceContainer = ({ dataView }: FindingsBaseProps) => const LatestFindingsByResource = ({ dataView }: FindingsBaseProps) => { const getPersistedDefaultQuery = usePersistedQuery(getDefaultQuery); const { urlQuery, setUrlQuery } = useUrlQuery(getPersistedDefaultQuery); + const [pageSize, setPageSize] = useLocalStorage( + LOCAL_STORAGE_PAGE_SIZE_FINDINGS_BY_RESOURCE_KEY, + urlQuery.pageSize + ); /** * Page URL query to ES query @@ -73,7 +79,10 @@ const LatestFindingsByResource = ({ dataView }: FindingsBaseProps) => { * Page ES query result */ const findingsGroupByResource = useFindingsByResource({ - ...getPaginationQuery(urlQuery), + ...getPaginationQuery({ + pageIndex: urlQuery.pageIndex, + pageSize: pageSize || urlQuery.pageSize, + }), sortDirection: urlQuery.sortDirection, query: baseEsQuery.query, enabled: !baseEsQuery.error, @@ -148,17 +157,18 @@ const LatestFindingsByResource = ({ dataView }: FindingsBaseProps) => { loading={findingsGroupByResource.isFetching} items={findingsGroupByResource.data?.page || []} pagination={getPaginationTableParams({ - pageSize: urlQuery.pageSize, + pageSize: pageSize || urlQuery.pageSize, pageIndex: urlQuery.pageIndex, totalItemCount: findingsGroupByResource.data?.total || 0, })} - setTableOptions={({ sort, page }) => + setTableOptions={({ sort, page }) => { + setPageSize(page.size); setUrlQuery({ sortDirection: sort?.direction, pageIndex: page.index, pageSize: page.size, - }) - } + }); + }} sorting={{ sort: { field: 'failed_findings', direction: urlQuery.sortDirection }, }} diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/resource_findings/resource_findings_container.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/resource_findings/resource_findings_container.tsx index 75997efaf629..e61415b7d0af 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/resource_findings/resource_findings_container.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/resource_findings/resource_findings_container.tsx @@ -15,6 +15,7 @@ import { Link, useParams } from 'react-router-dom'; import { FormattedMessage } from '@kbn/i18n-react'; import { generatePath } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; +import useLocalStorage from 'react-use/lib/useLocalStorage'; import { CspInlineDescriptionList } from '../../../../components/csp_inline_description_list'; import type { Evaluation } from '../../../../../common/types'; import { CspFinding } from '../../../../../common/schemas/csp_finding'; @@ -37,6 +38,7 @@ import { ResourceFindingsTable } from './resource_findings_table'; import { FindingsSearchBar } from '../../layout/findings_search_bar'; import { ErrorCallout } from '../../layout/error_callout'; import { FindingsDistributionBar } from '../../layout/findings_distribution_bar'; +import { LOCAL_STORAGE_PAGE_SIZE_RESOURCE_FINDINGS_KEY } from '../../../../../common/constants'; const getDefaultQuery = ({ query, @@ -90,6 +92,10 @@ export const ResourceFindings = ({ dataView }: FindingsBaseProps) => { const params = useParams<{ resourceId: string }>(); const getPersistedDefaultQuery = usePersistedQuery(getDefaultQuery); const { urlQuery, setUrlQuery } = useUrlQuery(getPersistedDefaultQuery); + const [pageSize, setPageSize] = useLocalStorage( + LOCAL_STORAGE_PAGE_SIZE_RESOURCE_FINDINGS_KEY, + urlQuery.pageSize + ); /** * Page URL query to ES query @@ -105,7 +111,7 @@ export const ResourceFindings = ({ dataView }: FindingsBaseProps) => { */ const resourceFindings = useResourceFindings({ ...getPaginationQuery({ - pageSize: urlQuery.pageSize, + pageSize: pageSize || urlQuery.pageSize, pageIndex: urlQuery.pageIndex, }), sort: urlQuery.sort, @@ -195,16 +201,17 @@ export const ResourceFindings = ({ dataView }: FindingsBaseProps) => { loading={resourceFindings.isFetching} items={resourceFindings.data?.page || []} pagination={getPaginationTableParams({ - pageSize: urlQuery.pageSize, + pageSize: pageSize || urlQuery.pageSize, pageIndex: urlQuery.pageIndex, totalItemCount: resourceFindings.data?.total || 0, })} sorting={{ sort: { field: urlQuery.sort.field, direction: urlQuery.sort.direction }, }} - setTableOptions={({ page, sort }) => - setUrlQuery({ pageIndex: page.index, pageSize: page.size, sort }) - } + setTableOptions={({ page, sort }) => { + setPageSize(page.size); + setUrlQuery({ pageIndex: page.index, pageSize: page.size, sort }); + }} onAddFilter={(field, value, negate) => setUrlQuery({ pageIndex: 0, diff --git a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_container.tsx b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_container.tsx index cc5f10a0bf06..84f39bb150c2 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_container.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_container.tsx @@ -7,6 +7,7 @@ import React, { useState, useMemo } from 'react'; import { EuiPanel, EuiSpacer } from '@elastic/eui'; import { useParams } from 'react-router-dom'; +import useLocalStorage from 'react-use/lib/useLocalStorage'; import { extractErrorMessage, createCspRuleSearchFilterByPackagePolicy, @@ -22,6 +23,7 @@ import { } from './use_csp_rules'; import * as TEST_SUBJECTS from './test_subjects'; import { RuleFlyout } from './rules_flyout'; +import { LOCAL_STORAGE_PAGE_SIZE_RULES_KEY } from '../../../common/constants'; interface RulesPageData { rules_page: RuleSavedObject[]; @@ -68,6 +70,7 @@ export type PageUrlParams = Record<'policyId' | 'packagePolicyId', string>; export const RulesContainer = () => { const params = useParams(); const [selectedRuleId, setSelectedRuleId] = useState(null); + const [pageSize, setPageSize] = useLocalStorage(LOCAL_STORAGE_PAGE_SIZE_RULES_KEY, 10); const [rulesQuery, setRulesQuery] = useState({ filter: createCspRuleSearchFilterByPackagePolicy({ packagePolicyId: params.packagePolicyId, @@ -75,7 +78,7 @@ export const RulesContainer = () => { }), search: '', page: 0, - perPage: 10, + perPage: pageSize || 10, }); const { data, status, error } = useFindCspRules({ @@ -105,11 +108,12 @@ export const RulesContainer = () => { total={rulesPageData.total} error={rulesPageData.error} loading={rulesPageData.loading} - perPage={rulesQuery.perPage} + perPage={pageSize || rulesQuery.perPage} page={rulesQuery.page} - setPagination={(paginationQuery) => - setRulesQuery((currentQuery) => ({ ...currentQuery, ...paginationQuery })) - } + setPagination={(paginationQuery) => { + setPageSize(paginationQuery.perPage); + setRulesQuery((currentQuery) => ({ ...currentQuery, ...paginationQuery })); + }} setSelectedRuleId={setSelectedRuleId} selectedRuleId={selectedRuleId} /> diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_indices_list.test.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_indices_list.test.js index 16d38d42d46b..50e984eec194 100644 --- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_indices_list.test.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_indices_list.test.js @@ -17,7 +17,7 @@ describe('', () => { let httpRequestsMockHelpers; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); ({ httpRequestsMockHelpers } = setupEnvironment()); }); diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/field_type_icon/field_type_icon.test.tsx b/x-pack/plugins/data_visualizer/public/application/common/components/field_type_icon/field_type_icon.test.tsx index 9962937fa80d..771b27ace1c5 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/field_type_icon/field_type_icon.test.tsx +++ b/x-pack/plugins/data_visualizer/public/application/common/components/field_type_icon/field_type_icon.test.tsx @@ -19,9 +19,10 @@ describe('FieldTypeIcon', () => { expect(typeIconComponent).toMatchSnapshot(); }); - test(`render with tooltip and test hovering`, () => { + // TODO: Broken with Jest 27 + test.skip(`render with tooltip and test hovering`, () => { // Use fake timers so we don't have to wait for the EuiToolTip timeout - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); const typeIconComponent = mount( diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/add_analytics_collections/add_analytics_collection_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/analytics/components/add_analytics_collections/add_analytics_collection_logic.test.ts index 7b281208f79e..a0610dc546fb 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/add_analytics_collections/add_analytics_collection_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/add_analytics_collections/add_analytics_collection_logic.test.ts @@ -97,7 +97,7 @@ describe('addAnalyticsCollectionLogic', () => { describe('listeners', () => { describe('onApiSuccess', () => { it('should flash a success toast and navigate to collection view', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); const { navigateToUrl } = mockKibanaValues; diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/delete_analytics_collection_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/delete_analytics_collection_logic.test.ts index 08ca206671de..baaea238fa6b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/delete_analytics_collection_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/delete_analytics_collection_logic.test.ts @@ -57,7 +57,7 @@ describe('deleteAnalyticsCollectionLogic', () => { it('calls makeRequest on deleteAnalyticsCollections', async () => { const collectionName = 'name'; - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); DeleteAnalyticsCollectionLogic.actions.makeRequest = jest.fn(); DeleteAnalyticsCollectionLogic.actions.deleteAnalyticsCollection(collectionName); jest.advanceTimersByTime(150); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs_logic.test.ts index 614f3506b4ef..17f3ab989162 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs_logic.test.ts @@ -119,7 +119,7 @@ describe('ApiLogsLogic', () => { describe('listeners', () => { describe('pollForApiLogs', () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); const setIntervalSpy = jest.spyOn(global, 'setInterval'); it('starts a poll that calls fetchApiLogs at set intervals', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_logic.test.ts index 59ec64c69d5a..9064d1185a3d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_logic.test.ts @@ -73,7 +73,7 @@ describe('CrawlerLogic', () => { beforeEach(() => { jest.clearAllMocks(); - jest.useFakeTimers(); // this should be run before every test to reset these mocks + jest.useFakeTimers('legacy'); // this should be run before every test to reset these mocks mount(); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.test.ts index 4e25477d65db..4ea8dd55ffb4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.test.ts @@ -380,7 +380,7 @@ describe('CurationLogic', () => { }); describe('updateCuration', () => { - beforeAll(() => jest.useFakeTimers()); + beforeAll(() => jest.useFakeTimers('legacy')); afterAll(() => jest.useRealTimers()); it('should make a PUT API call with queries and promoted/hidden IDs to update', async () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts index d82b4f5d4b05..1bb1a815d236 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts @@ -255,7 +255,7 @@ describe('EngineLogic', () => { }); describe('pollEmptyEngine', () => { - beforeEach(() => jest.useFakeTimers()); + beforeEach(() => jest.useFakeTimers('legacy')); afterEach(() => jest.clearAllTimers()); afterAll(() => jest.useRealTimers()); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/log_retention_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/log_retention_logic.test.ts index fba8b118d4d2..a4ea5595bba3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/log_retention_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/log_retention_logic.test.ts @@ -329,7 +329,7 @@ describe('LogRetentionLogic', () => { }); describe('fetchLogRetention', () => { - beforeAll(() => jest.useFakeTimers()); + beforeAll(() => jest.useFakeTimers('legacy')); afterAll(() => jest.useRealTimers()); describe('isLogRetentionUpdating', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts index bf0bad4f1088..f413fefb7f89 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts @@ -341,7 +341,7 @@ describe('RelevanceTuningLogic', () => { describe('getSearchResults', () => { beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/sample_response/sample_response_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/sample_response/sample_response_logic.test.ts index ee20fab3da66..6712a32c94bc 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/sample_response/sample_response_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/sample_response/sample_response_logic.test.ts @@ -83,7 +83,7 @@ describe('SampleResponseLogic', () => { describe('listeners', () => { describe('getSearchResults', () => { - beforeAll(() => jest.useFakeTimers()); + beforeAll(() => jest.useFakeTimers('legacy')); afterAll(() => jest.useRealTimers()); it('makes a search API request and calls getSearchResultsSuccess with the first result of the response', async () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search/search_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search/search_logic.test.ts index a56d2f7d5c94..ab8426dce363 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search/search_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search/search_logic.test.ts @@ -78,7 +78,7 @@ describe('SearchLogic', () => { describe('listeners', () => { describe('search', () => { - beforeAll(() => jest.useFakeTimers()); + beforeAll(() => jest.useFakeTimers('legacy')); afterAll(() => jest.useRealTimers()); it('should make a GET API call with a search query', async () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/method_connector/add_connector_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/method_connector/add_connector_logic.test.ts index e4b817f3d597..7487a7790f79 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/method_connector/add_connector_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/method_connector/add_connector_logic.test.ts @@ -53,7 +53,7 @@ describe('AddConnectorLogic', () => { describe('apiSuccess', () => { it('navigates to correct spot and flashes success toast', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); AddConnectorApiLogic.actions.apiSuccess({ indexName: 'success' } as any); await nextTick(); jest.advanceTimersByTime(1001); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/new_search_index_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/new_search_index_logic.test.ts index f5ae61dc75dd..8f22ba28b8dc 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/new_search_index_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/new_search_index_logic.test.ts @@ -86,7 +86,7 @@ describe('NewSearchIndexLogic', () => { }); }); it('calls makeRequest on whether API exists with a 150ms debounce', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); NewSearchIndexLogic.actions.makeRequest = jest.fn(); NewSearchIndexLogic.actions.setRawName('indexname'); await nextTick(); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/domain_management/add_domain/add_domain_form.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/domain_management/add_domain/add_domain_form.tsx index b0722ca50d2c..e3d38256e964 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/domain_management/add_domain/add_domain_form.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/domain_management/add_domain/add_domain_form.tsx @@ -19,6 +19,7 @@ import { EuiFieldText, EuiSpacer, EuiText, + EuiFormControlLayout, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -56,13 +57,14 @@ export const AddDomainForm: React.FC = () => { > - setAddDomainFormInputValue(e.target.value)} - fullWidth - /> + setAddDomainFormInputValue('') }}> + setAddDomainFormInputValue(e.target.value)} + fullWidth + /> + diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/domain_management/add_domain/validation_step_panel.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/domain_management/add_domain/validation_step_panel.tsx index 07e86b5f92d9..fb33f620f82a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/domain_management/add_domain/validation_step_panel.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/domain_management/add_domain/validation_step_panel.tsx @@ -10,10 +10,12 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem, + EuiLink, EuiMarkdownFormat, EuiPanel, EuiSpacer, EuiTitle, + getDefaultEuiMarkdownProcessingPlugins, } from '@elastic/eui'; import { CrawlerDomainValidationStep } from '../../../../../api/crawler/types'; @@ -27,6 +29,9 @@ interface ValidationStepPanelProps { step: CrawlerDomainValidationStep; } +const processingPlugins = getDefaultEuiMarkdownProcessingPlugins(); +processingPlugins[1][1].components.a = (props) => ; + export const ValidationStepPanel: React.FC = ({ step, label, @@ -49,7 +54,11 @@ export const ValidationStepPanel: React.FC = ({ {showErrorMessage && ( <> - + {step.message || ''} {action && ( diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/domain_management/domains_table.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/domain_management/domains_table.tsx index 44a22f258b50..c86fc2b3a20d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/domain_management/domains_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/domain_management/domains_table.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { useActions, useValues } from 'kea'; -import { EuiBasicTableColumn, EuiBasicTable } from '@elastic/eui'; +import { EuiBasicTableColumn, EuiBasicTable, EuiButtonIcon, EuiCopy } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -39,18 +39,23 @@ export const DomainsTable: React.FC = () => { { field: 'url', name: i18n.translate('xpack.enterpriseSearch.crawler.domainsTable.column.domainURL', { - defaultMessage: 'Domain URL', + defaultMessage: 'Domain', }), render: (_, domain: CrawlerDomain) => ( - - {domain.url} - + <> + + {(copy) => } + + + {domain.url} + + ), }, { diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/documents_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/documents_logic.test.ts index b927e6a5738e..1bd1e1a405ee 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/documents_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/documents_logic.test.ts @@ -85,7 +85,7 @@ describe('DocumentsLogic', () => { describe('listeners', () => { describe('setSearchQuery', () => { it('make documents apiRequest request after 250ms debounce', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); DocumentsLogic.actions.makeRequest = jest.fn(); DocumentsLogic.actions.setSearchQuery('test'); await nextTick(); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/index_view_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/index_view_logic.test.ts index e09b66051e4a..1b0a11d17f54 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/index_view_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/index_view_logic.test.ts @@ -199,7 +199,7 @@ describe('IndexViewLogic', () => { describe('createNewFetchIndexTimeout', () => { it('should trigger fetchIndex after timeout', async () => { IndexViewLogic.actions.fetchIndex = jest.fn(); - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); IndexViewLogic.actions.createNewFetchIndexTimeout(1); expect(IndexViewLogic.actions.fetchIndex).not.toHaveBeenCalled(); jest.advanceTimersByTime(2); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/indices_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/indices_logic.test.ts index ff761b17e738..d9b6a6248ce3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/indices_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/indices_logic.test.ts @@ -315,7 +315,7 @@ describe('IndicesLogic', () => { expect(IndicesLogic.actions.closeDeleteModal).toHaveBeenCalled(); }); it('calls makeRequest on fetchIndices', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); IndicesLogic.actions.makeRequest = jest.fn(); IndicesLogic.actions.fetchIndices({ meta: DEFAULT_META, returnHiddenIndices: false }); jest.advanceTimersByTime(150); @@ -326,7 +326,7 @@ describe('IndicesLogic', () => { }); }); it('calls makeRequest once on two fetchIndices calls within 150ms', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); IndicesLogic.actions.makeRequest = jest.fn(); IndicesLogic.actions.fetchIndices({ meta: DEFAULT_META, returnHiddenIndices: false }); jest.advanceTimersByTime(130); @@ -341,7 +341,7 @@ describe('IndicesLogic', () => { expect(IndicesLogic.actions.makeRequest).toHaveBeenCalledTimes(1); }); it('calls makeRequest twice on two fetchIndices calls outside 150ms', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); IndicesLogic.actions.makeRequest = jest.fn(); IndicesLogic.actions.fetchIndices({ meta: DEFAULT_META, returnHiddenIndices: false }); jest.advanceTimersByTime(150); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_logic.test.ts index 75c9010ae9be..a610cdc887d9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_logic.test.ts @@ -80,7 +80,7 @@ describe('SourcesLogic', () => { }; beforeEach(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); jest.clearAllMocks(); mount(); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups_logic.test.ts index bc82c9587167..a167c5a1c11c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups_logic.test.ts @@ -239,7 +239,7 @@ describe('GroupsLogic', () => { }; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.test.ts b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.test.ts index c9ccd1841cf7..df7fee347161 100644 --- a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.test.ts @@ -223,7 +223,7 @@ describe('callEnterpriseSearchConfigAPI', () => { }); it('handles timeouts', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); // Warning callEnterpriseSearchConfigAPI(mockDependencies); diff --git a/x-pack/plugins/enterprise_search/server/lib/ml_inference_pipeline/get_inference_errors.test.ts b/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/get_ml_inference_errors.test.ts similarity index 97% rename from x-pack/plugins/enterprise_search/server/lib/ml_inference_pipeline/get_inference_errors.test.ts rename to x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/get_ml_inference_errors.test.ts index d9939afedaa5..da72136402ac 100644 --- a/x-pack/plugins/enterprise_search/server/lib/ml_inference_pipeline/get_inference_errors.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/get_ml_inference_errors.test.ts @@ -7,7 +7,7 @@ import { ElasticsearchClient } from '@kbn/core/server'; -import { getMlInferenceErrors } from './get_inference_errors'; +import { getMlInferenceErrors } from './get_ml_inference_errors'; describe('getMlInferenceErrors', () => { const indexName = 'my-index'; diff --git a/x-pack/plugins/enterprise_search/server/lib/ml_inference_pipeline/get_inference_errors.ts b/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/get_ml_inference_errors.ts similarity index 96% rename from x-pack/plugins/enterprise_search/server/lib/ml_inference_pipeline/get_inference_errors.ts rename to x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/get_ml_inference_errors.ts index 09adc656d576..1556b478de21 100644 --- a/x-pack/plugins/enterprise_search/server/lib/ml_inference_pipeline/get_inference_errors.ts +++ b/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/get_ml_inference_errors.ts @@ -12,7 +12,7 @@ import { import { ElasticsearchClient } from '@kbn/core/server'; -import { MlInferenceError } from '../../../common/types/pipelines'; +import { MlInferenceError } from '../../../../../common/types/pipelines'; export interface ErrorAggregationBucket extends AggregationsStringRareTermsBucketKeys { max_error_timestamp: { diff --git a/x-pack/plugins/enterprise_search/server/lib/indices/fetch_ml_inference_pipeline_history.test.ts b/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/get_ml_inference_pipeline_history.test.ts similarity index 95% rename from x-pack/plugins/enterprise_search/server/lib/indices/fetch_ml_inference_pipeline_history.test.ts rename to x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/get_ml_inference_pipeline_history.test.ts index 61d11b50ae48..461bd0e88261 100644 --- a/x-pack/plugins/enterprise_search/server/lib/indices/fetch_ml_inference_pipeline_history.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/get_ml_inference_pipeline_history.test.ts @@ -11,9 +11,9 @@ import { } from '@elastic/elasticsearch/lib/api/types'; import { ElasticsearchClient } from '@kbn/core/server'; -import { MlInferenceHistoryResponse } from '../../../common/types/pipelines'; +import { MlInferenceHistoryResponse } from '../../../../../common/types/pipelines'; -import { fetchMlInferencePipelineHistory } from './fetch_ml_inference_pipeline_history'; +import { fetchMlInferencePipelineHistory } from './get_ml_inference_pipeline_history'; const DEFAULT_RESPONSE: SearchResponse = { _shards: { diff --git a/x-pack/plugins/enterprise_search/server/lib/indices/fetch_ml_inference_pipeline_history.ts b/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/get_ml_inference_pipeline_history.ts similarity index 94% rename from x-pack/plugins/enterprise_search/server/lib/indices/fetch_ml_inference_pipeline_history.ts rename to x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/get_ml_inference_pipeline_history.ts index 70cbe687590c..f6a01ec2b3e8 100644 --- a/x-pack/plugins/enterprise_search/server/lib/indices/fetch_ml_inference_pipeline_history.ts +++ b/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/get_ml_inference_pipeline_history.ts @@ -12,7 +12,7 @@ import { import { ElasticsearchClient } from '@kbn/core/server'; -import { MlInferenceHistoryResponse } from '../../../common/types/pipelines'; +import { MlInferenceHistoryResponse } from '../../../../../common/types/pipelines'; export const fetchMlInferencePipelineHistory = async ( client: ElasticsearchClient, diff --git a/x-pack/plugins/enterprise_search/server/lib/pipelines/ml_inference/pipeline_processors/delete_ml_inference_pipeline.test.ts b/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/delete_ml_inference_pipeline.test.ts similarity index 100% rename from x-pack/plugins/enterprise_search/server/lib/pipelines/ml_inference/pipeline_processors/delete_ml_inference_pipeline.test.ts rename to x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/delete_ml_inference_pipeline.test.ts diff --git a/x-pack/plugins/enterprise_search/server/lib/pipelines/ml_inference/pipeline_processors/delete_ml_inference_pipeline.ts b/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/delete_ml_inference_pipeline.ts similarity index 97% rename from x-pack/plugins/enterprise_search/server/lib/pipelines/ml_inference/pipeline_processors/delete_ml_inference_pipeline.ts rename to x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/delete_ml_inference_pipeline.ts index 19654d0b2e93..6cb74d75dd6c 100644 --- a/x-pack/plugins/enterprise_search/server/lib/pipelines/ml_inference/pipeline_processors/delete_ml_inference_pipeline.ts +++ b/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/delete_ml_inference_pipeline.ts @@ -7,7 +7,7 @@ import { ElasticsearchClient } from '@kbn/core/server'; -import { DeleteMlInferencePipelineResponse } from '../../../../../common/types/pipelines'; +import { DeleteMlInferencePipelineResponse } from '../../../../../../common/types/pipelines'; import { detachMlInferencePipeline } from './detach_ml_inference_pipeline'; diff --git a/x-pack/plugins/enterprise_search/server/lib/pipelines/ml_inference/pipeline_processors/detach_ml_inference_pipeline.test.ts b/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/detach_ml_inference_pipeline.test.ts similarity index 100% rename from x-pack/plugins/enterprise_search/server/lib/pipelines/ml_inference/pipeline_processors/detach_ml_inference_pipeline.test.ts rename to x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/detach_ml_inference_pipeline.test.ts diff --git a/x-pack/plugins/enterprise_search/server/lib/pipelines/ml_inference/pipeline_processors/detach_ml_inference_pipeline.ts b/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/detach_ml_inference_pipeline.ts similarity index 96% rename from x-pack/plugins/enterprise_search/server/lib/pipelines/ml_inference/pipeline_processors/detach_ml_inference_pipeline.ts rename to x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/detach_ml_inference_pipeline.ts index 02d6c328a8e4..28f099fad6fa 100644 --- a/x-pack/plugins/enterprise_search/server/lib/pipelines/ml_inference/pipeline_processors/detach_ml_inference_pipeline.ts +++ b/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/detach_ml_inference_pipeline.ts @@ -8,9 +8,9 @@ import { IngestPutPipelineRequest } from '@elastic/elasticsearch/lib/api/types'; import { ElasticsearchClient } from '@kbn/core/server'; -import { DeleteMlInferencePipelineResponse } from '../../../../../common/types/pipelines'; +import { DeleteMlInferencePipelineResponse } from '../../../../../../common/types/pipelines'; -import { getInferencePipelineNameFromIndexName } from '../../../../utils/ml_inference_pipeline_utils'; +import { getInferencePipelineNameFromIndexName } from '../../../../../utils/ml_inference_pipeline_utils'; export const detachMlInferencePipeline = async ( indexName: string, diff --git a/x-pack/plugins/enterprise_search/server/lib/indices/fetch_ml_inference_pipeline_processors.test.ts b/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/get_ml_inference_pipeline_processors.test.ts similarity index 99% rename from x-pack/plugins/enterprise_search/server/lib/indices/fetch_ml_inference_pipeline_processors.test.ts rename to x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/get_ml_inference_pipeline_processors.test.ts index 941ef42aaa44..1a20551ab791 100644 --- a/x-pack/plugins/enterprise_search/server/lib/indices/fetch_ml_inference_pipeline_processors.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/get_ml_inference_pipeline_processors.test.ts @@ -9,7 +9,7 @@ import { errors } from '@elastic/elasticsearch'; import { ElasticsearchClient } from '@kbn/core/server'; import { MlTrainedModels } from '@kbn/ml-plugin/server'; -import { InferencePipeline, TrainedModelState } from '../../../common/types/pipelines'; +import { InferencePipeline, TrainedModelState } from '../../../../../../common/types/pipelines'; import { fetchAndAddTrainedModelData, @@ -19,7 +19,7 @@ import { fetchMlInferencePipelineProcessors, fetchPipelineProcessorInferenceData, InferencePipelineData, -} from './fetch_ml_inference_pipeline_processors'; +} from './get_ml_inference_pipeline_processors'; const mockGetPipeline = { 'my-index@ml-inference': { diff --git a/x-pack/plugins/enterprise_search/server/lib/indices/fetch_ml_inference_pipeline_processors.ts b/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/get_ml_inference_pipeline_processors.ts similarity index 96% rename from x-pack/plugins/enterprise_search/server/lib/indices/fetch_ml_inference_pipeline_processors.ts rename to x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/get_ml_inference_pipeline_processors.ts index 1eabe28eb78b..4a2ba80ca43a 100644 --- a/x-pack/plugins/enterprise_search/server/lib/indices/fetch_ml_inference_pipeline_processors.ts +++ b/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/get_ml_inference_pipeline_processors.ts @@ -9,9 +9,9 @@ import { IngestGetPipelineResponse } from '@elastic/elasticsearch/lib/api/types' import { ElasticsearchClient } from '@kbn/core/server'; import { MlTrainedModels } from '@kbn/ml-plugin/server'; -import { getMlModelTypesForModelConfig } from '../../../common/ml_inference_pipeline'; -import { InferencePipeline, TrainedModelState } from '../../../common/types/pipelines'; -import { getInferencePipelineNameFromIndexName } from '../../utils/ml_inference_pipeline_utils'; +import { getMlModelTypesForModelConfig } from '../../../../../../common/ml_inference_pipeline'; +import { InferencePipeline, TrainedModelState } from '../../../../../../common/types/pipelines'; +import { getInferencePipelineNameFromIndexName } from '../../../../../utils/ml_inference_pipeline_utils'; export type InferencePipelineData = InferencePipeline & { trainedModelName: string; @@ -202,7 +202,7 @@ export const fetchMlInferencePipelineProcessors = async ( indexName: string ): Promise => { if (!trainedModelsProvider) { - return Promise.reject(new Error('Machine Learning is not enabled')); + throw new Error('Machine Learning is not enabled'); } const allMlPipelines = await fetchMlInferencePipelines(client); diff --git a/x-pack/plugins/enterprise_search/server/lib/ml_inference_pipeline/get_inference_pipelines.test.ts b/x-pack/plugins/enterprise_search/server/lib/pipelines/ml_inference/get_ml_inference_pipelines.test.ts similarity index 93% rename from x-pack/plugins/enterprise_search/server/lib/ml_inference_pipeline/get_inference_pipelines.test.ts rename to x-pack/plugins/enterprise_search/server/lib/pipelines/ml_inference/get_ml_inference_pipelines.test.ts index 45953166667a..0765fafcc9d1 100644 --- a/x-pack/plugins/enterprise_search/server/lib/ml_inference_pipeline/get_inference_pipelines.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/pipelines/ml_inference/get_ml_inference_pipelines.test.ts @@ -9,11 +9,7 @@ import { IngestProcessorContainer } from '@elastic/elasticsearch/lib/api/types'; import { ElasticsearchClient } from '@kbn/core/server'; import { MlTrainedModels } from '@kbn/ml-plugin/server'; -import { getMlInferencePipelines } from './get_inference_pipelines'; - -jest.mock('../indices/fetch_ml_inference_pipeline_processors', () => ({ - getMlModelConfigsForModelIds: jest.fn(), -})); +import { getMlInferencePipelines } from './get_ml_inference_pipelines'; describe('getMlInferencePipelines', () => { const mockClient = { @@ -109,10 +105,10 @@ describe('getMlInferencePipelines', () => { expect( (actualPipelines.pipeline1.processors as IngestProcessorContainer[])[1].inference?.model_id - ).toBeDefined(); + ).toEqual('model1'); expect( (actualPipelines.pipeline2.processors as IngestProcessorContainer[])[1].inference?.model_id - ).toBeDefined(); + ).toEqual('model2'); expect( (actualPipelines.pipeline3.processors as IngestProcessorContainer[])[1].inference?.model_id ).toEqual(''); // Redacted model ID @@ -121,7 +117,7 @@ describe('getMlInferencePipelines', () => { ).toEqual(''); expect( (actualPipelines.pipeline4.processors as IngestProcessorContainer[])[2].inference?.model_id - ).toBeDefined(); + ).toEqual('model2'); expect( (actualPipelines.pipeline4.processors as IngestProcessorContainer[])[3].inference?.model_id ).toEqual(''); diff --git a/x-pack/plugins/enterprise_search/server/lib/ml_inference_pipeline/get_inference_pipelines.ts b/x-pack/plugins/enterprise_search/server/lib/pipelines/ml_inference/get_ml_inference_pipelines.ts similarity index 83% rename from x-pack/plugins/enterprise_search/server/lib/ml_inference_pipeline/get_inference_pipelines.ts rename to x-pack/plugins/enterprise_search/server/lib/pipelines/ml_inference/get_ml_inference_pipelines.ts index 4bdf7e95d4a0..2dfc6951b222 100644 --- a/x-pack/plugins/enterprise_search/server/lib/ml_inference_pipeline/get_inference_pipelines.ts +++ b/x-pack/plugins/enterprise_search/server/lib/pipelines/ml_inference/get_ml_inference_pipelines.ts @@ -20,7 +20,7 @@ export const getMlInferencePipelines = async ( trainedModelsProvider: MlTrainedModels | undefined ): Promise> => { if (!trainedModelsProvider) { - return Promise.reject(new Error('Machine Learning is not enabled')); + throw new Error('Machine Learning is not enabled'); } // Fetch all ML inference pipelines and trained models that are accessible in the current @@ -37,17 +37,22 @@ export const getMlInferencePipelines = async ( // Process pipelines: check if the model_id is one of the redacted ones, if so, redact it in the // result as well - const inferencePipelinesResult: Record = {}; - Object.entries(fetchedInferencePipelines).forEach(([name, inferencePipeline]) => { - inferencePipelinesResult[name] = { - ...inferencePipeline, - processors: inferencePipeline.processors?.map((processor) => - redactModelIdIfInaccessible(processor, accessibleModelIds) - ), - }; - }); + const inferencePipelinesResult: Record = Object.entries( + fetchedInferencePipelines + ).reduce( + (currentPipelines, [name, inferencePipeline]) => ({ + ...currentPipelines, + [name]: { + ...inferencePipeline, + processors: inferencePipeline.processors?.map((processor) => + redactModelIdIfInaccessible(processor, accessibleModelIds) + ), + }, + }), + {} + ); - return Promise.resolve(inferencePipelinesResult); + return inferencePipelinesResult; }; /** diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.test.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.test.ts index 1ad901330caf..52039cc48173 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.test.ts @@ -14,12 +14,15 @@ import { SharedServices } from '@kbn/ml-plugin/server/shared_services'; import { ErrorCode } from '../../../common/types/error_codes'; -jest.mock('../../lib/indices/fetch_ml_inference_pipeline_history', () => ({ +jest.mock('../../lib/indices/pipelines/ml_inference/get_ml_inference_pipeline_history', () => ({ fetchMlInferencePipelineHistory: jest.fn(), })); -jest.mock('../../lib/indices/fetch_ml_inference_pipeline_processors', () => ({ - fetchMlInferencePipelineProcessors: jest.fn(), -})); +jest.mock( + '../../lib/indices/pipelines/ml_inference/pipeline_processors/get_ml_inference_pipeline_processors', + () => ({ + fetchMlInferencePipelineProcessors: jest.fn(), + }) +); jest.mock( '../../lib/indices/pipelines/ml_inference/pipeline_processors/create_ml_inference_pipeline', () => ({ @@ -33,13 +36,13 @@ jest.mock( }) ); jest.mock( - '../../lib/pipelines/ml_inference/pipeline_processors/delete_ml_inference_pipeline', + '../../lib/indices/pipelines/ml_inference/pipeline_processors/delete_ml_inference_pipeline', () => ({ deleteMlInferencePipeline: jest.fn(), }) ); jest.mock( - '../../lib/pipelines/ml_inference/pipeline_processors/detach_ml_inference_pipeline', + '../../lib/indices/pipelines/ml_inference/pipeline_processors/detach_ml_inference_pipeline', () => ({ detachMlInferencePipeline: jest.fn(), }) @@ -47,22 +50,22 @@ jest.mock( jest.mock('../../lib/indices/exists_index', () => ({ indexOrAliasExists: jest.fn(), })); -jest.mock('../../lib/ml_inference_pipeline/get_inference_errors', () => ({ +jest.mock('../../lib/indices/pipelines/ml_inference/get_ml_inference_errors', () => ({ getMlInferenceErrors: jest.fn(), })); -jest.mock('../../lib/ml_inference_pipeline/get_inference_pipelines', () => ({ +jest.mock('../../lib/pipelines/ml_inference/get_ml_inference_pipelines', () => ({ getMlInferencePipelines: jest.fn(), })); import { indexOrAliasExists } from '../../lib/indices/exists_index'; -import { fetchMlInferencePipelineHistory } from '../../lib/indices/fetch_ml_inference_pipeline_history'; -import { fetchMlInferencePipelineProcessors } from '../../lib/indices/fetch_ml_inference_pipeline_processors'; +import { getMlInferenceErrors } from '../../lib/indices/pipelines/ml_inference/get_ml_inference_errors'; +import { fetchMlInferencePipelineHistory } from '../../lib/indices/pipelines/ml_inference/get_ml_inference_pipeline_history'; import { attachMlInferencePipeline } from '../../lib/indices/pipelines/ml_inference/pipeline_processors/attach_ml_pipeline'; import { createAndReferenceMlInferencePipeline } from '../../lib/indices/pipelines/ml_inference/pipeline_processors/create_ml_inference_pipeline'; -import { getMlInferenceErrors } from '../../lib/ml_inference_pipeline/get_inference_errors'; -import { getMlInferencePipelines } from '../../lib/ml_inference_pipeline/get_inference_pipelines'; -import { deleteMlInferencePipeline } from '../../lib/pipelines/ml_inference/pipeline_processors/delete_ml_inference_pipeline'; -import { detachMlInferencePipeline } from '../../lib/pipelines/ml_inference/pipeline_processors/detach_ml_inference_pipeline'; +import { deleteMlInferencePipeline } from '../../lib/indices/pipelines/ml_inference/pipeline_processors/delete_ml_inference_pipeline'; +import { detachMlInferencePipeline } from '../../lib/indices/pipelines/ml_inference/pipeline_processors/detach_ml_inference_pipeline'; +import { fetchMlInferencePipelineProcessors } from '../../lib/indices/pipelines/ml_inference/pipeline_processors/get_ml_inference_pipeline_processors'; +import { getMlInferencePipelines } from '../../lib/pipelines/ml_inference/get_ml_inference_pipelines'; import { ElasticsearchResponseError } from '../../utils/identify_exceptions'; import { registerIndexRoutes } from './indices'; diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts index ee497ba671fa..02a7dd528f87 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts @@ -31,18 +31,18 @@ import { createIndex } from '../../lib/indices/create_index'; import { indexOrAliasExists } from '../../lib/indices/exists_index'; import { fetchIndex } from '../../lib/indices/fetch_index'; import { fetchIndices } from '../../lib/indices/fetch_indices'; -import { fetchMlInferencePipelineHistory } from '../../lib/indices/fetch_ml_inference_pipeline_history'; -import { fetchMlInferencePipelineProcessors } from '../../lib/indices/fetch_ml_inference_pipeline_processors'; import { generateApiKey } from '../../lib/indices/generate_api_key'; +import { getMlInferenceErrors } from '../../lib/indices/pipelines/ml_inference/get_ml_inference_errors'; +import { fetchMlInferencePipelineHistory } from '../../lib/indices/pipelines/ml_inference/get_ml_inference_pipeline_history'; import { attachMlInferencePipeline } from '../../lib/indices/pipelines/ml_inference/pipeline_processors/attach_ml_pipeline'; import { createAndReferenceMlInferencePipeline } from '../../lib/indices/pipelines/ml_inference/pipeline_processors/create_ml_inference_pipeline'; -import { getMlInferenceErrors } from '../../lib/ml_inference_pipeline/get_inference_errors'; -import { getMlInferencePipelines } from '../../lib/ml_inference_pipeline/get_inference_pipelines'; +import { deleteMlInferencePipeline } from '../../lib/indices/pipelines/ml_inference/pipeline_processors/delete_ml_inference_pipeline'; +import { detachMlInferencePipeline } from '../../lib/indices/pipelines/ml_inference/pipeline_processors/detach_ml_inference_pipeline'; +import { fetchMlInferencePipelineProcessors } from '../../lib/indices/pipelines/ml_inference/pipeline_processors/get_ml_inference_pipeline_processors'; import { createIndexPipelineDefinitions } from '../../lib/pipelines/create_pipeline_definitions'; import { getCustomPipelines } from '../../lib/pipelines/get_custom_pipelines'; import { getPipeline } from '../../lib/pipelines/get_pipeline'; -import { deleteMlInferencePipeline } from '../../lib/pipelines/ml_inference/pipeline_processors/delete_ml_inference_pipeline'; -import { detachMlInferencePipeline } from '../../lib/pipelines/ml_inference/pipeline_processors/detach_ml_inference_pipeline'; +import { getMlInferencePipelines } from '../../lib/pipelines/ml_inference/get_ml_inference_pipelines'; import { RouteDependencies } from '../../plugin'; import { createError } from '../../utils/create_error'; import { elasticsearchErrorHandler } from '../../utils/elasticsearch_error_handler'; diff --git a/x-pack/plugins/features/common/sub_feature.ts b/x-pack/plugins/features/common/sub_feature.ts index 2795e50bce47..58142fd88c0c 100644 --- a/x-pack/plugins/features/common/sub_feature.ts +++ b/x-pack/plugins/features/common/sub_feature.ts @@ -16,6 +16,17 @@ export interface SubFeatureConfig { /** Display name for this sub-feature */ name: string; + /** + * Whether or not this privilege should only be granted to `All Spaces *`. Should be used for features that do not + * support Spaces. Defaults to `false`. + */ + requireAllSpaces?: boolean; + + /** + * Optional message to display on the Role Management screen when configuring permissions for this feature. + */ + privilegesTooltip?: string; + /** Collection of privilege groups */ privilegeGroups: readonly SubFeaturePrivilegeGroupConfig[]; } @@ -90,6 +101,10 @@ export class SubFeature { return this.config.privilegeGroups; } + public get requireAllSpaces() { + return this.config.requireAllSpaces ?? false; + } + public toRaw() { return { ...this.config }; } diff --git a/x-pack/plugins/features/server/feature_schema.ts b/x-pack/plugins/features/server/feature_schema.ts index 30b2ddea3d7d..05d172887d87 100644 --- a/x-pack/plugins/features/server/feature_schema.ts +++ b/x-pack/plugins/features/server/feature_schema.ts @@ -163,6 +163,8 @@ const kibanaMutuallyExclusiveSubFeaturePrivilegeSchema = const kibanaSubFeatureSchema = schema.object({ name: schema.string(), + requireAllSpaces: schema.maybe(schema.boolean()), + privilegesTooltip: schema.maybe(schema.string()), privilegeGroups: schema.maybe( schema.arrayOf( schema.oneOf([ diff --git a/x-pack/plugins/files/common/constants.ts b/x-pack/plugins/files/common/constants.ts index be0bfa3ca80c..665945c1c65c 100644 --- a/x-pack/plugins/files/common/constants.ts +++ b/x-pack/plugins/files/common/constants.ts @@ -27,3 +27,5 @@ export const FILE_SHARE_SO_TYPE = 'fileShare'; * The name of the fixed size ES-backed blob store */ export const ES_FIXED_SIZE_INDEX_BLOB_STORE = 'esFixedSizeIndex' as const; + +export const FILES_MANAGE_PRIVILEGE = 'files:manageFiles' as const; diff --git a/x-pack/plugins/files/kibana.json b/x-pack/plugins/files/kibana.json index ad83b24ef084..47637d78fa0d 100755 --- a/x-pack/plugins/files/kibana.json +++ b/x-pack/plugins/files/kibana.json @@ -9,7 +9,6 @@ "description": "File upload, download, sharing, and serving over HTTP implementation in Kibana.", "server": true, "ui": true, - "requiredPlugins": [], "requiredBundles": ["kibanaUtils"], "optionalPlugins": ["security", "usageCollection"] } diff --git a/x-pack/plugins/files/public/components/file_picker/components/file_card.tsx b/x-pack/plugins/files/public/components/file_picker/components/file_card.tsx index 4c290b1b114e..88c77f36a6c0 100644 --- a/x-pack/plugins/files/public/components/file_picker/components/file_card.tsx +++ b/x-pack/plugins/files/public/components/file_picker/components/file_card.tsx @@ -58,11 +58,6 @@ export const FileCard: FunctionComponent = ({ file }) => { `} meta={file.meta as FileImageMetadata} src={client.getDownloadHref({ id: file.id, fileKind: kind })} - // There is an issue where the intersection observer does not fire reliably. - // I'm not sure if this is becuause of the image being in a modal - // The result is that the image does not always get loaded. - // TODO: Investigate this behaviour further - lazy={false} /> ) : (
this.setIsLoading(true))).subscribe(), this.internalIsLoading$ .pipe(debounceTime(100), distinctUntilChanged()) .subscribe(this.isLoading$), ]; } + private readonly requests$ = combineLatest([ + this.currentPage$.pipe(distinctUntilChanged()), + this.query$.pipe(distinctUntilChanged(), debounceTime(100)), + this.retry$, + ]); + /** * File objects we have loaded on the front end, stored here so that it can * easily be passed to all relevant UI. @@ -74,11 +81,7 @@ export class FilePickerState { * @note This is not explicitly kept in sync with the selected files! * @note This is not explicitly kept in sync with the selected files! */ - public readonly files$ = combineLatest([ - this.currentPage$.pipe(distinctUntilChanged()), - this.query$.pipe(distinctUntilChanged(), debounceTime(100)), - this.retry$, - ]).pipe( + public readonly files$ = this.requests$.pipe( switchMap(([page, query]) => this.sendRequest(page, query)), tap(({ total }) => this.updateTotalPages({ total })), tap(({ total }) => this.hasFiles$.next(Boolean(total))), diff --git a/x-pack/plugins/files/public/components/image/components/img.tsx b/x-pack/plugins/files/public/components/image/components/img.tsx index 295b062ca1fd..953eb93a1991 100644 --- a/x-pack/plugins/files/public/components/image/components/img.tsx +++ b/x-pack/plugins/files/public/components/image/components/img.tsx @@ -12,21 +12,25 @@ import { css } from '@emotion/react'; import { sizes } from '../styles'; export interface Props extends ImgHTMLAttributes { - hidden: boolean; size?: EuiImageSize; observerRef: (el: null | HTMLImageElement) => void; } export const Img = React.forwardRef( - ({ observerRef, src, hidden, size, ...rest }, ref) => { + ({ observerRef, src, size, ...rest }, ref) => { const { euiTheme } = useEuiTheme(); const styles = [ css` transition: opacity ${euiTheme.animation.extraFast}; `, - hidden + !src ? css` visibility: hidden; + position: absolute; // ensure that empty img tag occupies full container + top: 0; + right: 0; + bottom: 0; + left: 0; ` : undefined, size ? sizes[size] : undefined, diff --git a/x-pack/plugins/files/public/components/image/image.tsx b/x-pack/plugins/files/public/components/image/image.tsx index b83739d180c9..e353fced3ec3 100644 --- a/x-pack/plugins/files/public/components/image/image.tsx +++ b/x-pack/plugins/files/public/components/image/image.tsx @@ -32,15 +32,6 @@ export interface Props extends ImgHTMLAttributes { * Emits when the image first becomes visible */ onFirstVisible?: () => void; - - /** - * As an optimisation images are only loaded when they are visible. - * This setting overrides this behavior and loads an image as soon as the - * component mounts. - * - * @default true - */ - lazy?: boolean; } /** @@ -55,33 +46,18 @@ export interface Props extends ImgHTMLAttributes { */ export const Image = React.forwardRef( ( - { - src, - alt, - onFirstVisible, - onLoad, - onError, - meta, - wrapperProps, - size = 'original', - lazy = true, - ...rest - }, + { src, alt, onFirstVisible, onLoad, onError, meta, wrapperProps, size = 'original', ...rest }, ref ) => { const [isLoaded, setIsLoaded] = useState(false); const [blurDelayExpired, setBlurDelayExpired] = useState(false); const { isVisible, ref: observerRef } = useViewportObserver({ onFirstVisible }); - const loadImage = lazy ? isVisible : true; - useEffect(() => { - let unmounted = false; const id = window.setTimeout(() => { - if (!unmounted) setBlurDelayExpired(true); + setBlurDelayExpired(true); }, 200); return () => { - unmounted = true; window.clearTimeout(id); }; }, []); @@ -112,8 +88,7 @@ export const Image = React.forwardRef( observerRef={observerRef} ref={ref} size={size} - hidden={!loadImage} - src={loadImage ? src : undefined} + src={isVisible ? src : undefined} alt={alt} onLoad={(ev) => { setIsLoaded(true); diff --git a/x-pack/plugins/files/server/feature.ts b/x-pack/plugins/files/server/feature.ts new file mode 100644 index 000000000000..1685e95cc9fa --- /dev/null +++ b/x-pack/plugins/files/server/feature.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DEFAULT_APP_CATEGORIES } from '@kbn/core-application-common'; +import { KibanaFeatureConfig } from '@kbn/features-plugin/common'; +import { i18n } from '@kbn/i18n'; +import { PLUGIN_ID } from '../common'; +import { FILES_MANAGE_PRIVILEGE } from '../common/constants'; +import { hiddenTypes } from './saved_objects'; + +// TODO: This should be registered once we have a management section for files content +export const filesFeature: KibanaFeatureConfig = { + id: PLUGIN_ID, + name: i18n.translate('xpack.files.featureRegistry.filesFeatureName', { + defaultMessage: 'Files', + }), + minimumLicense: 'basic', + order: 10000, + category: DEFAULT_APP_CATEGORIES.management, + app: [PLUGIN_ID], + privilegesTooltip: i18n.translate('xpack.files.featureRegistry.filesPrivilegesTooltip', { + defaultMessage: 'Provide access to files across all apps', + }), + privileges: { + all: { + app: [PLUGIN_ID], + savedObject: { + all: hiddenTypes, + read: hiddenTypes, + }, + ui: [], + api: [FILES_MANAGE_PRIVILEGE], + }, + read: { + app: [PLUGIN_ID], + savedObject: { + all: hiddenTypes, + read: hiddenTypes, + }, + ui: [], + }, + }, +}; diff --git a/x-pack/plugins/files/server/routes/find.ts b/x-pack/plugins/files/server/routes/find.ts index 4f5a6da46b45..9ec5ab681cb6 100644 --- a/x-pack/plugins/files/server/routes/find.ts +++ b/x-pack/plugins/files/server/routes/find.ts @@ -7,6 +7,7 @@ import { schema } from '@kbn/config-schema'; import type { CreateHandler, FilesRouter } from './types'; import { FileJSON } from '../../common'; +import { FILES_MANAGE_PRIVILEGE } from '../../common/constants'; import { FILES_API_ROUTES, CreateRouteDefinition } from './api_routes'; const method = 'post' as const; @@ -63,16 +64,14 @@ const handler: CreateHandler = async ({ files }, req, res) => { }); }; -// TODO: Find out whether we want to add stricter access controls to this route. -// Currently this is giving read-access to all files which bypasses the -// security we set up on a per route level for the "getById" and "list" endpoints. -// Alternatively, we can remove the access controls on the "file kind" endpoints -// or remove them entirely. export function register(router: FilesRouter) { router[method]( { path: FILES_API_ROUTES.find, validate: { ...rt }, + options: { + tags: [`access:${FILES_MANAGE_PRIVILEGE}`], + }, }, handler ); diff --git a/x-pack/plugins/files/server/routes/metrics.ts b/x-pack/plugins/files/server/routes/metrics.ts index eb1d0ae39b9a..9ae898e17bb8 100644 --- a/x-pack/plugins/files/server/routes/metrics.ts +++ b/x-pack/plugins/files/server/routes/metrics.ts @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { FILES_MANAGE_PRIVILEGE } from '../../common/constants'; import type { FilesRouter } from './types'; import { FilesMetrics } from '../../common'; @@ -27,6 +28,9 @@ export function register(router: FilesRouter) { { path: FILES_API_ROUTES.metrics, validate: {}, + options: { + tags: [`access:${FILES_MANAGE_PRIVILEGE}`], + }, }, handler ); diff --git a/x-pack/plugins/fleet/.storybook/smoke.test.tsx b/x-pack/plugins/fleet/.storybook/smoke.test.tsx index 1fca60a1af4a..a3984a42a5ab 100644 --- a/x-pack/plugins/fleet/.storybook/smoke.test.tsx +++ b/x-pack/plugins/fleet/.storybook/smoke.test.tsx @@ -11,14 +11,16 @@ import { act } from 'react-dom/test-utils'; import initStoryshots from '@storybook/addon-storyshots'; describe('Fleet Storybook Smoke', () => { - initStoryshots({ - configPath: __dirname, - framework: 'react', - test: async ({ story }) => { - const renderer = mount(createElement(story.render)); - // wait until the element will perform all renders and resolve all promises (lazy loading, especially) - await act(() => new Promise((resolve) => setTimeout(resolve, 0))); - expect(renderer.html()).not.toContain('euiErrorBoundary'); - }, + test('Init', async () => { + await initStoryshots({ + configPath: __dirname, + framework: 'react', + test: async ({ story }) => { + const renderer = mount(createElement(story.render)); + // wait until the element will perform all renders and resolve all promises (lazy loading, especially) + await act(() => new Promise((resolve) => setTimeout(resolve, 0))); + expect(renderer.html()).not.toContain('euiErrorBoundary'); + }, + }); }); }); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/components/post_install_add_agent_modal.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/components/post_install_add_agent_modal.tsx index b6d568971140..18be0df35539 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/components/post_install_add_agent_modal.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/components/post_install_add_agent_modal.tsx @@ -51,7 +51,7 @@ export const PostInstallAddAgentModal: React.FunctionComponent<{

Elastic Agent, }} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.test.tsx index 98b168830820..33503018b49a 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.test.tsx @@ -110,7 +110,7 @@ describe('agent_list_page', () => { totalInactive: 0, }, }); - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterEach(() => { diff --git a/x-pack/plugins/fleet/server/services/elastic_agent_manifest.ts b/x-pack/plugins/fleet/server/services/elastic_agent_manifest.ts index f05441f3db1f..af6ae76a646b 100644 --- a/x-pack/plugins/fleet/server/services/elastic_agent_manifest.ts +++ b/x-pack/plugins/fleet/server/services/elastic_agent_manifest.ts @@ -81,21 +81,12 @@ spec: - name: varlog mountPath: /var/log readOnly: true - - name: etc-kubernetes - mountPath: /hostfs/etc/kubernetes + - name: etc-full + mountPath: /hostfs/etc readOnly: true - name: var-lib mountPath: /hostfs/var/lib readOnly: true - - name: passwd - mountPath: /hostfs/etc/passwd - readOnly: true - - name: group - mountPath: /hostfs/etc/group - readOnly: true - - name: etcsysmd - mountPath: /hostfs/etc/systemd - readOnly: true volumes: - name: datastreams configMap: @@ -113,26 +104,15 @@ spec: - name: varlog hostPath: path: /var/log - # Needed for cloudbeat - - name: etc-kubernetes + # The following volumes are needed for Cloud Security Posture integration (cloudbeat) + # If you are not using this integration, then these volumes and the corresponding + # mounts can be removed. + - name: etc-full hostPath: - path: /etc/kubernetes - # Needed for cloudbeat + path: /etc - name: var-lib hostPath: path: /var/lib - # Needed for cloudbeat - - name: passwd - hostPath: - path: /etc/passwd - # Needed for cloudbeat - - name: group - hostPath: - path: /etc/group - # Needed for cloudbeat - - name: etcsysmd - hostPath: - path: /etc/systemd --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding @@ -367,21 +347,12 @@ spec: - name: varlog mountPath: /var/log readOnly: true - - name: etc-kubernetes - mountPath: /hostfs/etc/kubernetes + - name: etc-full + mountPath: /hostfs/etc readOnly: true - name: var-lib mountPath: /hostfs/var/lib readOnly: true - - name: passwd - mountPath: /hostfs/etc/passwd - readOnly: true - - name: group - mountPath: /hostfs/etc/group - readOnly: true - - name: etcsysmd - mountPath: /hostfs/etc/systemd - readOnly: true - name: etc-mid mountPath: /etc/machine-id readOnly: true @@ -398,26 +369,15 @@ spec: - name: varlog hostPath: path: /var/log - # Needed for cloudbeat - - name: etc-kubernetes + # The following volumes are needed for Cloud Security Posture integration (cloudbeat) + # If you are not using this integration, then these volumes and the corresponding + # mounts can be removed. + - name: etc-full hostPath: - path: /etc/kubernetes - # Needed for cloudbeat + path: /etc - name: var-lib hostPath: path: /var/lib - # Needed for cloudbeat - - name: passwd - hostPath: - path: /etc/passwd - # Needed for cloudbeat - - name: group - hostPath: - path: /etc/group - # Needed for cloudbeat - - name: etcsysmd - hostPath: - path: /etc/systemd # Mount /etc/machine-id from the host to determine host ID # Needed for Elastic Security integration - name: etc-mid diff --git a/x-pack/plugins/fleet/server/services/epm/registry/proxy.ts b/x-pack/plugins/fleet/server/services/epm/registry/proxy.ts index 8b1529de93b3..0ff64789d12e 100644 --- a/x-pack/plugins/fleet/server/services/epm/registry/proxy.ts +++ b/x-pack/plugins/fleet/server/services/epm/registry/proxy.ts @@ -5,12 +5,9 @@ * 2.0. */ -import HttpProxyAgent from 'http-proxy-agent'; -import HttpsProxyAgent from 'https-proxy-agent'; -import type { - HttpsProxyAgentOptions, - HttpsProxyAgent as IHttpsProxyAgent, -} from 'https-proxy-agent'; +import { HttpProxyAgent } from 'http-proxy-agent'; +import { HttpsProxyAgent } from 'https-proxy-agent'; +import type { HttpsProxyAgentOptions } from 'https-proxy-agent'; import { appContextService } from '../..'; @@ -20,7 +17,7 @@ export interface RegistryProxySettings { proxyRejectUnauthorizedCertificates?: boolean; } -type ProxyAgent = IHttpsProxyAgent | HttpProxyAgent; +type ProxyAgent = HttpsProxyAgent | HttpProxyAgent; type GetProxyAgentParams = RegistryProxySettings & { targetUrl: string }; export function getRegistryProxyUrl(): string | undefined { @@ -30,11 +27,10 @@ export function getRegistryProxyUrl(): string | undefined { export function getProxyAgent(options: GetProxyAgentParams): ProxyAgent { const isHttps = options.targetUrl.startsWith('https:'); - const agentOptions = isHttps && getProxyAgentOptions(options); + const agentOptions = isHttps ? getProxyAgentOptions(options) : options.proxyUrl; const agent: ProxyAgent = isHttps - ? // @ts-expect-error ts(7009) HttpsProxyAgent isn't a class so TS complains about using `new` - new HttpsProxyAgent(agentOptions) - : new HttpProxyAgent(options.proxyUrl); + ? new HttpsProxyAgent(agentOptions) + : new HttpProxyAgent(agentOptions); return agent; } diff --git a/x-pack/plugins/fleet/server/services/package_policies/package_policy_name_helper.ts b/x-pack/plugins/fleet/server/services/package_policies/package_policy_name_helper.ts index c712ccc06ad9..90db39a3e03b 100644 --- a/x-pack/plugins/fleet/server/services/package_policies/package_policy_name_helper.ts +++ b/x-pack/plugins/fleet/server/services/package_policies/package_policy_name_helper.ts @@ -39,20 +39,23 @@ export async function incrementPackagePolicyCopyName( // find all pacakge policies starting with the same name and increment the name const packagePolicyData = await packagePolicyService.list(soClient, { perPage: SO_SEARCH_LIMIT, - kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.name: ${packageName}*`, + // split package name on first space as KQL do not support wildcard and space + kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.name: ${packageName.split(' ')[0]}*`, }); const maxVersion = packagePolicyData.items.length > 0 ? Math.max( - ...packagePolicyData.items.map((item) => { - const matches = item.name.match(/^(.*)\s\(copy\s?([0-9]*)\)$/); - if (matches) { - return parseInt(matches[2], 10) || 1; - } + ...packagePolicyData.items + .filter((item) => item.name.startsWith(packageName)) + .map((item) => { + const matches = item.name.match(/^(.*)\s\(copy\s?([0-9]*)\)$/); + if (matches) { + return parseInt(matches[2], 10) || 1; + } - return 0; - }) + return 0; + }) ) : 0; diff --git a/x-pack/plugins/global_search_bar/public/components/search_bar.test.tsx b/x-pack/plugins/global_search_bar/public/components/search_bar.test.tsx index 78f38087b9ad..2bd37e0448b6 100644 --- a/x-pack/plugins/global_search_bar/public/components/search_bar.test.tsx +++ b/x-pack/plugins/global_search_bar/public/components/search_bar.test.tsx @@ -42,7 +42,7 @@ const createResult = (result: Result): GlobalSearchResult => { const createBatch = (...results: Result[]): GlobalSearchBatchedResults => ({ results: results.map(createResult), }); -jest.useFakeTimers(); +jest.useFakeTimers('legacy'); describe('SearchBar', () => { let searchService: ReturnType; diff --git a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/edit_warning.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/edit_warning.test.ts index 98d6078da031..39417219cddb 100644 --- a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/edit_warning.test.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/edit_warning.test.ts @@ -16,7 +16,7 @@ describe(' edit warning', () => { const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/frozen_phase.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/frozen_phase.test.ts index ffe11133be7f..5b08b4225916 100644 --- a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/frozen_phase.test.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/frozen_phase.test.ts @@ -17,7 +17,7 @@ describe(' frozen phase', () => { const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/cloud_aware_behavior.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/cloud_aware_behavior.test.ts index 75db772ec092..be83028c81b2 100644 --- a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/cloud_aware_behavior.test.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/cloud_aware_behavior.test.ts @@ -18,7 +18,7 @@ describe(' node allocation cloud-aware behavior', () => { const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/cold_phase.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/cold_phase.test.ts index 63382de45f41..217a6264f574 100644 --- a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/cold_phase.test.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/cold_phase.test.ts @@ -14,7 +14,7 @@ describe(' node allocation in the cold phase', () => { const { httpSetup, setDelayResponse, httpRequestsMockHelpers } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/general_behavior.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/general_behavior.test.ts index 4830cee8ee23..e9f970fbba20 100644 --- a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/general_behavior.test.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/general_behavior.test.ts @@ -24,7 +24,7 @@ describe(' node allocation general behavior', () => { const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/warm_phase.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/warm_phase.test.ts index 6f96aaf07da1..df6e409550be 100644 --- a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/warm_phase.test.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/warm_phase.test.ts @@ -14,7 +14,7 @@ describe(' node allocation in the warm phase', () => { const { httpSetup, setDelayResponse, httpRequestsMockHelpers } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/request_flyout.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/request_flyout.test.ts index 61dda6fa65ef..30873b54548e 100644 --- a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/request_flyout.test.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/request_flyout.test.ts @@ -15,7 +15,7 @@ describe(' request flyout', () => { const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/cold_phase_validation.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/cold_phase_validation.test.ts index e75d2cb72ab2..cee0af407442 100644 --- a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/cold_phase_validation.test.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/cold_phase_validation.test.ts @@ -15,7 +15,7 @@ describe(' cold phase validation', () => { const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/error_indicators.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/error_indicators.test.ts index bd4a2caec0be..2503b32f0506 100644 --- a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/error_indicators.test.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/error_indicators.test.ts @@ -14,7 +14,7 @@ describe(' error indicators', () => { const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/hot_phase_validation.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/hot_phase_validation.test.ts index 71f83a59360d..54683f638c74 100644 --- a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/hot_phase_validation.test.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/hot_phase_validation.test.ts @@ -16,7 +16,7 @@ describe(' hot phase validation', () => { const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/policy_name_validation.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/policy_name_validation.test.ts index c530f73a66c1..19ee23142cd9 100644 --- a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/policy_name_validation.test.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/policy_name_validation.test.ts @@ -17,7 +17,7 @@ describe(' policy name validation', () => { const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/timing.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/timing.test.ts index 5838f04ba70e..08790cf23bf9 100644 --- a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/timing.test.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/timing.test.ts @@ -18,7 +18,7 @@ describe(' timing validation', () => { const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/warm_phase_validation.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/warm_phase_validation.test.ts index 47917b1f8e3d..6fb079819ec3 100644 --- a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/warm_phase_validation.test.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/warm_phase_validation.test.ts @@ -15,7 +15,7 @@ describe(' warm phase validation', () => { const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_clone.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_clone.test.tsx index 861b1041a4f1..99277bb4cc4b 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_clone.test.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_clone.test.tsx @@ -48,7 +48,7 @@ describe('', () => { const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); httpRequestsMockHelpers.setLoadTelemetryResponse({}); httpRequestsMockHelpers.setLoadComponentTemplatesResponse([]); httpRequestsMockHelpers.setLoadTemplateResponse(templateToClone.name, templateToClone); diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx index 6813398a34ae..ca42777532ac 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx @@ -80,7 +80,7 @@ describe('', () => { const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); httpRequestsMockHelpers.setLoadComponentTemplatesResponse(componentTemplates); httpRequestsMockHelpers.setLoadNodesPluginsResponse([]); diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.test.tsx index 4b94cb92c83d..a99b5476a766 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.test.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.test.tsx @@ -52,7 +52,7 @@ describe('', () => { const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); httpRequestsMockHelpers.setLoadComponentTemplatesResponse([]); }); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/date_range_datatype.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/date_range_datatype.test.tsx index 60114223bef0..1b752807ba1c 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/date_range_datatype.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/date_range_datatype.test.tsx @@ -29,7 +29,7 @@ describe('Mappings editor: date range datatype', () => { let testBed: MappingsEditorTestBed; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/other_datatype.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/other_datatype.test.tsx index 81ca155b022b..58d063cc658c 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/other_datatype.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/other_datatype.test.tsx @@ -21,7 +21,7 @@ describe('Mappings editor: other datatype', () => { let testBed: MappingsEditorTestBed; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/point_datatype.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/point_datatype.test.tsx index 8c235f2d2d9e..c35d6c231186 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/point_datatype.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/point_datatype.test.tsx @@ -28,7 +28,7 @@ describe('Mappings editor: point datatype', () => { let testBed: MappingsEditorTestBed; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/scaled_float_datatype.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/scaled_float_datatype.test.tsx index 17e7317e098c..bbd0afa495e4 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/scaled_float_datatype.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/scaled_float_datatype.test.tsx @@ -31,7 +31,7 @@ describe('Mappings editor: scaled float datatype', () => { let testBed: MappingsEditorTestBed; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/shape_datatype.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/shape_datatype.test.tsx index 94aea2a3b13a..9724a81d673b 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/shape_datatype.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/shape_datatype.test.tsx @@ -29,7 +29,7 @@ describe('Mappings editor: shape datatype', () => { let testBed: MappingsEditorTestBed; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx index db8678478aa3..38fd47476e93 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx @@ -34,7 +34,7 @@ describe('Mappings editor: text datatype', () => { let testBed: MappingsEditorTestBed; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/version_datatype.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/version_datatype.test.tsx index 5f638ebb31a4..6a61d9bac058 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/version_datatype.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/version_datatype.test.tsx @@ -26,7 +26,7 @@ describe('Mappings editor: version datatype', () => { let testBed: MappingsEditorTestBed; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/edit_field.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/edit_field.test.tsx index 4440f54f1034..7403be97c664 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/edit_field.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/edit_field.test.tsx @@ -21,7 +21,7 @@ describe('Mappings editor: edit field', () => { let testBed: MappingsEditorTestBed; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mapped_fields.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mapped_fields.test.tsx index eb219503424b..fe32e3b6099f 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mapped_fields.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mapped_fields.test.tsx @@ -14,7 +14,7 @@ const onChangeHandler = jest.fn(); describe('Mappings editor: mapped fields', () => { beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx index 9b4f31f3dfc1..c296361f3685 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx @@ -21,7 +21,7 @@ describe('Mappings editor: core', () => { let testBed: MappingsEditorTestBed; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/runtime_fields.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/runtime_fields.test.tsx index 76e5dcda8fc4..d1b85685588d 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/runtime_fields.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/runtime_fields.test.tsx @@ -20,7 +20,7 @@ describe('Mappings editor: runtime fields', () => { let getMappingsEditorData = getMappingsEditorDataFactory(onChangeHandler); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table.tsx index f81f80faca73..d045c594f0ee 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { EuiBasicTable } from '@elastic/eui'; +import { EuiInMemoryTable } from '@elastic/eui'; import type { SnapshotNode } from '../../../../../common/http_api'; import { HostsTableColumns } from './hosts_table_columns'; import { useHostTable } from '../hooks/use_host_table'; @@ -18,11 +18,5 @@ interface Props { export const HostsTable: React.FunctionComponent = ({ nodes }) => { const items = useHostTable(nodes); - return ( - - ); + return ; }; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table_columns.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table_columns.tsx index 7bd8184b0e46..04d035b5fb7e 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table_columns.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table_columns.tsx @@ -9,13 +9,14 @@ import { EuiBasicTableColumn } from '@elastic/eui'; import React from 'react'; import { i18n } from '@kbn/i18n'; import { EuiText } from '@elastic/eui'; +import type { SnapshotNodeMetric } from '../../../../../common/http_api'; import { scaleUpPercentage } from '../../../../components/infrastructure_node_metrics_tables/shared/hooks'; -import type { SnapshotNodeMetric } from '../../../../../common/http_api/snapshot_api'; import { NumberCell } from '../../../../components/infrastructure_node_metrics_tables/shared/components'; interface HostNodeRow extends HostMetics { - os?: string | null | undefined; - servicesOnHost?: number | null | undefined; + os?: string | null; + servicesOnHost?: number | null; + name: string; } export interface HostMetics { @@ -31,7 +32,8 @@ export const HostsTableColumns: Array> = [ name: i18n.translate('xpack.infra.hostsTable.nameColumnHeader', { defaultMessage: 'Name', }), - field: 'label', + field: 'name', + sortable: true, truncateText: true, textOnly: true, render: (name: string) => {name}, @@ -41,59 +43,63 @@ export const HostsTableColumns: Array> = [ defaultMessage: 'Operating System', }), field: 'os', - render: (os: string) => {os ?? '-'}, + sortable: true, + render: (os: string) => {os}, }, { name: i18n.translate('xpack.infra.hostsTable.numberOfCpusColumnHeader', { defaultMessage: '# of CPUs', }), - field: 'cpuCores', - render: (cpuCores: { value: number }) => , + field: 'cpuCores.value', + sortable: true, + render: (value: number) => , }, { name: i18n.translate('xpack.infra.hostsTable.diskLatencyColumnHeader', { defaultMessage: 'Disk Latency', }), field: 'diskLatency', + sortable: true, render: (ds: number) => , }, { name: i18n.translate('xpack.infra.hostsTable.averageTxColumnHeader', { defaultMessage: 'TX (avg.)', }), - field: 'tx', - render: (tx: { avg: number }) => , + field: 'tx.avg', + sortable: true, + render: (avg: number) => , }, { name: i18n.translate('xpack.infra.hostsTable.averageRxColumnHeader', { defaultMessage: 'RX (avg.)', }), - field: 'rx', - render: (rx: { avg: number }) => , + field: 'rx.avg', + sortable: true, + render: (avg: number) => , }, { name: i18n.translate('xpack.infra.hostsTable.averageMemoryTotalColumnHeader', { defaultMessage: 'Memory total (avg.)', }), - field: 'memoryTotal', - render: (memoryTotal: { avg: number }) => ( - - ), + field: 'memoryTotal.avg', + sortable: true, + render: (avg: number) => , }, { name: i18n.translate('xpack.infra.hostsTable.servicesOnHostColumnHeader', { defaultMessage: 'Services on Host', }), field: 'servicesOnHost', + sortable: true, render: (servicesOnHost: number) => , }, { name: i18n.translate('xpack.infra.hostsTable.averageMemoryUsageColumnHeader', { defaultMessage: 'Memory usage (avg.)', }), - field: 'memory', - render: (memory: { avg: number }) => ( - - ), + field: 'memory.avg', + sortable: true, + render: (avg: number) => , }, ]; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_host_table.test.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_host_table.test.ts index e0d26413f045..81b0e93f1012 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_host_table.test.ts +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_host_table.test.ts @@ -70,6 +70,8 @@ describe('useHostTable hook', () => { const items = [ { + name: 'host-0', + os: '-', rx: { name: 'rx', avg: 252456.92916666667, @@ -91,11 +93,10 @@ describe('useHostTable hook', () => { avg: 34359.738368, }, - value: 'host-0', - label: 'host-0', - os: null, }, { + name: 'host-1', + os: 'macOS', rx: { name: 'rx', avg: 95.86339715321859, @@ -116,10 +117,6 @@ describe('useHostTable hook', () => { name: 'memoryTotal', avg: 9.194304, }, - value: 'host-1', - label: 'host-1', - ip: '243.86.94.22', - os: 'macOS', }, ]; const result = renderHook(() => useHostTable(nodes)); diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_host_table.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_host_table.ts index 4c604b2a2721..32eed5e54ce6 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_host_table.ts +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_host_table.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { last } from 'lodash'; import { useMemo } from 'react'; import type { SnapshotNode, SnapshotNodeMetric } from '../../../../../common/http_api'; import { HostMetics } from '../components/hosts_table_columns'; @@ -13,9 +12,10 @@ import { HostMetics } from '../components/hosts_table_columns'; type MappedMetrics = Record; export const useHostTable = (nodes: SnapshotNode[]) => { - const items: MappedMetrics[] = useMemo(() => { - return nodes.map(({ metrics, path }) => ({ - ...last(path), + const items = useMemo(() => { + return nodes.map(({ metrics, path, name }) => ({ + name, + os: path.at(-1)?.os ?? '-', ...metrics.reduce((data, metric) => { data[metric.name as keyof HostMetics] = metric; return data; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hosts_content.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/hosts_content.tsx index 25aa168ca8dc..5ab4a062d7fc 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/hosts_content.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hosts_content.tsx @@ -50,15 +50,15 @@ export const HostsContent: React.FunctionComponent = () => { [], 'host', sourceId, - 1666081614879, // currentTime. need to add support for TimeRange? + 1666710279338, // currentTime. need to add support for TimeRange? '', '', true, { - from: 1666081614879, // dynamic time range needs to be supported + from: 1666710279338, // dynamic time range needs to be supported interval: '1m', lookbackSize: 5, - to: 1666082814879, + to: 1666711479338, } ); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/pipeline_processors_editor.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/pipeline_processors_editor.test.tsx index c5daa1db2ac0..9e31c71e0f27 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/pipeline_processors_editor.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/pipeline_processors_editor.test.tsx @@ -39,7 +39,7 @@ describe('Pipeline Editor', () => { let testBed: SetupResult; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/append.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/append.test.tsx index f35e05b800f1..632dc824dbd1 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/append.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/append.test.tsx @@ -17,7 +17,7 @@ describe('Processor: Append', () => { const { httpSetup } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/bytes.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/bytes.test.tsx index d4ac176d6aaf..44acef642caa 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/bytes.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/bytes.test.tsx @@ -16,7 +16,7 @@ describe('Processor: Bytes', () => { const { httpSetup } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/circle.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/circle.test.tsx index 00153471ea65..01b25bc508bc 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/circle.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/circle.test.tsx @@ -16,7 +16,7 @@ describe('Processor: Circle', () => { const { httpSetup } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/common_processor_fields.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/common_processor_fields.test.tsx index ebffd5adf78c..45781e15eca5 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/common_processor_fields.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/common_processor_fields.test.tsx @@ -16,7 +16,7 @@ describe('Processor: Common Fields For All Processors', () => { const { httpSetup } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/community_id.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/community_id.test.tsx index e571474576ff..4ed8ff5b0528 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/community_id.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/community_id.test.tsx @@ -16,7 +16,7 @@ describe('Processor: Community id', () => { const { httpSetup } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/convert.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/convert.test.tsx index 3090e59b32e0..d328bb6759b2 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/convert.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/convert.test.tsx @@ -26,7 +26,7 @@ describe('Processor: Convert', () => { const { httpSetup } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/csv.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/csv.test.tsx index 6414976b56f9..c3237c1d2325 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/csv.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/csv.test.tsx @@ -29,7 +29,7 @@ describe('Processor: CSV', () => { const { httpSetup } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/date.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/date.test.tsx index 22666ebbe2a9..fa1edd6d6cb5 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/date.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/date.test.tsx @@ -16,7 +16,7 @@ describe('Processor: Date', () => { const { httpSetup } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/date_index.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/date_index.test.tsx index b9e990f36c15..74a49b11821e 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/date_index.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/date_index.test.tsx @@ -16,7 +16,7 @@ describe('Processor: Date Index Name', () => { const { httpSetup } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/dot_expander.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/dot_expander.test.tsx index 7ebad2de01a9..c95a25487041 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/dot_expander.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/dot_expander.test.tsx @@ -16,7 +16,7 @@ describe('Processor: Dot Expander', () => { const { httpSetup } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/fail.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/fail.test.tsx index 9b8148bd0dff..94a7bc65e65c 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/fail.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/fail.test.tsx @@ -15,7 +15,7 @@ describe('Processor: Fail', () => { const { httpSetup } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/fingerprint.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/fingerprint.test.tsx index 49d7937fab00..b5b5fa9c9e1d 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/fingerprint.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/fingerprint.test.tsx @@ -28,7 +28,7 @@ describe('Processor: Fingerprint', () => { const { httpSetup } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/grok.test.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/grok.test.ts index 90ea4ed1a010..89efc203c0c5 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/grok.test.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/grok.test.ts @@ -17,7 +17,7 @@ describe('Processor: Grok', () => { const { httpSetup } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); // disable all react-beautiful-dnd development warnings (window as any)['__react-beautiful-dnd-disable-dev-warnings'] = true; }); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/network_direction.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/network_direction.test.tsx index 4f92aec06efd..330b651b5b51 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/network_direction.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/network_direction.test.tsx @@ -30,7 +30,7 @@ describe('Processor: Network Direction', () => { const { httpSetup } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor_form.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor_form.test.tsx index b2e2fb81f2c8..1b89dbe835a9 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor_form.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor_form.test.tsx @@ -14,7 +14,7 @@ describe('Processor: Bytes', () => { const { httpSetup } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/registered_domain.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/registered_domain.test.tsx index dcf332912a94..b4fafe6a4757 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/registered_domain.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/registered_domain.test.tsx @@ -24,7 +24,7 @@ describe('Processor: Registered Domain', () => { const { httpSetup } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/set.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/set.test.tsx index ebfa67864890..d90c34ee10c8 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/set.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/set.test.tsx @@ -16,7 +16,7 @@ describe('Processor: Set', () => { const { httpSetup } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/uri_parts.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/uri_parts.test.tsx index 9062fcc02f7f..e59dba0aecdd 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/uri_parts.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/uri_parts.test.tsx @@ -22,7 +22,7 @@ describe('Processor: URI parts', () => { const { httpSetup } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/user_agent.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/user_agent.test.tsx index ed778aa1cc1f..e2e083715c88 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/user_agent.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/user_agent.test.tsx @@ -27,7 +27,7 @@ describe('Processor: User Agent', () => { const { httpSetup } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/test_pipeline.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/test_pipeline.test.tsx index b15172185cff..5bd7d1d30aea 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/test_pipeline.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/test_pipeline.test.tsx @@ -46,7 +46,7 @@ describe('Test pipeline', () => { }; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/lens/public/datasources/form_based/form_based.test.ts b/x-pack/plugins/lens/public/datasources/form_based/form_based.test.ts index 3f30dff6fd1a..fdaf1f51c644 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/form_based.test.ts +++ b/x-pack/plugins/lens/public/datasources/form_based/form_based.test.ts @@ -315,7 +315,9 @@ describe('IndexPattern Data Source', () => { describe('#toExpression', () => { it('should generate an empty expression when no columns are selected', async () => { const state = FormBasedDatasource.initialize(); - expect(FormBasedDatasource.toExpression(state, 'first', indexPatterns)).toEqual(null); + expect( + FormBasedDatasource.toExpression(state, 'first', indexPatterns, 'testing-seed') + ).toEqual(null); }); it('should create a table when there is a formula without aggs', async () => { @@ -338,7 +340,9 @@ describe('IndexPattern Data Source', () => { }, }, }; - expect(FormBasedDatasource.toExpression(queryBaseState, 'first', indexPatterns)).toEqual({ + expect( + FormBasedDatasource.toExpression(queryBaseState, 'first', indexPatterns, 'testing-seed') + ).toEqual({ chain: [ { function: 'createTable', @@ -385,8 +389,9 @@ describe('IndexPattern Data Source', () => { }, }; - expect(FormBasedDatasource.toExpression(queryBaseState, 'first', indexPatterns)) - .toMatchInlineSnapshot(` + expect( + FormBasedDatasource.toExpression(queryBaseState, 'first', indexPatterns, 'testing-seed') + ).toMatchInlineSnapshot(` Object { "chain": Array [ Object { @@ -487,6 +492,12 @@ describe('IndexPattern Data Source', () => { "partialRows": Array [ false, ], + "probability": Array [ + 1, + ], + "samplerSeed": Array [ + 1889181588, + ], "timeFields": Array [ "timestamp", ], @@ -560,7 +571,12 @@ describe('IndexPattern Data Source', () => { }, }; - const ast = FormBasedDatasource.toExpression(queryBaseState, 'first', indexPatterns) as Ast; + const ast = FormBasedDatasource.toExpression( + queryBaseState, + 'first', + indexPatterns, + 'testing-seed' + ) as Ast; expect(ast.chain[1].arguments.timeFields).toEqual(['timestamp', 'another_datefield']); }); @@ -595,7 +611,12 @@ describe('IndexPattern Data Source', () => { }, }; - const ast = FormBasedDatasource.toExpression(queryBaseState, 'first', indexPatterns) as Ast; + const ast = FormBasedDatasource.toExpression( + queryBaseState, + 'first', + indexPatterns, + 'testing-seed' + ) as Ast; expect((ast.chain[1].arguments.aggs[1] as Ast).chain[0].arguments.timeShift).toEqual(['1d']); }); @@ -802,7 +823,12 @@ describe('IndexPattern Data Source', () => { }, }; - const ast = FormBasedDatasource.toExpression(queryBaseState, 'first', indexPatterns) as Ast; + const ast = FormBasedDatasource.toExpression( + queryBaseState, + 'first', + indexPatterns, + 'testing-seed' + ) as Ast; const count = (ast.chain[1].arguments.aggs[1] as Ast).chain[0]; const sum = (ast.chain[1].arguments.aggs[2] as Ast).chain[0]; const average = (ast.chain[1].arguments.aggs[3] as Ast).chain[0]; @@ -866,7 +892,12 @@ describe('IndexPattern Data Source', () => { }, }; - const ast = FormBasedDatasource.toExpression(queryBaseState, 'first', indexPatterns) as Ast; + const ast = FormBasedDatasource.toExpression( + queryBaseState, + 'first', + indexPatterns, + 'testing-seed' + ) as Ast; expect(ast.chain[1].arguments.aggs[0]).toMatchInlineSnapshot(` Object { "chain": Array [ @@ -990,7 +1021,12 @@ describe('IndexPattern Data Source', () => { }, }; - const ast = FormBasedDatasource.toExpression(queryBaseState, 'first', indexPatterns) as Ast; + const ast = FormBasedDatasource.toExpression( + queryBaseState, + 'first', + indexPatterns, + 'testing-seed' + ) as Ast; const timeScaleCalls = ast.chain.filter((fn) => fn.function === 'lens_time_scale'); const formatCalls = ast.chain.filter((fn) => fn.function === 'lens_format_column'); expect(timeScaleCalls).toHaveLength(1); @@ -1055,7 +1091,12 @@ describe('IndexPattern Data Source', () => { }, }; - const ast = FormBasedDatasource.toExpression(queryBaseState, 'first', indexPatterns) as Ast; + const ast = FormBasedDatasource.toExpression( + queryBaseState, + 'first', + indexPatterns, + 'testing-seed' + ) as Ast; const filteredMetricAgg = (ast.chain[1].arguments.aggs[0] as Ast).chain[0].arguments; const metricAgg = (filteredMetricAgg.customMetric[0] as Ast).chain[0].arguments; const bucketAgg = (filteredMetricAgg.customBucket[0] as Ast).chain[0].arguments; @@ -1106,7 +1147,12 @@ describe('IndexPattern Data Source', () => { }, }; - const ast = FormBasedDatasource.toExpression(queryBaseState, 'first', indexPatterns) as Ast; + const ast = FormBasedDatasource.toExpression( + queryBaseState, + 'first', + indexPatterns, + 'testing-seed' + ) as Ast; const formatIndex = ast.chain.findIndex((fn) => fn.function === 'lens_format_column'); const calculationIndex = ast.chain.findIndex((fn) => fn.function === 'moving_average'); expect(calculationIndex).toBeLessThan(formatIndex); @@ -1154,7 +1200,12 @@ describe('IndexPattern Data Source', () => { }, }; - const ast = FormBasedDatasource.toExpression(queryBaseState, 'first', indexPatterns) as Ast; + const ast = FormBasedDatasource.toExpression( + queryBaseState, + 'first', + indexPatterns, + 'testing-seed' + ) as Ast; expect(ast.chain[1].arguments.metricsAtAllLevels).toEqual([false]); expect(JSON.parse(ast.chain[2].arguments.idMap[0] as string)).toEqual({ 'col-0-0': [expect.objectContaining({ id: 'bucket1' })], @@ -1193,7 +1244,12 @@ describe('IndexPattern Data Source', () => { }, }; - const ast = FormBasedDatasource.toExpression(queryBaseState, 'first', indexPatterns) as Ast; + const ast = FormBasedDatasource.toExpression( + queryBaseState, + 'first', + indexPatterns, + 'testing-seed' + ) as Ast; expect(ast.chain[1].arguments.timeFields).toEqual(['timestamp']); expect(ast.chain[1].arguments.timeFields).not.toContain('timefield'); }); @@ -1250,7 +1306,7 @@ describe('IndexPattern Data Source', () => { const optimizeMock = jest.spyOn(operationDefinitionMap.percentile, 'optimizeEsAggs'); - FormBasedDatasource.toExpression(queryBaseState, 'first', indexPatterns); + FormBasedDatasource.toExpression(queryBaseState, 'first', indexPatterns, 'testing-seed'); expect(operationDefinitionMap.percentile.optimizeEsAggs).toHaveBeenCalledTimes(1); @@ -1318,7 +1374,12 @@ describe('IndexPattern Data Source', () => { return { aggs: aggs.reverse(), esAggsIdMap }; }); - const ast = FormBasedDatasource.toExpression(queryBaseState, 'first', indexPatterns) as Ast; + const ast = FormBasedDatasource.toExpression( + queryBaseState, + 'first', + indexPatterns, + 'testing-seed' + ) as Ast; expect(operationDefinitionMap.percentile.optimizeEsAggs).toHaveBeenCalledTimes(1); @@ -1382,7 +1443,12 @@ describe('IndexPattern Data Source', () => { }, }; - const ast = FormBasedDatasource.toExpression(queryBaseState, 'first', indexPatterns) as Ast; + const ast = FormBasedDatasource.toExpression( + queryBaseState, + 'first', + indexPatterns, + 'testing-seed' + ) as Ast; const idMap = JSON.parse(ast.chain[2].arguments.idMap as unknown as string); @@ -1487,7 +1553,12 @@ describe('IndexPattern Data Source', () => { }, }; - const ast = FormBasedDatasource.toExpression(queryBaseState, 'first', indexPatterns) as Ast; + const ast = FormBasedDatasource.toExpression( + queryBaseState, + 'first', + indexPatterns, + 'testing-seed' + ) as Ast; // @ts-expect-error we can't isolate just the reference type expect(operationDefinitionMap.testReference.toExpression).toHaveBeenCalled(); expect(ast.chain[3]).toEqual('mock'); @@ -1520,7 +1591,12 @@ describe('IndexPattern Data Source', () => { }, }; - const ast = FormBasedDatasource.toExpression(queryBaseState, 'first', indexPatterns) as Ast; + const ast = FormBasedDatasource.toExpression( + queryBaseState, + 'first', + indexPatterns, + 'testing-seed' + ) as Ast; expect(JSON.parse(ast.chain[2].arguments.idMap[0] as string)).toEqual({ 'col-0-0': [ @@ -1607,7 +1683,12 @@ describe('IndexPattern Data Source', () => { }, }; - const ast = FormBasedDatasource.toExpression(queryBaseState, 'first', indexPatterns) as Ast; + const ast = FormBasedDatasource.toExpression( + queryBaseState, + 'first', + indexPatterns, + 'testing-seed' + ) as Ast; const chainLength = ast.chain.length; expect(ast.chain[chainLength - 2].arguments.name).toEqual(['math']); expect(ast.chain[chainLength - 1].arguments.id).toEqual(['formula']); @@ -1631,6 +1712,7 @@ describe('IndexPattern Data Source', () => { }, }, currentIndexPatternId: '1', + sampling: 1, }; expect(FormBasedDatasource.insertLayer(state, 'newLayer', ['link-to-id'])).toEqual({ ...state, @@ -1640,6 +1722,7 @@ describe('IndexPattern Data Source', () => { indexPatternId: '1', columnOrder: [], columns: {}, + sampling: 1, linkToLayers: ['link-to-id'], }, }, @@ -1665,12 +1748,75 @@ describe('IndexPattern Data Source', () => { currentIndexPatternId: '1', }; expect(FormBasedDatasource.removeLayer(state, 'first')).toEqual({ - ...state, + removedLayerIds: ['first'], + newState: { + ...state, + layers: { + second: { + indexPatternId: '2', + columnOrder: [], + columns: {}, + }, + }, + }, + }); + }); + + it('should remove linked layers', () => { + const state = { layers: { + first: { + indexPatternId: '1', + columnOrder: [], + columns: {}, + }, second: { indexPatternId: '2', columnOrder: [], columns: {}, + linkToLayers: ['first'], + }, + }, + currentIndexPatternId: '1', + }; + expect(FormBasedDatasource.removeLayer(state, 'first')).toEqual({ + removedLayerIds: ['first', 'second'], + newState: { + ...state, + layers: {}, + }, + }); + }); + }); + + describe('#clearLayer', () => { + it('should clear a layer', () => { + const state = { + layers: { + first: { + indexPatternId: '1', + columnOrder: ['some', 'order'], + columns: { + some: {} as GenericIndexPatternColumn, + columns: {} as GenericIndexPatternColumn, + }, + linkToLayers: ['some-layer'], + }, + }, + currentIndexPatternId: '1', + }; + expect(FormBasedDatasource.clearLayer(state, 'first')).toEqual({ + removedLayerIds: [], + newState: { + ...state, + layers: { + first: { + indexPatternId: '1', + columnOrder: [], + columns: {}, + linkToLayers: ['some-layer'], + sampling: 1, + }, }, }, }); @@ -1693,9 +1839,14 @@ describe('IndexPattern Data Source', () => { }, currentIndexPatternId: '1', }; - expect(FormBasedDatasource.removeLayer(state, 'first')).toEqual({ - ...state, - layers: {}, + expect(FormBasedDatasource.clearLayer(state, 'first')).toEqual({ + removedLayerIds: ['second'], + newState: { + ...state, + layers: { + first: { ...state.layers.first, linkToLayers: undefined, sampling: 1 }, + }, + }, }); }); }); diff --git a/x-pack/plugins/lens/public/datasources/form_based/form_based.tsx b/x-pack/plugins/lens/public/datasources/form_based/form_based.tsx index 62c77ec5225f..b863c69d7f7a 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/form_based.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/form_based.tsx @@ -36,6 +36,7 @@ import type { IndexPatternField, IndexPattern, IndexPatternRef, + DatasourceLayerSettingsProps, } from '../../types'; import { changeIndexPattern, @@ -96,6 +97,7 @@ import { getStateTimeShiftWarningMessages } from './time_shift_utils'; import { getPrecisionErrorWarningMessages } from './utils'; import { DOCUMENT_FIELD_NAME } from '../../../common/constants'; import { isColumnOfType } from './operations/definitions/helpers'; +import { LayerSettingsPanel } from './layer_settings'; import { FormBasedLayer } from '../..'; export type { OperationType, GenericIndexPatternColumn } from './operations'; export { deleteColumn } from './operations'; @@ -218,27 +220,47 @@ export function getFormBasedDatasource({ removeLayer(state: FormBasedPrivateState, layerId: string) { const newLayers = { ...state.layers }; delete newLayers[layerId]; + const removedLayerIds: string[] = [layerId]; // delete layers linked to this layer Object.keys(newLayers).forEach((id) => { const linkedLayers = newLayers[id]?.linkToLayers; if (linkedLayers && linkedLayers.includes(layerId)) { delete newLayers[id]; + removedLayerIds.push(id); } }); return { - ...state, - layers: newLayers, + removedLayerIds, + newState: { + ...state, + layers: newLayers, + }, }; }, clearLayer(state: FormBasedPrivateState, layerId: string) { + const newLayers = { ...state.layers }; + + const removedLayerIds: string[] = []; + // delete layers linked to this layer + Object.keys(newLayers).forEach((id) => { + const linkedLayers = newLayers[id]?.linkToLayers; + if (linkedLayers && linkedLayers.includes(layerId)) { + delete newLayers[id]; + removedLayerIds.push(id); + } + }); + return { - ...state, - layers: { - ...state.layers, - [layerId]: blankLayer(state.currentIndexPatternId, state.layers[layerId].linkToLayers), + removedLayerIds, + newState: { + ...state, + layers: { + ...newLayers, + [layerId]: blankLayer(state.currentIndexPatternId, state.layers[layerId].linkToLayers), + }, }, }; }, @@ -392,8 +414,34 @@ export function getFormBasedDatasource({ return fields; }, - toExpression: (state, layerId, indexPatterns) => - toExpression(state, layerId, indexPatterns, uiSettings), + toExpression: (state, layerId, indexPatterns, searchSessionId) => + toExpression(state, layerId, indexPatterns, uiSettings, searchSessionId), + + renderLayerSettings( + domElement: Element, + props: DatasourceLayerSettingsProps + ) { + render( + + + + + + + , + domElement + ); + }, renderDataPanel(domElement: Element, props: DatasourceDataPanelProps) { const { onChangeIndexPattern, ...otherProps } = props; @@ -569,6 +617,22 @@ export function getFormBasedDatasource({ getDropProps, onDrop, + getSupportedActionsForLayer(layerId, state, _, openLayerSettings) { + if (!openLayerSettings) { + return []; + } + return [ + { + displayName: i18n.translate('xpack.lens.indexPattern.layerSettingsAction', { + defaultMessage: 'Layer settings', + }), + execute: openLayerSettings, + icon: 'gear', + isCompatible: Boolean(state.layers[layerId]), + 'data-test-subj': 'lnsLayerSettings', + }, + ]; + }, getCustomWorkspaceRenderer: ( state: FormBasedPrivateState, @@ -931,5 +995,6 @@ function blankLayer(indexPatternId: string, linkToLayers?: string[]): FormBasedL linkToLayers, columns: {}, columnOrder: [], + sampling: 1, }; } diff --git a/x-pack/plugins/lens/public/datasources/form_based/form_based_suggestions.test.tsx b/x-pack/plugins/lens/public/datasources/form_based/form_based_suggestions.test.tsx index 2489659f0da5..eca0c032ee22 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/form_based_suggestions.test.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/form_based_suggestions.test.tsx @@ -176,6 +176,7 @@ function testInitialState(): FormBasedPrivateState { currentIndexPatternId: '1', layers: { first: { + sampling: 1, indexPatternId: '1', columnOrder: ['col1'], columns: { @@ -458,7 +459,7 @@ describe('IndexPattern Data Source suggestions', () => { }); describe('with a previous empty layer', () => { - function stateWithEmptyLayer() { + function stateWithEmptyLayer(): FormBasedPrivateState { const state = testInitialState(); return { ...state, @@ -761,6 +762,35 @@ describe('IndexPattern Data Source suggestions', () => { }) ); }); + + it('should inherit the sampling rate when generating new layer, if avaialble', () => { + const state = stateWithEmptyLayer(); + state.layers.previousLayer.sampling = 0.001; + const suggestions = getDatasourceSuggestionsForField( + state, + '1', + { + name: 'bytes', + displayName: 'bytes', + type: 'number', + aggregatable: true, + searchable: true, + }, + expectedIndexPatterns + ); + + expect(suggestions).toContainEqual( + expect.objectContaining({ + state: expect.objectContaining({ + layers: { + previousLayer: expect.objectContaining({ + sampling: 0.001, + }), + }, + }), + }) + ); + }); }); describe('suggesting extensions to non-empty tables', () => { diff --git a/x-pack/plugins/lens/public/datasources/form_based/form_based_suggestions.ts b/x-pack/plugins/lens/public/datasources/form_based/form_based_suggestions.ts index 52008f10bcde..81ce81bb4905 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/form_based_suggestions.ts +++ b/x-pack/plugins/lens/public/datasources/form_based/form_based_suggestions.ts @@ -529,6 +529,12 @@ function getEmptyLayerSuggestionsForField( newLayer = createNewLayerWithMetricAggregation(indexPattern, field); } + // copy the sampling rate to the new layer + // or just default to 1 + if (newLayer) { + newLayer.sampling = state.layers[layerId]?.sampling ?? 1; + } + const newLayerSuggestions = newLayer ? [ buildSuggestion({ diff --git a/x-pack/plugins/lens/public/datasources/form_based/layer_settings.tsx b/x-pack/plugins/lens/public/datasources/form_based/layer_settings.tsx new file mode 100644 index 000000000000..7d02ac98f23a --- /dev/null +++ b/x-pack/plugins/lens/public/datasources/form_based/layer_settings.tsx @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiFormRow, EuiRange, EuiBetaBadge } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import type { DatasourceLayerSettingsProps } from '../../types'; +import type { FormBasedPrivateState } from './types'; + +const samplingValue = [0.0001, 0.001, 0.01, 0.1, 1]; + +export function LayerSettingsPanel({ + state, + setState, + layerId, +}: DatasourceLayerSettingsProps) { + const samplingIndex = samplingValue.findIndex((v) => v === state.layers[layerId].sampling); + const currentSamplingIndex = samplingIndex > -1 ? samplingIndex : samplingValue.length - 1; + return ( + + {i18n.translate('xpack.lens.xyChart.randomSampling.label', { + defaultMessage: 'Sampling', + })}{' '} + + + } + > + { + setState({ + ...state, + layers: { + ...state.layers, + [layerId]: { + ...state.layers[layerId], + sampling: samplingValue[Number(e.currentTarget.value)], + }, + }, + }); + }} + showInput={false} + showRange={false} + showTicks + step={1} + min={0} + max={samplingValue.length - 1} + ticks={samplingValue.map((v, i) => ({ label: `${v}`, value: i }))} + /> + + ); +} diff --git a/x-pack/plugins/lens/public/datasources/form_based/layerpanel.tsx b/x-pack/plugins/lens/public/datasources/form_based/layerpanel.tsx index c18b79b28c58..349139cd41b2 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/layerpanel.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/layerpanel.tsx @@ -29,6 +29,13 @@ export function LayerPanel({ const notFoundTitleLabel = i18n.translate('xpack.lens.layerPanel.missingDataView', { defaultMessage: 'Data view not found', }); + const indexPatternRefs = dataViews.indexPatternRefs.map((ref) => { + const isPersisted = dataViews.indexPatterns[ref.id]?.isPersisted ?? true; + return { + ...ref, + isAdhoc: !isPersisted, + }; + }); return ( diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/count.tsx b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/count.tsx index 0d292b7a3a26..60c1a0cdf0f5 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/count.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/count.tsx @@ -235,7 +235,7 @@ export const countOperation: OperationDefinition({ quickFunctionDocumentation: i18n.translate( 'xpack.lens.indexPattern.avg.quickFunctionDescription', { - defaultMessage: 'The average value of a number field.', + defaultMessage: 'The mean value of a set of number fields.', } ), }); diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/ranges/ranges.test.tsx b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/ranges/ranges.test.tsx index d0dd9f645861..d238fd16b893 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/ranges/ranges.test.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/ranges/ranges.test.tsx @@ -187,7 +187,7 @@ describe('ranges', () => { } beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); beforeEach(() => { diff --git a/x-pack/plugins/lens/public/datasources/form_based/to_expression.ts b/x-pack/plugins/lens/public/datasources/form_based/to_expression.ts index fc77aa6520bd..365d80a3d828 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/to_expression.ts +++ b/x-pack/plugins/lens/public/datasources/form_based/to_expression.ts @@ -7,6 +7,7 @@ import type { IUiSettingsClient } from '@kbn/core/public'; import { partition, uniq } from 'lodash'; +import seedrandom from 'seedrandom'; import { AggFunctionsMapping, EsaggsExpressionFunctionDefinition, @@ -52,7 +53,8 @@ const updatePositionIndex = (currentId: string, newIndex: number) => { function getExpressionForLayer( layer: FormBasedLayer, indexPattern: IndexPattern, - uiSettings: IUiSettingsClient + uiSettings: IUiSettingsClient, + searchSessionId?: string ): ExpressionAstExpression | null { const { columnOrder } = layer; if (columnOrder.length === 0 || !indexPattern) { @@ -392,6 +394,8 @@ function getExpressionForLayer( metricsAtAllLevels: false, partialRows: false, timeFields: allDateHistogramFields, + probability: layer.sampling || 1, + samplerSeed: seedrandom(searchSessionId).int32(), }).toAst(), { type: 'function', @@ -441,13 +445,15 @@ export function toExpression( state: FormBasedPrivateState, layerId: string, indexPatterns: IndexPatternMap, - uiSettings: IUiSettingsClient + uiSettings: IUiSettingsClient, + searchSessionId?: string ) { if (state.layers[layerId]) { return getExpressionForLayer( state.layers[layerId], indexPatterns[state.layers[layerId].indexPatternId], - uiSettings + uiSettings, + searchSessionId ); } diff --git a/x-pack/plugins/lens/public/datasources/form_based/types.ts b/x-pack/plugins/lens/public/datasources/form_based/types.ts index 3c695d5064e3..0846c96d76dc 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/types.ts +++ b/x-pack/plugins/lens/public/datasources/form_based/types.ts @@ -54,6 +54,7 @@ export interface FormBasedLayer { linkToLayers?: string[]; // Partial columns represent the temporary invalid states incompleteColumns?: Record; + sampling?: number; } export interface FormBasedPersistedState { diff --git a/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.test.ts b/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.test.ts index cef1bfa96b8a..25bab766558e 100644 --- a/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.test.ts +++ b/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.test.ts @@ -217,21 +217,24 @@ describe('IndexPattern Data Source', () => { describe('#removeLayer', () => { it('should remove a layer', () => { expect(TextBasedDatasource.removeLayer(baseState, 'a')).toEqual({ - ...baseState, - layers: { - a: { - columns: [], - allColumns: [ - { - columnId: 'col1', - fieldName: 'Test 1', - meta: { - type: 'number', + removedLayerIds: ['a'], + newState: { + ...baseState, + layers: { + a: { + columns: [], + allColumns: [ + { + columnId: 'col1', + fieldName: 'Test 1', + meta: { + type: 'number', + }, }, - }, - ], - query: { sql: 'SELECT * FROM foo' }, - index: 'foo', + ], + query: { sql: 'SELECT * FROM foo' }, + index: 'foo', + }, }, }, }); diff --git a/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.tsx b/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.tsx index afe6368477cc..368036e3ebd5 100644 --- a/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.tsx +++ b/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.tsx @@ -281,18 +281,24 @@ export function getTextBasedDatasource({ }; return { - ...state, - layers: newLayers, - fieldList: state.fieldList, + removedLayerIds: [layerId], + newState: { + ...state, + layers: newLayers, + fieldList: state.fieldList, + }, }; }, clearLayer(state: TextBasedPrivateState, layerId: string) { return { - ...state, - layers: { - ...state.layers, - [layerId]: { ...state.layers[layerId], columns: [] }, + removedLayerIds: [], + newState: { + ...state, + layers: { + ...state.layers, + [layerId]: { ...state.layers[layerId], columns: [] }, + }, }, }; }, diff --git a/x-pack/plugins/lens/public/drag_drop/drag_drop.test.tsx b/x-pack/plugins/lens/public/drag_drop/drag_drop.test.tsx index af680fbc2716..b8c59712b096 100644 --- a/x-pack/plugins/lens/public/drag_drop/drag_drop.test.tsx +++ b/x-pack/plugins/lens/public/drag_drop/drag_drop.test.tsx @@ -19,7 +19,7 @@ import { import { act } from 'react-dom/test-utils'; import { DropType } from '../types'; -jest.useFakeTimers(); +jest.useFakeTimers('legacy'); const dataTransfer = { setData: jest.fn(), diff --git a/x-pack/plugins/lens/public/drag_drop/providers/providers.test.tsx b/x-pack/plugins/lens/public/drag_drop/providers/providers.test.tsx index a8312cc92745..8bbe30e29cb8 100644 --- a/x-pack/plugins/lens/public/drag_drop/providers/providers.test.tsx +++ b/x-pack/plugins/lens/public/drag_drop/providers/providers.test.tsx @@ -9,7 +9,7 @@ import React, { useContext } from 'react'; import { mount } from 'enzyme'; import { RootDragDropProvider, DragContext } from '.'; -jest.useFakeTimers(); +jest.useFakeTimers('legacy'); describe('RootDragDropProvider', () => { test('reuses contexts for each render', () => { diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_container.scss b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_container.scss index b8ae7fa51519..7b74d0e96641 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_container.scss +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_container.scss @@ -1,52 +1,3 @@ -@import '@elastic/eui/src/components/flyout/variables'; -@import '@elastic/eui/src/components/flyout/mixins'; - -.lnsDimensionContainer { - // Use the EuiFlyout style - @include euiFlyout; - // But with custom positioning to keep it within the sidebar contents - animation: euiFlyout $euiAnimSpeedNormal $euiAnimSlightResistance; - left: 0; - max-width: none !important; - z-index: $euiZContentMenu; - - @include euiBreakpoint('l', 'xl') { - height: 100% !important; - position: absolute; - top: 0 !important; - } - - .lnsFrameLayout__sidebar-isFullscreen & { - border-left: $euiBorderThin; // Force border regardless of theme in fullscreen - box-shadow: none; - } -} - -.lnsDimensionContainer__header { - padding: $euiSize; - - .lnsFrameLayout__sidebar-isFullscreen & { - display: none; - } -} - -.lnsDimensionContainer__content { - @include euiYScroll; - flex: 1; -} - -.lnsDimensionContainer__footer { - padding: $euiSize; - - .lnsFrameLayout__sidebar-isFullscreen & { - display: none; - } -} - -.lnsBody--overflowHidden { - overflow: hidden; -} - .lnsLayerAddButton:hover { text-decoration: none; diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_container.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_container.tsx index 9d71e74eff47..13ba032f9b90 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_container.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_container.tsx @@ -7,44 +7,12 @@ import './dimension_container.scss'; -import React, { useState, useEffect, useCallback } from 'react'; -import { - EuiFlyoutHeader, - EuiFlyoutFooter, - EuiTitle, - EuiButtonIcon, - EuiButtonEmpty, - EuiFlexGroup, - EuiFlexItem, - EuiFocusTrap, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { DONT_CLOSE_DIMENSION_CONTAINER_ON_CLICK_CLASS } from '../../../utils'; - -function fromExcludedClickTarget(event: Event) { - for ( - let node: HTMLElement | null = event.target as HTMLElement; - node !== null; - node = node!.parentElement - ) { - if ( - node.classList!.contains(DONT_CLOSE_DIMENSION_CONTAINER_ON_CLICK_CLASS) || - node.classList!.contains('euiBody-hasPortalContent') || - node.getAttribute('data-euiportal') === 'true' - ) { - return true; - } - } - return false; -} +import React from 'react'; +import { FlyoutContainer } from './flyout_container'; export function DimensionContainer({ - isOpen, - groupLabel, - handleClose, panel, - isFullscreen, - panelRef, + ...props }: { isOpen: boolean; handleClose: () => boolean; @@ -53,107 +21,5 @@ export function DimensionContainer({ isFullscreen: boolean; panelRef: (el: HTMLDivElement) => void; }) { - const [focusTrapIsEnabled, setFocusTrapIsEnabled] = useState(false); - - const closeFlyout = useCallback(() => { - const canClose = handleClose(); - if (canClose) { - setFocusTrapIsEnabled(false); - } - return canClose; - }, [handleClose]); - - useEffect(() => { - document.body.classList.toggle('lnsBody--overflowHidden', isOpen); - return () => { - if (isOpen) { - setFocusTrapIsEnabled(false); - } - document.body.classList.remove('lnsBody--overflowHidden'); - }; - }, [isOpen]); - - if (!isOpen) { - return null; - } - - return ( -

- { - if (isFullscreen || fromExcludedClickTarget(event)) { - return; - } - closeFlyout(); - }} - onEscapeKey={closeFlyout} - > -
{ - if (isOpen) { - // EuiFocusTrap interferes with animating elements with absolute position: - // running this onAnimationEnd, otherwise the flyout pushes content when animating - setFocusTrapIsEnabled(true); - } - }} - > - - - - -

- - {i18n.translate('xpack.lens.configure.configurePanelTitle', { - defaultMessage: '{groupLabel}', - values: { - groupLabel, - }, - })} - -

-
-
- - - - -
-
- -
{panel}
- - - - {i18n.translate('xpack.lens.dimensionContainer.close', { - defaultMessage: 'Close', - })} - - -
-
-
- ); + return {panel}; } diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/flyout_container.scss b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/flyout_container.scss new file mode 100644 index 000000000000..b08eb6281fa0 --- /dev/null +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/flyout_container.scss @@ -0,0 +1,48 @@ +@import '@elastic/eui/src/components/flyout/variables'; +@import '@elastic/eui/src/components/flyout/mixins'; + +.lnsDimensionContainer { + // Use the EuiFlyout style + @include euiFlyout; + // But with custom positioning to keep it within the sidebar contents + animation: euiFlyout $euiAnimSpeedNormal $euiAnimSlightResistance; + left: 0; + max-width: none !important; + z-index: $euiZContentMenu; + + @include euiBreakpoint('l', 'xl') { + height: 100% !important; + position: absolute; + top: 0 !important; + } + + .lnsFrameLayout__sidebar-isFullscreen & { + border-left: $euiBorderThin; // Force border regardless of theme in fullscreen + box-shadow: none; + } +} + +.lnsDimensionContainer__header { + padding: $euiSize; + + .lnsFrameLayout__sidebar-isFullscreen & { + display: none; + } +} + +.lnsDimensionContainer__content { + @include euiYScroll; + flex: 1; +} + +.lnsDimensionContainer__footer { + padding: $euiSize; + + .lnsFrameLayout__sidebar-isFullscreen & { + display: none; + } +} + +.lnsBody--overflowHidden { + overflow: hidden; +} diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/flyout_container.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/flyout_container.tsx new file mode 100644 index 000000000000..041f59df332f --- /dev/null +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/flyout_container.tsx @@ -0,0 +1,157 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import './flyout_container.scss'; + +import React, { useState, useEffect, useCallback } from 'react'; +import { + EuiFlyoutHeader, + EuiFlyoutFooter, + EuiTitle, + EuiButtonIcon, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiFocusTrap, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { DONT_CLOSE_DIMENSION_CONTAINER_ON_CLICK_CLASS } from '../../../utils'; + +function fromExcludedClickTarget(event: Event) { + for ( + let node: HTMLElement | null = event.target as HTMLElement; + node !== null; + node = node!.parentElement + ) { + if ( + node.classList!.contains(DONT_CLOSE_DIMENSION_CONTAINER_ON_CLICK_CLASS) || + node.classList!.contains('euiBody-hasPortalContent') || + node.getAttribute('data-euiportal') === 'true' + ) { + return true; + } + } + return false; +} + +export function FlyoutContainer({ + isOpen, + groupLabel, + handleClose, + isFullscreen, + panelRef, + children, +}: { + isOpen: boolean; + handleClose: () => boolean; + children: React.ReactElement | null; + groupLabel: string; + isFullscreen: boolean; + panelRef: (el: HTMLDivElement) => void; +}) { + const [focusTrapIsEnabled, setFocusTrapIsEnabled] = useState(false); + + const closeFlyout = useCallback(() => { + const canClose = handleClose(); + if (canClose) { + setFocusTrapIsEnabled(false); + } + return canClose; + }, [handleClose]); + + useEffect(() => { + document.body.classList.toggle('lnsBody--overflowHidden', isOpen); + return () => { + if (isOpen) { + setFocusTrapIsEnabled(false); + } + document.body.classList.remove('lnsBody--overflowHidden'); + }; + }, [isOpen]); + + if (!isOpen) { + return null; + } + + return ( +
+ { + if (isFullscreen || fromExcludedClickTarget(event)) { + return; + } + closeFlyout(); + }} + onEscapeKey={closeFlyout} + > +
{ + if (isOpen) { + // EuiFocusTrap interferes with animating elements with absolute position: + // running this onAnimationEnd, otherwise the flyout pushes content when animating + setFocusTrapIsEnabled(true); + } + }} + > + + + + +

+ {i18n.translate('xpack.lens.configure.configurePanelTitle', { + defaultMessage: '{groupLabel}', + values: { + groupLabel, + }, + })} +

+
+
+ + + + +
+
+ +
{children}
+ + + + {i18n.translate('xpack.lens.dimensionContainer.close', { + defaultMessage: 'Close', + })} + + +
+
+
+ ); +} diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_actions/layer_actions.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_actions/layer_actions.tsx index bc1c41caa650..9c32a24eac4d 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_actions/layer_actions.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_actions/layer_actions.tsx @@ -104,6 +104,9 @@ const InContextMenuActions = (props: LayerActionsProps) => { closePopover={closePopover} panelPaddingSize="none" anchorPosition="downLeft" + panelProps={{ + 'data-test-subj': 'lnsLayerActionsMenu', + }} > ( initialActiveDimensionState ); + const [isPanelSettingsOpen, setPanelSettingsOpen] = useState(false); + const [hideTooltip, setHideTooltip] = useState(false); const { @@ -120,6 +123,7 @@ export function LayerPanel( }, [activeVisualization.id]); const panelRef = useRef(null); + const settingsPanelRef = useRef(null); const registerLayerRef = useCallback( (el) => registerNewLayerRef(layerId, el), [layerId, registerNewLayerRef] @@ -316,7 +320,14 @@ export function LayerPanel( ...(activeVisualization.getSupportedActionsForLayer?.( layerId, visualizationState, - updateVisualization + updateVisualization, + () => setPanelSettingsOpen(true) + ) || []), + ...(layerDatasource?.getSupportedActionsForLayer?.( + layerId, + layerDatasourceState, + (newState) => updateDatasource(datasourceId, newState), + () => setPanelSettingsOpen(true) ) || []), ...getSharedActions({ activeVisualization, @@ -332,12 +343,16 @@ export function LayerPanel( [ activeVisualization, core, + datasourceId, isOnlyLayer, isTextBasedLanguage, + layerDatasource, + layerDatasourceState, layerId, layerIndex, onCloneLayer, onRemoveLayer, + updateDatasource, updateVisualization, visualizationState, ] @@ -624,7 +639,42 @@ export function LayerPanel( })} - + {(layerDatasource?.renderLayerSettings || activeVisualization?.renderLayerSettings) && ( + (settingsPanelRef.current = el)} + isOpen={isPanelSettingsOpen} + isFullscreen={false} + groupLabel={i18n.translate('xpack.lens.editorFrame.layerSettingsTitle', { + defaultMessage: 'Layer settings', + })} + handleClose={() => { + // update the current layer settings + setPanelSettingsOpen(false); + return true; + }} + > +
+
+ {layerDatasource?.renderLayerSettings && ( + + )} + {activeVisualization?.renderLayerSettings && ( + + )} +
+
+
+ )} (panelRef.current = el)} isOpen={isDimensionPanelOpen} diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/expression_helpers.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/expression_helpers.ts index c9b0358b81a0..aee10196d515 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/expression_helpers.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/expression_helpers.ts @@ -11,7 +11,8 @@ import { Visualization, DatasourceMap, DatasourceLayers, IndexPatternMap } from export function getDatasourceExpressionsByLayers( datasourceMap: DatasourceMap, datasourceStates: DatasourceStates, - indexPatterns: IndexPatternMap + indexPatterns: IndexPatternMap, + searchSessionId?: string ): null | Record { const datasourceExpressions: Array<[string, Ast | string]> = []; @@ -24,7 +25,7 @@ export function getDatasourceExpressionsByLayers( const layers = datasource.getLayers(state); layers.forEach((layerId) => { - const result = datasource.toExpression(state, layerId, indexPatterns); + const result = datasource.toExpression(state, layerId, indexPatterns, searchSessionId); if (result) { datasourceExpressions.push([layerId, result]); } @@ -53,6 +54,7 @@ export function buildExpression({ title, description, indexPatterns, + searchSessionId, }: { title?: string; description?: string; @@ -62,6 +64,7 @@ export function buildExpression({ datasourceStates: DatasourceStates; datasourceLayers: DatasourceLayers; indexPatterns: IndexPatternMap; + searchSessionId?: string; }): Ast | null { if (visualization === null) { return null; @@ -70,7 +73,8 @@ export function buildExpression({ const datasourceExpressionsByLayers = getDatasourceExpressionsByLayers( datasourceMap, datasourceStates, - indexPatterns + indexPatterns, + searchSessionId ); const visualizationExpression = visualization.toExpression( diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.test.tsx index e6b762911d4d..276b56cc69ff 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.test.tsx @@ -494,8 +494,8 @@ describe('chart_switch', () => { switchTo('visB', instance); expect(datasourceMap.testDatasource.removeLayer).toHaveBeenCalledWith({}, 'a'); - expect(datasourceMap.testDatasource.removeLayer).toHaveBeenCalledWith(undefined, 'b'); - expect(datasourceMap.testDatasource.removeLayer).toHaveBeenCalledWith(undefined, 'c'); + expect(datasourceMap.testDatasource.removeLayer).toHaveBeenCalledWith({}, 'b'); + expect(datasourceMap.testDatasource.removeLayer).toHaveBeenCalledWith({}, 'c'); expect(visualizationMap.visB.getSuggestions).toHaveBeenCalledWith( expect.objectContaining({ keptLayerIds: ['a'], diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx index 38f62b6e928b..2f2003970171 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx @@ -162,6 +162,7 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({ const changesApplied = useLensSelector(selectChangesApplied); const triggerApply = useLensSelector(selectTriggerApplyChanges); const datasourceLayers = useLensSelector((state) => selectDatasourceLayers(state, datasourceMap)); + const searchSessionId = useLensSelector(selectSearchSessionId); const [localState, setLocalState] = useState({ expressionBuildError: undefined, @@ -317,6 +318,7 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({ datasourceStates, datasourceLayers, indexPatterns: dataViews.indexPatterns, + searchSessionId, }); if (ast) { @@ -349,16 +351,17 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({ })); } }, [ + configurationValidationError?.length, + missingRefsErrors.length, + unknownVisError, activeVisualization, visualization.state, + visualization.activeId, datasourceMap, datasourceStates, datasourceLayers, - configurationValidationError?.length, - missingRefsErrors.length, - unknownVisError, - visualization.activeId, dataViews.indexPatterns, + searchSessionId, ]); useEffect(() => { diff --git a/x-pack/plugins/lens/public/mocks/datasource_mock.ts b/x-pack/plugins/lens/public/mocks/datasource_mock.ts index cfb93882559e..986a753a1a4a 100644 --- a/x-pack/plugins/lens/public/mocks/datasource_mock.ts +++ b/x-pack/plugins/lens/public/mocks/datasource_mock.ts @@ -26,7 +26,7 @@ export function createMockDatasource(id: string): DatasourceMock { return { id: 'testDatasource', - clearLayer: jest.fn((state, _layerId) => state), + clearLayer: jest.fn((state, _layerId) => ({ newState: state, removedLayerIds: [] })), getDatasourceSuggestionsForField: jest.fn((_state, _item, filterFn, _indexPatterns) => []), getDatasourceSuggestionsForVisualizeField: jest.fn( (_state, _indexpatternId, _fieldName, _indexPatterns) => [] @@ -44,7 +44,7 @@ export function createMockDatasource(id: string): DatasourceMock { renderLayerPanel: jest.fn(), toExpression: jest.fn((_frame, _state, _indexPatterns) => null), insertLayer: jest.fn((_state, _newLayerId) => ({})), - removeLayer: jest.fn((_state, _layerId) => {}), + removeLayer: jest.fn((state, layerId) => ({ newState: state, removedLayerIds: [layerId] })), cloneLayer: jest.fn((_state, _layerId, _newLayerId, getNewId) => {}), removeColumn: jest.fn((props) => {}), getLayers: jest.fn((_state) => []), diff --git a/x-pack/plugins/lens/public/state_management/lens_slice.test.ts b/x-pack/plugins/lens/public/state_management/lens_slice.test.ts index 934fc0854b6e..df9d42015632 100644 --- a/x-pack/plugins/lens/public/state_management/lens_slice.test.ts +++ b/x-pack/plugins/lens/public/state_management/lens_slice.test.ts @@ -39,6 +39,7 @@ describe('lensSlice', () => { let store: EnhancedStore<{ lens: LensAppState }>; beforeEach(() => { store = makeLensStore({}).store; + jest.clearAllMocks(); }); const customQuery = { query: 'custom' } as Query; @@ -275,17 +276,21 @@ describe('lensSlice', () => { return { id: datasourceId, getPublicAPI: () => ({ - datasourceId: 'testDatasource', + datasourceId, getOperationForColumnId: jest.fn(), getTableSpec: jest.fn(), }), getLayers: () => ['layer1'], - clearLayer: (layerIds: unknown, layerId: string) => - (layerIds as string[]).map((id: string) => + clearLayer: (layerIds: unknown, layerId: string) => ({ + removedLayerIds: [], + newState: (layerIds as string[]).map((id: string) => id === layerId ? `${datasourceId}_clear_${layerId}` : id ), - removeLayer: (layerIds: unknown, layerId: string) => - (layerIds as string[]).filter((id: string) => id !== layerId), + }), + removeLayer: (layerIds: unknown, layerId: string) => ({ + newState: (layerIds as string[]).filter((id: string) => id !== layerId), + removedLayerIds: [layerId], + }), insertLayer: (layerIds: unknown, layerId: string, layersToLinkTo: string[]) => [ ...(layerIds as string[]), layerId, @@ -317,8 +322,9 @@ describe('lensSlice', () => { (layerIds as string[]).map((id: string) => id === layerId ? `vis_clear_${layerId}` : id ), - removeLayer: (layerIds: unknown, layerId: string) => - (layerIds as string[]).filter((id: string) => id !== layerId), + removeLayer: jest.fn((layerIds: unknown, layerId: string) => + (layerIds as string[]).filter((id: string) => id !== layerId) + ), getLayerIds: (layerIds: unknown) => layerIds as string[], getLayersToLinkTo: (state, newLayerId) => ['linked-layer-id'], appendLayer: (layerIds: unknown, layerId: string) => [...(layerIds as string[]), layerId], @@ -482,6 +488,54 @@ describe('lensSlice', () => { expect(state.datasourceStates.testDatasource2.state).toEqual(['layer2']); expect(state.stagedPreview).not.toBeDefined(); }); + + it('removeLayer: should remove all layers from visualization that were removed by datasource', () => { + const removedLayerId = 'other-removed-layer'; + + const testDatasource3 = testDatasource('testDatasource3'); + testDatasource3.removeLayer = (layerIds: unknown, layerId: string) => ({ + newState: (layerIds as string[]).filter((id: string) => id !== layerId), + removedLayerIds: [layerId, removedLayerId], + }); + + const localStore = makeLensStore({ + preloadedState: { + activeDatasourceId: 'testDatasource', + datasourceStates: { + ...datasourceStates, + testDatasource3: { + isLoading: false, + state: [], + }, + }, + visualization: { + activeId: activeVisId, + state: ['layer1', 'layer2'], + }, + stagedPreview: { + visualization: { + activeId: activeVisId, + state: ['layer1', 'layer2'], + }, + datasourceStates, + }, + }, + storeDeps: mockStoreDeps({ + visualizationMap: visualizationMap as unknown as VisualizationMap, + datasourceMap: { ...datasourceMap, testDatasource3 } as unknown as DatasourceMap, + }), + }).store; + + localStore.dispatch( + removeOrClearLayer({ + visualizationId: 'testVis', + layerId: 'layer1', + layerIds: ['layer1', 'layer2'], + }) + ); + + expect(visualizationMap[activeVisId].removeLayer).toHaveBeenCalledTimes(2); + }); }); describe('removing a dimension', () => { @@ -546,8 +600,6 @@ describe('lensSlice', () => { datasourceMap: datasourceMap as unknown as DatasourceMap, }), }).store; - - jest.clearAllMocks(); }); it('removes a dimension', () => { diff --git a/x-pack/plugins/lens/public/state_management/lens_slice.ts b/x-pack/plugins/lens/public/state_management/lens_slice.ts index b90c5bc965a6..3f3490906f88 100644 --- a/x-pack/plugins/lens/public/state_management/lens_slice.ts +++ b/x-pack/plugins/lens/public/state_management/lens_slice.ts @@ -7,7 +7,7 @@ import { createAction, createReducer, current, PayloadAction } from '@reduxjs/toolkit'; import { VisualizeFieldContext } from '@kbn/ui-actions-plugin/public'; -import { mapValues } from 'lodash'; +import { mapValues, uniq } from 'lodash'; import { Query } from '@kbn/es-query'; import { History } from 'history'; import { LensEmbeddableInput } from '..'; @@ -400,16 +400,23 @@ export const makeLensReducer = (storeDeps: LensStoreDeps) => { layerIds.length ) === 'clear'; + let removedLayerIds: string[] = []; + state.datasourceStates = mapValues( state.datasourceStates, (datasourceState, datasourceId) => { const datasource = datasourceMap[datasourceId!]; + + const { newState, removedLayerIds: removedLayerIdsForThisDatasource } = isOnlyLayer + ? datasource.clearLayer(datasourceState.state, layerId) + : datasource.removeLayer(datasourceState.state, layerId); + + removedLayerIds = [...removedLayerIds, ...removedLayerIdsForThisDatasource]; + return { ...datasourceState, ...(datasourceId === state.activeDatasourceId && { - state: isOnlyLayer - ? datasource.clearLayer(datasourceState.state, layerId) - : datasource.removeLayer(datasourceState.state, layerId), + state: newState, }), }; } @@ -419,10 +426,22 @@ export const makeLensReducer = (storeDeps: LensStoreDeps) => { const currentDataViewsId = activeDataSource.getUsedDataView( state.datasourceStates[state.activeDatasourceId!].state ); - state.visualization.state = - isOnlyLayer || !activeVisualization.removeLayer - ? activeVisualization.clearLayer(state.visualization.state, layerId, currentDataViewsId) - : activeVisualization.removeLayer(state.visualization.state, layerId); + + if (isOnlyLayer || !activeVisualization.removeLayer) { + state.visualization.state = activeVisualization.clearLayer( + state.visualization.state, + layerId, + currentDataViewsId + ); + } + + uniq(removedLayerIds).forEach( + (removedId) => + (state.visualization.state = activeVisualization.removeLayer?.( + state.visualization.state, + removedId + )) + ); }, [changeIndexPattern.type]: ( state, @@ -977,9 +996,12 @@ export const makeLensReducer = (storeDeps: LensStoreDeps) => { ); }) ?? []; if (layerDatasourceId) { - state.datasourceStates[layerDatasourceId].state = datasourceMap[ - layerDatasourceId - ].removeLayer(current(state).datasourceStates[layerDatasourceId].state, layerId); + const { newState } = datasourceMap[layerDatasourceId].removeLayer( + current(state).datasourceStates[layerDatasourceId].state, + layerId + ); + state.datasourceStates[layerDatasourceId].state = newState; + // TODO - call removeLayer for any extra (linked) layers removed by the datasource } }); }, diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index e01c35f3457d..94a0589f78b0 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -265,8 +265,8 @@ export interface Datasource { insertLayer: (state: T, newLayerId: string, linkToLayers?: string[]) => T; createEmptyLayer: (indexPatternId: string) => T; - removeLayer: (state: T, layerId: string) => T; - clearLayer: (state: T, layerId: string) => T; + removeLayer: (state: T, layerId: string) => { newState: T; removedLayerIds: string[] }; + clearLayer: (state: T, layerId: string) => { newState: T; removedLayerIds: string[] }; cloneLayer: ( state: T, layerId: string, @@ -301,6 +301,10 @@ export interface Datasource { }) => T; getSelectedFields?: (state: T) => string[]; + renderLayerSettings?: ( + domElement: Element, + props: DatasourceLayerSettingsProps + ) => ((cleanupElement: Element) => void) | void; renderDataPanel: ( domElement: Element, props: DatasourceDataPanelProps @@ -360,7 +364,8 @@ export interface Datasource { toExpression: ( state: T, layerId: string, - indexPatterns: IndexPatternMap + indexPatterns: IndexPatternMap, + searchSessionId?: string ) => ExpressionAstExpression | string | null; getDatasourceSuggestionsForField: ( @@ -458,6 +463,13 @@ export interface Datasource { * Get all the used DataViews from state */ getUsedDataViews: (state: T) => string[]; + + getSupportedActionsForLayer?: ( + layerId: string, + state: T, + setState: StateSetter, + openLayerSettings?: () => void + ) => LayerAction[]; } export interface DatasourceFixAction { @@ -509,6 +521,12 @@ export interface DatasourcePublicAPI { hasDefaultTimeField: () => boolean; } +export interface DatasourceLayerSettingsProps { + layerId: string; + state: T; + setState: StateSetter; +} + export interface DatasourceDataPanelProps { state: T; dragDropContext: DragContextState; @@ -715,6 +733,11 @@ export interface VisualizationToolbarProps { state: T; } +export type VisualizationLayerSettingsProps = VisualizationConfigProps & { + setState(newState: T | ((currState: T) => T)): void; + panelRef: MutableRefObject; +}; + export type VisualizationDimensionEditorProps = VisualizationConfigProps & { groupId: string; accessor: string; @@ -1010,7 +1033,8 @@ export interface Visualization { getSupportedActionsForLayer?: ( layerId: string, state: T, - setState: StateSetter + setState: StateSetter, + openLayerSettings?: () => void ) => LayerAction[]; /** returns the type string of the given layer */ getLayerType: (layerId: string, state?: T) => LayerType | undefined; @@ -1090,6 +1114,11 @@ export interface Visualization { dropProps: GetDropPropsArgs ) => { dropTypes: DropType[]; nextLabel?: string } | undefined; + renderLayerSettings?: ( + domElement: Element, + props: VisualizationLayerSettingsProps + ) => ((cleanupElement: Element) => void) | void; + /** * Additional editor that gets rendered inside the dimension popover. * This can be used to configure dimension-specific options diff --git a/x-pack/plugins/lens/public/visualizations/metric/dimension_editor.test.tsx b/x-pack/plugins/lens/public/visualizations/metric/dimension_editor.test.tsx index 9826e60a83bc..b45eef4ec379 100644 --- a/x-pack/plugins/lens/public/visualizations/metric/dimension_editor.test.tsx +++ b/x-pack/plugins/lens/public/visualizations/metric/dimension_editor.test.tsx @@ -12,7 +12,11 @@ import { OperationDescriptor, VisualizationDimensionEditorProps } from '../../ty import { CustomPaletteParams, PaletteOutput, PaletteRegistry } from '@kbn/coloring'; import { MetricVisualizationState } from './visualization'; -import { DimensionEditor, SupportingVisType } from './dimension_editor'; +import { + DimensionEditor, + DimensionEditorAdditionalSection, + SupportingVisType, +} from './dimension_editor'; import { HTMLAttributes, mount, ReactWrapper, shallow } from 'enzyme'; import { CollapseSetting } from '../../shared_components/collapse_setting'; import { EuiButtonGroup, EuiColorPicker, PropsOf } from '@elastic/eui'; @@ -154,42 +158,6 @@ describe('dimension editor', () => { this.colorPicker.props().onChange!(color, {} as EuiColorPickerOutput); }); } - - private get supportingVisButtonGroup() { - return this._wrapper.find( - 'EuiButtonGroup[data-test-subj="lnsMetric_supporting_visualization_buttons"]' - ) as unknown as ReactWrapper>; - } - - public get currentSupportingVis() { - return this.supportingVisButtonGroup - .props() - .idSelected?.split('--')[1] as SupportingVisType; - } - - public isDisabled(type: SupportingVisType) { - return this.supportingVisButtonGroup.props().options.find(({ id }) => id.includes(type)) - ?.isDisabled; - } - - public setSupportingVis(type: SupportingVisType) { - this.supportingVisButtonGroup.props().onChange(`some-id--${type}`); - } - - private get progressDirectionControl() { - return this._wrapper.find( - 'EuiButtonGroup[data-test-subj="lnsMetric_progress_direction_buttons"]' - ) as unknown as ReactWrapper>; - } - - public get progressDirectionShowing() { - return this.progressDirectionControl.exists(); - } - - public setProgressDirection(direction: LayoutDirection) { - this.progressDirectionControl.props().onChange(direction); - this._wrapper.update(); - } } const mockSetState = jest.fn(); @@ -266,144 +234,6 @@ describe('dimension editor', () => { `); }); }); - - describe('supporting visualizations', () => { - const stateWOTrend = { - ...metricAccessorState, - trendlineLayerId: undefined, - }; - - describe('reflecting visualization state', () => { - it('should select the correct button', () => { - expect( - getHarnessWithState({ ...stateWOTrend, showBar: false, maxAccessor: undefined }) - .currentSupportingVis - ).toBe('none'); - expect( - getHarnessWithState({ ...stateWOTrend, showBar: true }).currentSupportingVis - ).toBe('bar'); - expect( - getHarnessWithState(metricAccessorState).currentSupportingVis - ).toBe('trendline'); - }); - - it('should disable bar when no max dimension', () => { - expect( - getHarnessWithState({ - ...stateWOTrend, - showBar: false, - maxAccessor: 'something', - }).isDisabled('bar') - ).toBeFalsy(); - expect( - getHarnessWithState({ - ...stateWOTrend, - showBar: false, - maxAccessor: undefined, - }).isDisabled('bar') - ).toBeTruthy(); - }); - - it('should disable trendline when no default time field', () => { - expect( - getHarnessWithState(stateWOTrend, { - hasDefaultTimeField: () => false, - getOperationForColumnId: (id) => ({} as OperationDescriptor), - } as DatasourcePublicAPI).isDisabled('trendline') - ).toBeTruthy(); - expect( - getHarnessWithState(stateWOTrend, { - hasDefaultTimeField: () => true, - getOperationForColumnId: (id) => ({} as OperationDescriptor), - } as DatasourcePublicAPI).isDisabled('trendline') - ).toBeFalsy(); - }); - }); - - it('should disable trendline when a metric dimension has a reduced time range', () => { - expect( - getHarnessWithState(stateWOTrend, { - hasDefaultTimeField: () => true, - getOperationForColumnId: (id) => - ({ hasReducedTimeRange: id === stateWOTrend.metricAccessor } as OperationDescriptor), - } as DatasourcePublicAPI).isDisabled('trendline') - ).toBeTruthy(); - expect( - getHarnessWithState(stateWOTrend, { - hasDefaultTimeField: () => true, - getOperationForColumnId: (id) => - ({ - hasReducedTimeRange: id === stateWOTrend.secondaryMetricAccessor, - } as OperationDescriptor), - } as DatasourcePublicAPI).isDisabled('trendline') - ).toBeTruthy(); - }); - - describe('responding to buttons', () => { - it('enables trendline', () => { - getHarnessWithState(stateWOTrend).setSupportingVis('trendline'); - - expect(mockSetState).toHaveBeenCalledWith({ ...stateWOTrend, showBar: false }); - expect(props.addLayer).toHaveBeenCalledWith('metricTrendline'); - - expectCalledBefore(mockSetState, props.addLayer as jest.Mock); - }); - - it('enables bar', () => { - getHarnessWithState(metricAccessorState).setSupportingVis('bar'); - - expect(mockSetState).toHaveBeenCalledWith({ ...metricAccessorState, showBar: true }); - expect(props.removeLayer).toHaveBeenCalledWith(metricAccessorState.trendlineLayerId); - - expectCalledBefore(mockSetState, props.removeLayer as jest.Mock); - }); - - it('selects none from bar', () => { - getHarnessWithState(stateWOTrend).setSupportingVis('none'); - - expect(mockSetState).toHaveBeenCalledWith({ ...stateWOTrend, showBar: false }); - expect(props.removeLayer).not.toHaveBeenCalled(); - }); - - it('selects none from trendline', () => { - getHarnessWithState(metricAccessorState).setSupportingVis('none'); - - expect(mockSetState).toHaveBeenCalledWith({ ...metricAccessorState, showBar: false }); - expect(props.removeLayer).toHaveBeenCalledWith(metricAccessorState.trendlineLayerId); - - expectCalledBefore(mockSetState, props.removeLayer as jest.Mock); - }); - }); - - describe('progress bar direction controls', () => { - it('hides direction controls if bar not showing', () => { - expect( - getHarnessWithState({ ...stateWOTrend, showBar: false }).progressDirectionShowing - ).toBeFalsy(); - }); - - it('toggles progress direction', () => { - const harness = getHarnessWithState(metricAccessorState); - - expect(harness.progressDirectionShowing).toBeTruthy(); - expect(harness.currentState.progressDirection).toBe('vertical'); - - harness.setProgressDirection('horizontal'); - harness.setProgressDirection('vertical'); - harness.setProgressDirection('horizontal'); - - expect(mockSetState).toHaveBeenCalledTimes(3); - expect(mockSetState.mock.calls.map((args) => args[0].progressDirection)) - .toMatchInlineSnapshot(` - Array [ - "horizontal", - "vertical", - "horizontal", - ] - `); - }); - }); - }); }); describe('secondary metric dimension', () => { @@ -628,4 +458,235 @@ describe('dimension editor', () => { `); }); }); + + describe('additional section', () => { + const accessor = 'primary-metric-col-id'; + const metricAccessorState = { ...fullState, metricAccessor: accessor }; + + class Harness { + public _wrapper; + + constructor( + wrapper: ReactWrapper> + ) { + this._wrapper = wrapper; + } + + private get rootComponent() { + return this._wrapper.find(DimensionEditorAdditionalSection); + } + + public get currentState() { + return this.rootComponent.props().state; + } + + private get supportingVisButtonGroup() { + return this._wrapper.find( + 'EuiButtonGroup[data-test-subj="lnsMetric_supporting_visualization_buttons"]' + ) as unknown as ReactWrapper>; + } + + public get currentSupportingVis() { + return this.supportingVisButtonGroup + .props() + .idSelected?.split('--')[1] as SupportingVisType; + } + + public isDisabled(type: SupportingVisType) { + return this.supportingVisButtonGroup.props().options.find(({ id }) => id.includes(type)) + ?.isDisabled; + } + + public setSupportingVis(type: SupportingVisType) { + this.supportingVisButtonGroup.props().onChange(`some-id--${type}`); + } + + private get progressDirectionControl() { + return this._wrapper.find( + 'EuiButtonGroup[data-test-subj="lnsMetric_progress_direction_buttons"]' + ) as unknown as ReactWrapper>; + } + + public get progressDirectionShowing() { + return this.progressDirectionControl.exists(); + } + + public setProgressDirection(direction: LayoutDirection) { + this.progressDirectionControl.props().onChange(direction); + this._wrapper.update(); + } + } + + const mockSetState = jest.fn(); + + const getHarnessWithState = (state: MetricVisualizationState, datasource = props.datasource) => + new Harness( + mountWithIntl( + + ) + ); + + it.each([ + { name: 'secondary metric', accessor: metricAccessorState.secondaryMetricAccessor }, + { name: 'max', accessor: metricAccessorState.maxAccessor }, + { name: 'break down by', accessor: metricAccessorState.breakdownByAccessor }, + ])('doesnt show for the following dimension: %s', ({ accessor: testAccessor }) => { + expect( + shallow( + + ).isEmptyRender() + ).toBeTruthy(); + }); + + describe('supporting visualizations', () => { + const stateWOTrend = { + ...metricAccessorState, + trendlineLayerId: undefined, + }; + + describe('reflecting visualization state', () => { + it('should select the correct button', () => { + expect( + getHarnessWithState({ ...stateWOTrend, showBar: false, maxAccessor: undefined }) + .currentSupportingVis + ).toBe('none'); + expect( + getHarnessWithState({ ...stateWOTrend, showBar: true }).currentSupportingVis + ).toBe('bar'); + expect( + getHarnessWithState(metricAccessorState).currentSupportingVis + ).toBe('trendline'); + }); + + it('should disable bar when no max dimension', () => { + expect( + getHarnessWithState({ + ...stateWOTrend, + showBar: false, + maxAccessor: 'something', + }).isDisabled('bar') + ).toBeFalsy(); + expect( + getHarnessWithState({ + ...stateWOTrend, + showBar: false, + maxAccessor: undefined, + }).isDisabled('bar') + ).toBeTruthy(); + }); + + it('should disable trendline when no default time field', () => { + expect( + getHarnessWithState(stateWOTrend, { + hasDefaultTimeField: () => false, + getOperationForColumnId: (id) => ({} as OperationDescriptor), + } as DatasourcePublicAPI).isDisabled('trendline') + ).toBeTruthy(); + expect( + getHarnessWithState(stateWOTrend, { + hasDefaultTimeField: () => true, + getOperationForColumnId: (id) => ({} as OperationDescriptor), + } as DatasourcePublicAPI).isDisabled('trendline') + ).toBeFalsy(); + }); + }); + + it('should disable trendline when a metric dimension has a reduced time range', () => { + expect( + getHarnessWithState(stateWOTrend, { + hasDefaultTimeField: () => true, + getOperationForColumnId: (id) => + ({ + hasReducedTimeRange: id === stateWOTrend.metricAccessor, + } as OperationDescriptor), + } as DatasourcePublicAPI).isDisabled('trendline') + ).toBeTruthy(); + expect( + getHarnessWithState(stateWOTrend, { + hasDefaultTimeField: () => true, + getOperationForColumnId: (id) => + ({ + hasReducedTimeRange: id === stateWOTrend.secondaryMetricAccessor, + } as OperationDescriptor), + } as DatasourcePublicAPI).isDisabled('trendline') + ).toBeTruthy(); + }); + + describe('responding to buttons', () => { + it('enables trendline', () => { + getHarnessWithState(stateWOTrend).setSupportingVis('trendline'); + + expect(mockSetState).toHaveBeenCalledWith({ ...stateWOTrend, showBar: false }); + expect(props.addLayer).toHaveBeenCalledWith('metricTrendline'); + + expectCalledBefore(mockSetState, props.addLayer as jest.Mock); + }); + + it('enables bar', () => { + getHarnessWithState(metricAccessorState).setSupportingVis('bar'); + + expect(mockSetState).toHaveBeenCalledWith({ ...metricAccessorState, showBar: true }); + expect(props.removeLayer).toHaveBeenCalledWith(metricAccessorState.trendlineLayerId); + + expectCalledBefore(mockSetState, props.removeLayer as jest.Mock); + }); + + it('selects none from bar', () => { + getHarnessWithState(stateWOTrend).setSupportingVis('none'); + + expect(mockSetState).toHaveBeenCalledWith({ ...stateWOTrend, showBar: false }); + expect(props.removeLayer).not.toHaveBeenCalled(); + }); + + it('selects none from trendline', () => { + getHarnessWithState(metricAccessorState).setSupportingVis('none'); + + expect(mockSetState).toHaveBeenCalledWith({ ...metricAccessorState, showBar: false }); + expect(props.removeLayer).toHaveBeenCalledWith(metricAccessorState.trendlineLayerId); + + expectCalledBefore(mockSetState, props.removeLayer as jest.Mock); + }); + }); + + describe('progress bar direction controls', () => { + it('hides direction controls if bar not showing', () => { + expect( + getHarnessWithState({ ...stateWOTrend, showBar: false }).progressDirectionShowing + ).toBeFalsy(); + }); + + it('toggles progress direction', () => { + const harness = getHarnessWithState(metricAccessorState); + + expect(harness.progressDirectionShowing).toBeTruthy(); + expect(harness.currentState.progressDirection).toBe('vertical'); + + harness.setProgressDirection('horizontal'); + harness.setProgressDirection('vertical'); + harness.setProgressDirection('horizontal'); + + expect(mockSetState).toHaveBeenCalledTimes(3); + expect(mockSetState.mock.calls.map((args) => args[0].progressDirection)) + .toMatchInlineSnapshot(` + Array [ + "horizontal", + "vertical", + "horizontal", + ] + `); + }); + }); + }); + }); }); diff --git a/x-pack/plugins/lens/public/visualizations/metric/dimension_editor.tsx b/x-pack/plugins/lens/public/visualizations/metric/dimension_editor.tsx index 4ca35b060e02..6571e1bb2c7d 100644 --- a/x-pack/plugins/lens/public/visualizations/metric/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/visualizations/metric/dimension_editor.tsx @@ -17,6 +17,8 @@ import { EuiColorPicker, euiPaletteColorBlind, EuiSpacer, + EuiText, + useEuiTheme, } from '@elastic/eui'; import { LayoutDirection } from '@elastic/charts'; import React, { useCallback, useState } from 'react'; @@ -30,6 +32,7 @@ import { } from '@kbn/coloring'; import { getDataBoundsForPalette } from '@kbn/expression-metric-vis-plugin/public'; import { getColumnByAccessor } from '@kbn/visualizations-plugin/common/utils'; +import { css } from '@emotion/react'; import { isNumericFieldForDatatable } from '../../../common/expressions/datatable/utils'; import { applyPaletteParams, @@ -263,170 +266,8 @@ function PrimaryMetricEditor(props: SubProps) { const togglePalette = () => setIsPaletteOpen(!isPaletteOpen); - const supportingVisLabel = i18n.translate('xpack.lens.metric.supportingVis.label', { - defaultMessage: 'Supporting visualization', - }); - - const hasDefaultTimeField = props.datasource?.hasDefaultTimeField(); - const metricHasReducedTimeRange = Boolean( - state.metricAccessor && - props.datasource?.getOperationForColumnId(state.metricAccessor)?.hasReducedTimeRange - ); - const secondaryMetricHasReducedTimeRange = Boolean( - state.secondaryMetricAccessor && - props.datasource?.getOperationForColumnId(state.secondaryMetricAccessor)?.hasReducedTimeRange - ); - - const supportingVisHelpTexts: string[] = []; - - const supportsTrendline = - hasDefaultTimeField && !metricHasReducedTimeRange && !secondaryMetricHasReducedTimeRange; - - if (!supportsTrendline) { - supportingVisHelpTexts.push( - !hasDefaultTimeField - ? i18n.translate('xpack.lens.metric.supportingVis.needDefaultTimeField', { - defaultMessage: 'Use a data view with a default time field to enable trend lines.', - }) - : metricHasReducedTimeRange - ? i18n.translate('xpack.lens.metric.supportingVis.metricHasReducedTimeRange', { - defaultMessage: - 'Remove the reduced time range on this dimension to enable trend lines.', - }) - : secondaryMetricHasReducedTimeRange - ? i18n.translate('xpack.lens.metric.supportingVis.secondaryMetricHasReducedTimeRange', { - defaultMessage: - 'Remove the reduced time range on the secondary metric dimension to enable trend lines.', - }) - : '' - ); - } - - if (!state.maxAccessor) { - supportingVisHelpTexts.push( - i18n.translate('xpack.lens.metric.summportingVis.needMaxDimension', { - defaultMessage: 'Add a maximum dimension to enable the progress bar.', - }) - ); - } - - const buttonIdPrefix = `${idPrefix}--`; - return ( <> - ( -
{text}
- ))} - > - { - const supportingVisualizationType = id.split('--')[1] as SupportingVisType; - - switch (supportingVisualizationType) { - case 'trendline': - setState({ - ...state, - showBar: false, - }); - props.addLayer('metricTrendline'); - break; - case 'bar': - setState({ - ...state, - showBar: true, - }); - if (state.trendlineLayerId) props.removeLayer(state.trendlineLayerId); - break; - case 'none': - setState({ - ...state, - showBar: false, - }); - if (state.trendlineLayerId) props.removeLayer(state.trendlineLayerId); - break; - } - }} - /> -
- {showingBar(state) && ( - - { - const newDirection = id.replace(idPrefix, '') as LayoutDirection; - setState({ - ...state, - progressDirection: newDirection, - }); - }} - /> - - )} ); } + +export function DimensionEditorAdditionalSection({ + state, + datasource, + setState, + addLayer, + removeLayer, + accessor, +}: VisualizationDimensionEditorProps) { + const { euiTheme } = useEuiTheme(); + + if (accessor !== state.metricAccessor) { + return null; + } + + const idPrefix = htmlIdGenerator()(); + + const hasDefaultTimeField = datasource?.hasDefaultTimeField(); + const metricHasReducedTimeRange = Boolean( + state.metricAccessor && + datasource?.getOperationForColumnId(state.metricAccessor)?.hasReducedTimeRange + ); + const secondaryMetricHasReducedTimeRange = Boolean( + state.secondaryMetricAccessor && + datasource?.getOperationForColumnId(state.secondaryMetricAccessor)?.hasReducedTimeRange + ); + + const supportingVisHelpTexts: string[] = []; + + const supportsTrendline = + hasDefaultTimeField && !metricHasReducedTimeRange && !secondaryMetricHasReducedTimeRange; + + if (!supportsTrendline) { + supportingVisHelpTexts.push( + !hasDefaultTimeField + ? i18n.translate('xpack.lens.metric.supportingVis.needDefaultTimeField', { + defaultMessage: + 'Line visualizations require use of a data view with a default time field.', + }) + : metricHasReducedTimeRange + ? i18n.translate('xpack.lens.metric.supportingVis.metricHasReducedTimeRange', { + defaultMessage: + 'Line visualizations cannot be used when a reduced time range is applied to the primary metric.', + }) + : secondaryMetricHasReducedTimeRange + ? i18n.translate('xpack.lens.metric.supportingVis.secondaryMetricHasReducedTimeRange', { + defaultMessage: + 'Line visualizations cannot be used when a reduced time range is applied to the secondary metric.', + }) + : '' + ); + } + + if (!state.maxAccessor) { + supportingVisHelpTexts.push( + i18n.translate('xpack.lens.metric.summportingVis.needMaxDimension', { + defaultMessage: 'Bar visualizations require a maximum value to be defined.', + }) + ); + } + + const buttonIdPrefix = `${idPrefix}--`; + + return ( +
+ +

+ {i18n.translate('xpack.lens.metric.supportingVis.label', { + defaultMessage: 'Supporting visualization', + })} +

+
+ + <> + ( +

{text}

+ ))} + > + { + const supportingVisualizationType = id.split('--')[1] as SupportingVisType; + + switch (supportingVisualizationType) { + case 'trendline': + setState({ + ...state, + showBar: false, + }); + addLayer('metricTrendline'); + break; + case 'bar': + setState({ + ...state, + showBar: true, + }); + if (state.trendlineLayerId) removeLayer(state.trendlineLayerId); + break; + case 'none': + setState({ + ...state, + showBar: false, + }); + if (state.trendlineLayerId) removeLayer(state.trendlineLayerId); + break; + } + }} + /> +
+ {showingBar(state) && ( + + { + const newDirection = id.replace(idPrefix, '') as LayoutDirection; + setState({ + ...state, + progressDirection: newDirection, + }); + }} + /> + + )} + +
+ ); +} diff --git a/x-pack/plugins/lens/public/visualizations/metric/visualization.tsx b/x-pack/plugins/lens/public/visualizations/metric/visualization.tsx index 123821a62aa6..eac08b22c9ea 100644 --- a/x-pack/plugins/lens/public/visualizations/metric/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/metric/visualization.tsx @@ -30,7 +30,7 @@ import { Suggestion, } from '../../types'; import { GROUP_ID, LENS_METRIC_ID } from './constants'; -import { DimensionEditor } from './dimension_editor'; +import { DimensionEditor, DimensionEditorAdditionalSection } from './dimension_editor'; import { Toolbar } from './toolbar'; import { generateId } from '../../id_generator'; import { FormatSelectorOptions } from '../../datasources/form_based/dimension_panel/format_selector'; @@ -454,6 +454,10 @@ export const getMetricVisualization = ({ return newState; }, + getRemoveOperation(state, layerId) { + return layerId === state.trendlineLayerId ? 'remove' : 'clear'; + }, + getLayersToLinkTo(state, newLayerId: string): string[] { return newLayerId === state.trendlineLayerId ? [state.layerId] : []; }, @@ -617,6 +621,17 @@ export const getMetricVisualization = ({ ); }, + renderDimensionEditorAdditionalSection(domElement, props) { + render( + + + + + , + domElement + ); + }, + getErrorMessages(state) { // Is it possible to break it? return undefined; diff --git a/x-pack/plugins/maps/common/descriptor_types/style_property_descriptor_types.ts b/x-pack/plugins/maps/common/descriptor_types/style_property_descriptor_types.ts index 78e9393e5755..ad04d467741c 100644 --- a/x-pack/plugins/maps/common/descriptor_types/style_property_descriptor_types.ts +++ b/x-pack/plugins/maps/common/descriptor_types/style_property_descriptor_types.ts @@ -78,6 +78,7 @@ export type ColorDynamicOptions = { customColorRamp?: OrdinalColorStop[]; useCustomColorRamp?: boolean; dataMappingFunction?: DATA_MAPPING_FUNCTION; + invert?: boolean; // category color properties colorCategory?: string; // TODO move color category palettes to constants and make ENUM type @@ -176,6 +177,7 @@ export type SizeDynamicOptions = { maxSize: number; field?: StylePropertyField; fieldMetaOptions: FieldMetaOptions; + invert?: boolean; }; export type SizeStaticOptions = { diff --git a/x-pack/plugins/maps/public/classes/layers/layer_group/layer_group.test.ts b/x-pack/plugins/maps/public/classes/layers/layer_group/layer_group.test.ts new file mode 100644 index 000000000000..19aa122aa9f9 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/layers/layer_group/layer_group.test.ts @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { MAX_ZOOM, MIN_ZOOM } from '../../../../common/constants'; +import { LayerGroup } from './layer_group'; +import { ILayer } from '../layer'; + +describe('getMinZoom', () => { + test('should return MIN_ZOOM when there are no children', async () => { + const layerGroup = new LayerGroup({ layerDescriptor: LayerGroup.createDescriptor({}) }); + expect(layerGroup.getMinZoom()).toBe(MIN_ZOOM); + }); + + test('should return smallest child.getMinZoom()', async () => { + const layerGroup = new LayerGroup({ layerDescriptor: LayerGroup.createDescriptor({}) }); + layerGroup.setChildren([ + { + getMinZoom: () => { + return 1; + }, + } as unknown as ILayer, + { + getMinZoom: () => { + return 4; + }, + } as unknown as ILayer, + ]); + expect(layerGroup.getMinZoom()).toBe(1); + }); +}); + +describe('getMaxZoom', () => { + test('should return MAX_ZOOM when there are no children', async () => { + const layerGroup = new LayerGroup({ layerDescriptor: LayerGroup.createDescriptor({}) }); + expect(layerGroup.getMaxZoom()).toBe(MAX_ZOOM); + }); + + test('should return largest child.getMaxZoom()', async () => { + const layerGroup = new LayerGroup({ layerDescriptor: LayerGroup.createDescriptor({}) }); + layerGroup.setChildren([ + { + getMaxZoom: () => { + return 18; + }, + } as unknown as ILayer, + { + getMaxZoom: () => { + return 20; + }, + } as unknown as ILayer, + ]); + expect(layerGroup.getMaxZoom()).toBe(20); + }); +}); diff --git a/x-pack/plugins/maps/public/classes/layers/layer_group/layer_group.tsx b/x-pack/plugins/maps/public/classes/layers/layer_group/layer_group.tsx index c0e3c4ee5640..c1a2a2964a31 100644 --- a/x-pack/plugins/maps/public/classes/layers/layer_group/layer_group.tsx +++ b/x-pack/plugins/maps/public/classes/layers/layer_group/layer_group.tsx @@ -207,20 +207,34 @@ export class LayerGroup implements ILayer { return zoom >= this.getMinZoom() && zoom <= this.getMaxZoom(); } + /* + * Returns smallest min from children or MIN_ZOOM when there are no children + */ getMinZoom(): number { - let min = MIN_ZOOM; + let min: number | undefined; this._children.forEach((child) => { - min = Math.max(min, child.getMinZoom()); + if (min !== undefined) { + min = Math.min(min, child.getMinZoom()); + } else { + min = child.getMinZoom(); + } }); - return min; + return min !== undefined ? min : MIN_ZOOM; } + /* + * Returns largest max from children or MAX_ZOOM when there are no children + */ getMaxZoom(): number { - let max = MAX_ZOOM; + let max: number | undefined; this._children.forEach((child) => { - max = Math.min(max, child.getMaxZoom()); + if (max !== undefined) { + max = Math.max(max, child.getMaxZoom()); + } else { + max = child.getMaxZoom(); + } }); - return max; + return max !== undefined ? max : MAX_ZOOM; } getMinSourceZoom(): number { diff --git a/x-pack/plugins/maps/public/classes/styles/color_palettes.test.ts b/x-pack/plugins/maps/public/classes/styles/color_palettes.test.ts index 6c98cdc47650..d5a6ea694b3e 100644 --- a/x-pack/plugins/maps/public/classes/styles/color_palettes.test.ts +++ b/x-pack/plugins/maps/public/classes/styles/color_palettes.test.ts @@ -13,7 +13,7 @@ import { } from './color_palettes'; describe('getColorPalette', () => { - it('Should create RGB color ramp', () => { + test('Should create RGB color ramp', () => { expect(getColorPalette('Blues')).toEqual([ '#ecf1f7', '#d9e3ef', @@ -28,14 +28,14 @@ describe('getColorPalette', () => { }); describe('getColorRampCenterColor', () => { - it('Should get center color from color ramp', () => { + test('Should get center color from color ramp', () => { expect(getColorRampCenterColor('Blues')).toBe('#9eb9d8'); }); }); describe('getOrdinalMbColorRampStops', () => { - it('Should create color stops for custom range', () => { - expect(getOrdinalMbColorRampStops('Blues', 0, 1000)).toEqual([ + test('Should create color stops', () => { + expect(getOrdinalMbColorRampStops('Blues', 0, 1000, false)).toEqual([ 0, '#ecf1f7', 125, @@ -55,13 +55,34 @@ describe('getOrdinalMbColorRampStops', () => { ]); }); - it('Should snap to end of color stops for identical range', () => { - expect(getOrdinalMbColorRampStops('Blues', 23, 23)).toEqual([23, '#6092c0']); + test('Should create inverted color stops', () => { + expect(getOrdinalMbColorRampStops('Blues', 0, 1000, true)).toEqual([ + 0, + '#6092c0', + 125, + '#769fc8', + 250, + '#8bacd0', + 375, + '#9eb9d8', + 500, + '#b2c7df', + 625, + '#c5d5e7', + 750, + '#d9e3ef', + 875, + '#ecf1f7', + ]); + }); + + test('Should snap to end of color stops for identical range', () => { + expect(getOrdinalMbColorRampStops('Blues', 23, 23, false)).toEqual([23, '#6092c0']); }); }); describe('getPercentilesMbColorRampStops', () => { - it('Should create color stops for custom range', () => { + test('Should create color stops', () => { const percentiles = [ { percentile: '50.0', value: 5567.83 }, { percentile: '75.0', value: 8069 }, @@ -69,7 +90,7 @@ describe('getPercentilesMbColorRampStops', () => { { percentile: '95.0', value: 11145.5 }, { percentile: '99.0', value: 16958.18 }, ]; - expect(getPercentilesMbColorRampStops('Blues', percentiles)).toEqual([ + expect(getPercentilesMbColorRampStops('Blues', percentiles, false)).toEqual([ 5567.83, '#e0e8f2', 8069, @@ -82,4 +103,26 @@ describe('getPercentilesMbColorRampStops', () => { '#6092c0', ]); }); + + test('Should create inverted color stops', () => { + const percentiles = [ + { percentile: '50.0', value: 5567.83 }, + { percentile: '75.0', value: 8069 }, + { percentile: '90.0', value: 9581.13 }, + { percentile: '95.0', value: 11145.5 }, + { percentile: '99.0', value: 16958.18 }, + ]; + expect(getPercentilesMbColorRampStops('Blues', percentiles, true)).toEqual([ + 5567.83, + '#6092c0', + 8069, + '#82a7cd', + 9581.13, + '#a2bcd9', + 11145.5, + '#c2d2e6', + 16958.18, + '#e0e8f2', + ]); + }); }); diff --git a/x-pack/plugins/maps/public/classes/styles/color_palettes.ts b/x-pack/plugins/maps/public/classes/styles/color_palettes.ts index 6fa68047acbe..45cd1d988fe6 100644 --- a/x-pack/plugins/maps/public/classes/styles/color_palettes.ts +++ b/x-pack/plugins/maps/public/classes/styles/color_palettes.ts @@ -153,7 +153,7 @@ export function getColorPalette(colorPaletteId: string): string[] { const colorPalette = COLOR_PALETTES.find(({ value }: COLOR_PALETTE) => { return value === colorPaletteId; }); - return colorPalette ? (colorPalette.palette as string[]) : []; + return colorPalette ? [...(colorPalette.palette as string[])] : []; } export function getColorRampCenterColor(colorPaletteId: string): string | null { @@ -169,7 +169,8 @@ export function getColorRampCenterColor(colorPaletteId: string): string | null { export function getOrdinalMbColorRampStops( colorPaletteId: string | null, min: number, - max: number + max: number, + invert: boolean ): Array | null { if (!colorPaletteId) { return null; @@ -180,6 +181,10 @@ export function getOrdinalMbColorRampStops( } const palette = getColorPalette(colorPaletteId); + if (invert) { + palette.reverse(); + } + if (palette.length === 0) { return null; } @@ -203,7 +208,8 @@ export function getOrdinalMbColorRampStops( // [ stop_input_1: number, stop_output_1: color, stop_input_n: number, stop_output_n: color ] export function getPercentilesMbColorRampStops( colorPaletteId: string | null, - percentiles: PercentilesFieldMeta + percentiles: PercentilesFieldMeta, + invert: boolean ): Array | null { if (!colorPaletteId) { return null; @@ -213,13 +219,17 @@ export function getPercentilesMbColorRampStops( return value === colorPaletteId; }); - return paletteObject - ? paletteObject - .getPalette(percentiles.length) - .reduce((accu: Array, stopColor: string, idx: number) => { - return [...accu, percentiles[idx].value, stopColor]; - }, []) - : null; + if (!paletteObject) { + return null; + } + + const palette = paletteObject.getPalette(percentiles.length); + if (invert) { + palette.reverse(); + } + return palette.reduce((accu: Array, stopColor: string, idx: number) => { + return [...accu, percentiles[idx].value, stopColor]; + }, []); } export function getLinearGradient(colorStrings: string[]): string { diff --git a/x-pack/plugins/maps/public/classes/styles/components/ranged_style_legend_row.tsx b/x-pack/plugins/maps/public/classes/styles/components/ranged_style_legend_row.tsx index b6a0f901b3db..cf753da8dba0 100644 --- a/x-pack/plugins/maps/public/classes/styles/components/ranged_style_legend_row.tsx +++ b/x-pack/plugins/maps/public/classes/styles/components/ranged_style_legend_row.tsx @@ -15,6 +15,7 @@ interface Props { maxLabel: string | number; propertyLabel: string; fieldLabel: string; + invert: boolean; } export function RangedStyleLegendRow({ @@ -23,6 +24,7 @@ export function RangedStyleLegendRow({ maxLabel, propertyLabel, fieldLabel, + invert, }: Props) { return (
@@ -41,12 +43,12 @@ export function RangedStyleLegendRow({ - {minLabel} + {invert ? maxLabel : minLabel} - {maxLabel} + {invert ? minLabel : maxLabel} diff --git a/x-pack/plugins/maps/public/classes/styles/heatmap/components/legend/heatmap_legend.tsx b/x-pack/plugins/maps/public/classes/styles/heatmap/components/legend/heatmap_legend.tsx index 5459c55ed95c..390e48408d74 100644 --- a/x-pack/plugins/maps/public/classes/styles/heatmap/components/legend/heatmap_legend.tsx +++ b/x-pack/plugins/maps/public/classes/styles/heatmap/components/legend/heatmap_legend.tsx @@ -58,6 +58,7 @@ export class HeatmapLegend extends Component { })} propertyLabel={HEATMAP_COLOR_RAMP_LABEL} fieldLabel={this.state.label} + invert={false} /> ); } diff --git a/x-pack/plugins/maps/public/classes/styles/heatmap/heatmap_style.tsx b/x-pack/plugins/maps/public/classes/styles/heatmap/heatmap_style.tsx index a2abbad8cddf..f4413eea15ac 100644 --- a/x-pack/plugins/maps/public/classes/styles/heatmap/heatmap_style.tsx +++ b/x-pack/plugins/maps/public/classes/styles/heatmap/heatmap_style.tsx @@ -96,7 +96,8 @@ export class HeatmapStyle implements IStyle { const colorStops = getOrdinalMbColorRampStops( this._descriptor.colorRampName, MIN_RANGE, - MAX_RANGE + MAX_RANGE, + false ); if (colorStops) { mbMap.setPaintProperty(layerId, 'heatmap-color', [ diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/color/color_map_select.js b/x-pack/plugins/maps/public/classes/styles/vector/components/color/color_map_select.js index 4549367e5347..1409444e0742 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/color/color_map_select.js +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/color/color_map_select.js @@ -129,16 +129,26 @@ export class ColorMapSelect extends Component { ); } + _getColorPalettes() { + if (this.props.colorMapType === COLOR_MAP_TYPE.CATEGORICAL) { + return CATEGORICAL_COLOR_PALETTES; + } + + return this.props.invert + ? NUMERICAL_COLOR_PALETTES.map((paletteProps) => { + return { + ...paletteProps, + palette: [...paletteProps.palette].reverse(), + }; + }) + : NUMERICAL_COLOR_PALETTES; + } + _renderColorMapSelections() { if (this.props.isCustomOnly) { return null; } - const palettes = - this.props.colorMapType === COLOR_MAP_TYPE.ORDINAL - ? NUMERICAL_COLOR_PALETTES - : CATEGORICAL_COLOR_PALETTES; - const palettesWithCustom = [ { value: CUSTOM_COLOR_MAP, @@ -153,7 +163,7 @@ export class ColorMapSelect extends Component { type: 'text', 'data-test-subj': `colorMapSelectOption_${CUSTOM_COLOR_MAP}`, }, - ...palettes, + ...this._getColorPalettes(), ]; const toggle = this.props.showColorMapTypeToggle ? ( diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/color/dynamic_color_form.js b/x-pack/plugins/maps/public/classes/styles/vector/components/color/dynamic_color_form.tsx similarity index 50% rename from x-pack/plugins/maps/public/classes/styles/vector/components/color/dynamic_color_form.js rename to x-pack/plugins/maps/public/classes/styles/vector/components/color/dynamic_color_form.tsx index a2dba66d3956..fd0f367d2954 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/color/dynamic_color_form.js +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/color/dynamic_color_form.tsx @@ -6,12 +6,40 @@ */ import _ from 'lodash'; -import React, { Fragment } from 'react'; +import React, { ChangeEvent, ReactNode } from 'react'; +import { i18n } from '@kbn/i18n'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiSpacer, + EuiSwitch, + EuiSwitchEvent, +} from '@elastic/eui'; import { FieldSelect } from '../field_select'; +// @ts-expect-error import { ColorMapSelect } from './color_map_select'; -import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import { OtherCategoryColorPicker } from './other_category_color_picker'; -import { CATEGORICAL_DATA_TYPES, COLOR_MAP_TYPE } from '../../../../../../common/constants'; +import { + CategoryColorStop, + ColorDynamicOptions, + OrdinalColorStop, +} from '../../../../../../common/descriptor_types'; +import { + CATEGORICAL_DATA_TYPES, + COLOR_MAP_TYPE, + VECTOR_STYLES, +} from '../../../../../../common/constants'; +import { StyleField } from '../../style_fields_helper'; +import { DynamicColorProperty } from '../../properties/dynamic_color_property'; + +interface Props { + fields: StyleField[]; + onDynamicStyleChange: (propertyName: VECTOR_STYLES, options: ColorDynamicOptions) => void; + staticDynamicSelect?: ReactNode; + styleProperty: DynamicColorProperty; + swatches: string[]; +} export function DynamicColorForm({ fields, @@ -19,10 +47,20 @@ export function DynamicColorForm({ staticDynamicSelect, styleProperty, swatches, -}) { +}: Props) { const styleOptions = styleProperty.getOptions(); - const onColorMapSelect = ({ color, customColorMap, type, useCustomColorMap }) => { + const onColorMapSelect = ({ + color, + customColorMap, + type, + useCustomColorMap, + }: { + color?: null | string; + customColorMap?: OrdinalColorStop[] | CategoryColorStop[]; + type: COLOR_MAP_TYPE; + useCustomColorMap: boolean; + }) => { const newColorOptions = { ...styleOptions, type, @@ -30,7 +68,7 @@ export function DynamicColorForm({ if (type === COLOR_MAP_TYPE.ORDINAL) { newColorOptions.useCustomColorRamp = useCustomColorMap; if (customColorMap) { - newColorOptions.customColorRamp = customColorMap; + newColorOptions.customColorRamp = customColorMap as OrdinalColorStop[]; } if (color) { newColorOptions.color = color; @@ -38,7 +76,7 @@ export function DynamicColorForm({ } else { newColorOptions.useCustomColorPalette = useCustomColorMap; if (customColorMap) { - newColorOptions.customColorPalette = customColorMap; + newColorOptions.customColorPalette = customColorMap as CategoryColorStop[]; } if (color) { newColorOptions.colorCategory = color; @@ -48,7 +86,11 @@ export function DynamicColorForm({ onDynamicStyleChange(styleProperty.getStyleName(), newColorOptions); }; - const onFieldChange = async ({ field }) => { + const onFieldChange = ({ field }: { field: StyleField | null }) => { + if (!field) { + return; + } + const { name, origin, type: fieldType } = field; const defaultColorMapType = CATEGORICAL_DATA_TYPES.includes(fieldType) ? COLOR_MAP_TYPE.CATEGORICAL @@ -60,21 +102,28 @@ export function DynamicColorForm({ }); }; - const onColorMapTypeChange = async (e) => { - const colorMapType = e.target.value; + const onColorMapTypeChange = (e: ChangeEvent) => { + const colorMapType = e.target.value as COLOR_MAP_TYPE; onDynamicStyleChange(styleProperty.getStyleName(), { ...styleOptions, type: colorMapType, }); }; - const onOtherCategoryColorChange = (color) => { + const onOtherCategoryColorChange = (color: string) => { onDynamicStyleChange(styleProperty.getStyleName(), { ...styleOptions, otherCategoryColor: color, }); }; + const onInvertChange = (event: EuiSwitchEvent) => { + onDynamicStyleChange(styleProperty.getStyleName(), { + ...styleOptions, + invert: event.target.checked, + }); + }; + const getField = () => { const fieldName = styleProperty.getFieldName(); if (!fieldName) { @@ -92,10 +141,11 @@ export function DynamicColorForm({ return null; } + const invert = styleOptions.invert === undefined ? false : styleOptions.invert; const showColorMapTypeToggle = !CATEGORICAL_DATA_TYPES.includes(field.type); - if (styleProperty.isOrdinal()) { - return ( + return styleProperty.isOrdinal() ? ( + <> - ); - } else if (styleProperty.isCategorical()) { - return ( - <> - - - - ); - } + {!!styleOptions.useCustomColorRamp ? null : ( + + + + )} + + ) : ( + <> + + + + ); }; return ( - + <> - {staticDynamicSelect} + {staticDynamicSelect ? staticDynamicSelect : null} {renderColorMapSelect()} - + ); } diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/color/static_color_form.js b/x-pack/plugins/maps/public/classes/styles/vector/components/color/static_color_form.tsx similarity index 61% rename from x-pack/plugins/maps/public/classes/styles/vector/components/color/static_color_form.js rename to x-pack/plugins/maps/public/classes/styles/vector/components/color/static_color_form.tsx index 02716e7994c4..20f854916a63 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/color/static_color_form.js +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/color/static_color_form.tsx @@ -5,24 +5,34 @@ * 2.0. */ -import React from 'react'; +import React, { ReactNode } from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { VECTOR_STYLES } from '../../../../../../common/constants'; +import { ColorStaticOptions } from '../../../../../../common/descriptor_types'; import { MbValidatedColorPicker } from './mb_validated_color_picker'; +import { StaticColorProperty } from '../../properties/static_color_property'; + +interface Props { + onStaticStyleChange: (propertyName: VECTOR_STYLES, options: ColorStaticOptions) => void; + staticDynamicSelect?: ReactNode; + styleProperty: StaticColorProperty; + swatches: string[]; +} export function StaticColorForm({ onStaticStyleChange, staticDynamicSelect, styleProperty, swatches, -}) { - const onColorChange = (color) => { +}: Props) { + const onColorChange = (color: string) => { onStaticStyleChange(styleProperty.getStyleName(), { color }); }; return ( - {staticDynamicSelect} + {staticDynamicSelect ? staticDynamicSelect : null} , 'children'> & { @@ -21,9 +21,9 @@ type ColorEditorProps = Omit, 'ch export function VectorStyleColorEditor(props: ColorEditorProps) { const colorForm = props.styleProperty.isDynamic() ? ( - + ) : ( - + ); return ( diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/legend/marker_size_legend.tsx b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/marker_size_legend.tsx index c54a42b529a2..11deb6e55942 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/legend/marker_size_legend.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/marker_size_legend.tsx @@ -91,6 +91,7 @@ export class MarkerSizeLegend extends Component { if (!fieldMeta || !options) { return null; } + const invert = options.invert === undefined ? false : options.invert; const circleStyle = { fillOpacity: 0, @@ -136,14 +137,18 @@ export class MarkerSizeLegend extends Component { // Markers interpolated by area instead of radius to be more consistent with how the human eye+brain perceive shapes // and their visual relevance // This function mirrors output of maplibre expression created from DynamicSizeProperty.getMbSizeExpression - const value = Math.pow(percentage * Math.sqrt(fieldMeta!.delta), 2) + fieldMeta!.min; + const scaledWidth = Math.pow(percentage * Math.sqrt(fieldMeta!.delta), 2); + const value = invert ? fieldMeta!.max - scaledWidth : scaledWidth + fieldMeta!.min; return fieldMeta!.delta > 3 ? Math.round(value) : value; } const markers = []; if (fieldMeta.delta > 0) { - const smallestMarker = makeMarker(options.minSize, this._formatValue(fieldMeta.min)); + const smallestMarker = makeMarker( + options.minSize, + this._formatValue(invert ? fieldMeta.max : fieldMeta.min) + ); markers.push(smallestMarker); const markerDelta = options.maxSize - options.minSize; @@ -156,7 +161,10 @@ export class MarkerSizeLegend extends Component { } } - const largestMarker = makeMarker(options.maxSize, this._formatValue(fieldMeta.max)); + const largestMarker = makeMarker( + options.maxSize, + this._formatValue(invert ? fieldMeta.min : fieldMeta.max) + ); markers.push(largestMarker); return ( diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/legend/ordinal_legend.tsx b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/ordinal_legend.tsx index 2578a908bb68..048d1bf12a21 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/legend/ordinal_legend.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/ordinal_legend.tsx @@ -138,6 +138,9 @@ export class OrdinalLegend extends Component { this.props.style.isFieldMetaEnabled() && fieldMeta.isMaxOutsideStdRange ? `> ${max}` : max; } + const options = this.props.style.getOptions(); + const invert = options.invert === undefined ? false : options.invert; + return ( { maxLabel={maxLabel} propertyLabel={this.props.style.getDisplayStyleName()} fieldLabel={this.state.label} + invert={invert} /> ); } diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/size/dynamic_size_form.js b/x-pack/plugins/maps/public/classes/styles/vector/components/size/dynamic_size_form.js deleted file mode 100644 index a32ab43f6e07..000000000000 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/size/dynamic_size_form.js +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { Fragment } from 'react'; -import { FieldSelect } from '../field_select'; -import { SizeRangeSelector } from './size_range_selector'; -import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; - -export function DynamicSizeForm({ - fields, - onDynamicStyleChange, - staticDynamicSelect, - styleProperty, -}) { - const styleOptions = styleProperty.getOptions(); - - const onFieldChange = ({ field }) => { - onDynamicStyleChange(styleProperty.getStyleName(), { ...styleOptions, field }); - }; - - const onSizeRangeChange = ({ minSize, maxSize }) => { - onDynamicStyleChange(styleProperty.getStyleName(), { - ...styleOptions, - minSize, - maxSize, - }); - }; - - let sizeRange; - if (styleOptions.field && styleOptions.field.name) { - sizeRange = ( - - ); - } - - return ( - - - - {staticDynamicSelect} - - - - - - - {sizeRange} - - ); -} diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/size/dynamic_size_form.tsx b/x-pack/plugins/maps/public/classes/styles/vector/components/size/dynamic_size_form.tsx new file mode 100644 index 000000000000..8efe05bc2644 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/size/dynamic_size_form.tsx @@ -0,0 +1,109 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { ReactNode } from 'react'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiSpacer, + EuiSwitch, + EuiSwitchEvent, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FieldSelect } from '../field_select'; +import { SizeRangeSelector } from './size_range_selector'; +import { SizeDynamicOptions } from '../../../../../../common/descriptor_types'; +import { VECTOR_STYLES } from '../../../../../../common/constants'; +import { DynamicSizeProperty } from '../../properties/dynamic_size_property'; +import { StyleField } from '../../style_fields_helper'; + +interface Props { + fields: StyleField[]; + onDynamicStyleChange: (propertyName: VECTOR_STYLES, options: SizeDynamicOptions) => void; + staticDynamicSelect?: ReactNode; + styleProperty: DynamicSizeProperty; +} + +export function DynamicSizeForm({ + fields, + onDynamicStyleChange, + staticDynamicSelect, + styleProperty, +}: Props) { + const styleOptions = styleProperty.getOptions(); + + const onFieldChange = ({ field }: { field: StyleField | null }) => { + if (field) { + onDynamicStyleChange(styleProperty.getStyleName(), { + ...styleOptions, + field: { name: field.name, origin: field.origin }, + }); + } + }; + + const onSizeRangeChange = ({ minSize, maxSize }: { minSize: number; maxSize: number }) => { + onDynamicStyleChange(styleProperty.getStyleName(), { + ...styleOptions, + minSize, + maxSize, + }); + }; + + const onInvertChange = (event: EuiSwitchEvent) => { + onDynamicStyleChange(styleProperty.getStyleName(), { + ...styleOptions, + invert: event.target.checked, + }); + }; + + let sizeRange; + if (styleOptions.field && styleOptions.field.name) { + sizeRange = ( + <> + + + + + + ); + } + + return ( + <> + + + {staticDynamicSelect} + + + + + + + {sizeRange} + + ); +} diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/size/size_range_selector.js b/x-pack/plugins/maps/public/classes/styles/vector/components/size/size_range_selector.tsx similarity index 74% rename from x-pack/plugins/maps/public/classes/styles/vector/components/size/size_range_selector.js rename to x-pack/plugins/maps/public/classes/styles/vector/components/size/size_range_selector.tsx index b47e22fcd318..59bdfa3be593 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/size/size_range_selector.js +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/size/size_range_selector.tsx @@ -6,13 +6,19 @@ */ import React from 'react'; -import PropTypes from 'prop-types'; import { ValidatedDualRange } from '@kbn/kibana-react-plugin/public'; -import { MIN_SIZE, MAX_SIZE } from '../../vector_style_defaults'; +import { EuiDualRangeProps } from '@elastic/eui/src/components/form/range/dual_range'; import { i18n } from '@kbn/i18n'; +import { MIN_SIZE, MAX_SIZE } from '../../vector_style_defaults'; -export function SizeRangeSelector({ minSize, maxSize, onChange, ...rest }) { - const onSizeChange = ([min, max]) => { +interface Props extends Omit { + minSize: number; + maxSize: number; + onChange: ({ maxSize, minSize }: { maxSize: number; minSize: number }) => void; +} + +export function SizeRangeSelector({ minSize, maxSize, onChange, ...rest }: Props) { + const onSizeChange = ([min, max]: [string, string]) => { onChange({ minSize: Math.max(MIN_SIZE, parseInt(min, 10)), maxSize: Math.min(MAX_SIZE, parseInt(max, 10)), @@ -37,9 +43,3 @@ export function SizeRangeSelector({ minSize, maxSize, onChange, ...rest }) { /> ); } - -SizeRangeSelector.propTypes = { - minSize: PropTypes.number.isRequired, - maxSize: PropTypes.number.isRequired, - onChange: PropTypes.func.isRequired, -}; diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/size/static_size_form.js b/x-pack/plugins/maps/public/classes/styles/vector/components/size/static_size_form.tsx similarity index 69% rename from x-pack/plugins/maps/public/classes/styles/vector/components/size/static_size_form.js rename to x-pack/plugins/maps/public/classes/styles/vector/components/size/static_size_form.tsx index 64ef17b67ed1..a3a7ad7e50bd 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/size/static_size_form.js +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/size/static_size_form.tsx @@ -5,13 +5,23 @@ * 2.0. */ -import React from 'react'; -import { ValidatedRange } from '../../../../../components/validated_range'; +import React, { ReactNode } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +// @ts-expect-error +import { ValidatedRange } from '../../../../../components/validated_range'; +import { SizeStaticOptions } from '../../../../../../common/descriptor_types'; +import { VECTOR_STYLES } from '../../../../../../common/constants'; +import { StaticSizeProperty } from '../../properties/static_size_property'; + +interface Props { + onStaticStyleChange: (propertyName: VECTOR_STYLES, options: SizeStaticOptions) => void; + staticDynamicSelect?: ReactNode; + styleProperty: StaticSizeProperty; +} -export function StaticSizeForm({ onStaticStyleChange, staticDynamicSelect, styleProperty }) { - const onSizeChange = (size) => { +export function StaticSizeForm({ onStaticStyleChange, staticDynamicSelect, styleProperty }: Props) { + const onSizeChange = (size: number) => { onStaticStyleChange(styleProperty.getStyleName(), { size }); }; diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/size/vector_style_size_editor.tsx b/x-pack/plugins/maps/public/classes/styles/vector/components/size/vector_style_size_editor.tsx index b34ff87618f3..7c4aac0f32e2 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/size/vector_style_size_editor.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/size/vector_style_size_editor.tsx @@ -8,19 +8,19 @@ import React from 'react'; import { Props, StylePropEditor } from '../style_prop_editor'; -// @ts-expect-error import { DynamicSizeForm } from './dynamic_size_form'; -// @ts-expect-error import { StaticSizeForm } from './static_size_form'; import { SizeDynamicOptions, SizeStaticOptions } from '../../../../../../common/descriptor_types'; +import { DynamicSizeProperty } from '../../properties/dynamic_size_property'; +import { StaticSizeProperty } from '../../properties/static_size_property'; type SizeEditorProps = Omit, 'children'>; export function VectorStyleSizeEditor(props: SizeEditorProps) { const sizeForm = props.styleProperty.isDynamic() ? ( - + ) : ( - + ); return {sizeForm}; diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.tsx index cbf5a4fe868e..091c4c4e36b2 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.tsx @@ -142,6 +142,7 @@ export class DynamicColorProperty extends DynamicStyleProperty | null = null; let getValuePrefix: ((i: number, isNext: boolean) => string) | null = null; if (this._options.useCustomColorRamp) { @@ -364,7 +368,8 @@ export class DynamicColorProperty extends DynamicStyleProperty } + invert={false} maxLabel="100_format" minLabel="0_format" propertyLabel="Border width" diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property/dynamic_size_property.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property/dynamic_size_property.tsx index 83ac50c7b4ea..13d93dffaaec 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property/dynamic_size_property.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property/dynamic_size_property.tsx @@ -112,17 +112,25 @@ export class DynamicSizeProperty extends DynamicStyleProperty { } async _createMbMapInstance(initialView: MapCenterAndZoom | null): Promise { + this._reportUsage(); return new Promise((resolve) => { const mbStyle = { version: 8 as 8, @@ -270,6 +277,24 @@ export class MbMap extends Component { }); } + _reportUsage() { + const usageCollector = getUsageCollection(); + if (!usageCollector) return; + + const webglSupport = maplibregl.supported(); + + usageCollector.reportUiCounter( + APP_ID, + METRIC_TYPE.LOADED, + webglSupport ? 'gl_webglSupported' : 'gl_webglNotSupported' + ); + + // Report low system performance or no hardware GPU + if (webglSupport && !maplibregl.supported({ failIfMajorPerformanceCaveat: true })) { + usageCollector.reportUiCounter(APP_ID, METRIC_TYPE.LOADED, 'gl_majorPerformanceCaveat'); + } + } + async _loadMakiSprites(mbMap: MapboxMap) { if (this._isMounted) { // Math.floor rounds values < 1 to 0. This occurs when browser is zoomed out diff --git a/x-pack/plugins/ml/public/application/components/navigation_menu/date_picker_wrapper/date_picker_wrapper.test.tsx b/x-pack/plugins/ml/public/application/components/navigation_menu/date_picker_wrapper/date_picker_wrapper.test.tsx index 536cc26888a9..b794e6bbdf2b 100644 --- a/x-pack/plugins/ml/public/application/components/navigation_menu/date_picker_wrapper/date_picker_wrapper.test.tsx +++ b/x-pack/plugins/ml/public/application/components/navigation_menu/date_picker_wrapper/date_picker_wrapper.test.tsx @@ -104,7 +104,7 @@ const MockedEuiSuperDatePicker = EuiSuperDatePicker as jest.MockedFunction< describe('Navigation Menu: ', () => { beforeEach(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); MockedEuiSuperDatePicker.mockClear(); }); diff --git a/x-pack/plugins/ml/public/application/routing/use_resolver.test.ts b/x-pack/plugins/ml/public/application/routing/use_resolver.test.ts index 2ff7d4b02480..d174c4c2c7e3 100644 --- a/x-pack/plugins/ml/public/application/routing/use_resolver.test.ts +++ b/x-pack/plugins/ml/public/application/routing/use_resolver.test.ts @@ -38,7 +38,7 @@ const redirectToJobsManagementPage = jest.fn(() => Promise.resolve()); describe('useResolver', () => { afterEach(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterEach(() => { jest.advanceTimersByTime(0); diff --git a/x-pack/plugins/ml/public/application/services/anomaly_explorer_charts_service.test.ts b/x-pack/plugins/ml/public/application/services/anomaly_explorer_charts_service.test.ts index a4874b9c13dd..ad78677ad0f1 100644 --- a/x-pack/plugins/ml/public/application/services/anomaly_explorer_charts_service.test.ts +++ b/x-pack/plugins/ml/public/application/services/anomaly_explorer_charts_service.test.ts @@ -37,7 +37,7 @@ describe('AnomalyExplorerChartsService', () => { }; beforeEach(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); mlApiServicesMock.jobs.jobForCloning.mockImplementation(() => Promise.resolve({})); diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_charts/use_anomaly_charts_input_resolver.test.ts b/x-pack/plugins/ml/public/embeddables/anomaly_charts/use_anomaly_charts_input_resolver.test.ts index 4231f08b00c4..112863f560df 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_charts/use_anomaly_charts_input_resolver.test.ts +++ b/x-pack/plugins/ml/public/embeddables/anomaly_charts/use_anomaly_charts_input_resolver.test.ts @@ -48,7 +48,7 @@ describe('useAnomalyChartsInputResolver', () => { }; beforeEach(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); const jobIds = ['test-job']; embeddableInput = new BehaviorSubject({ diff --git a/x-pack/plugins/monitoring/public/components/renderers/setup_mode.test.js b/x-pack/plugins/monitoring/public/components/renderers/setup_mode.test.js index f2e952c96e88..7984b8c23cdc 100644 --- a/x-pack/plugins/monitoring/public/components/renderers/setup_mode.test.js +++ b/x-pack/plugins/monitoring/public/components/renderers/setup_mode.test.js @@ -171,7 +171,7 @@ describe('SetupModeRenderer', () => { it('should use a new product found in the api response', () => { const newProduct = { id: 1 }; - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); jest.doMock('../../lib/setup_mode', () => ({ getSetupModeState: () => ({ supported: true, diff --git a/x-pack/plugins/observability/public/components/shared/date_picker/date_picker.test.tsx b/x-pack/plugins/observability/public/components/shared/date_picker/date_picker.test.tsx index 138018e104c8..929425d2d150 100644 --- a/x-pack/plugins/observability/public/components/shared/date_picker/date_picker.test.tsx +++ b/x-pack/plugins/observability/public/components/shared/date_picker/date_picker.test.tsx @@ -133,7 +133,7 @@ describe('DatePicker', () => { }); it('enables auto-refresh when refreshPaused is false', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); const { wrapper } = mountDatePicker({ rangeFrom: 'now-15m', rangeTo: 'now', @@ -148,7 +148,7 @@ describe('DatePicker', () => { }); it('disables auto-refresh when refreshPaused is true', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); mountDatePicker({ rangeFrom: 'now-15m', rangeTo: 'now', diff --git a/x-pack/plugins/observability/server/assets/component_templates/slo_mappings_template.ts b/x-pack/plugins/observability/server/assets/component_templates/slo_mappings_template.ts index 550a50ba112a..1611d3ee601b 100644 --- a/x-pack/plugins/observability/server/assets/component_templates/slo_mappings_template.ts +++ b/x-pack/plugins/observability/server/assets/component_templates/slo_mappings_template.ts @@ -20,6 +20,9 @@ export const getSLOMappingsTemplate = (name: string) => ({ type: 'keyword', ignore_above: 256, }, + revision: { + type: 'long', + }, numerator: { type: 'long', }, @@ -29,6 +32,25 @@ export const getSLOMappingsTemplate = (name: string) => ({ context: { type: 'flattened', }, + _internal: { + properties: { + name: { type: 'keyword', ignore_above: 256 }, + budgeting_method: { type: 'keyword' }, + objective: { + properties: { + target: { type: 'double' }, + timeslice_target: { type: 'double' }, + timeslice_window: { type: 'keyword' }, + }, + }, + time_window: { + properties: { + duration: { type: 'keyword' }, + is_rolling: { type: 'boolean' }, + }, + }, + }, + }, }, }, }, diff --git a/x-pack/plugins/observability/server/saved_objects/slo.ts b/x-pack/plugins/observability/server/saved_objects/slo.ts index a088765a988a..461896a35f84 100644 --- a/x-pack/plugins/observability/server/saved_objects/slo.ts +++ b/x-pack/plugins/observability/server/saved_objects/slo.ts @@ -42,8 +42,11 @@ export const slo: SavedObjectsType = { objective: { properties: { target: { type: 'float' }, + timeslice_target: { type: 'float' }, + timeslice_window: { type: 'keyword' }, }, }, + revision: { type: 'short' }, created_at: { type: 'date' }, updated_at: { type: 'date' }, }, diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/apm_transaction_duration.test.ts.snap b/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/apm_transaction_duration.test.ts.snap index 61fadab240ae..187126b8e8ef 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/apm_transaction_duration.test.ts.snap +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/apm_transaction_duration.test.ts.snap @@ -57,6 +57,31 @@ Object { "field": "@timestamp", }, }, + "slo._internal.budgeting_method": Object { + "terms": Object { + "field": "slo._internal.budgeting_method", + }, + }, + "slo._internal.name": Object { + "terms": Object { + "field": "slo._internal.name", + }, + }, + "slo._internal.objective.target": Object { + "terms": Object { + "field": "slo._internal.objective.target", + }, + }, + "slo._internal.time_window.duration": Object { + "terms": Object { + "field": "slo._internal.time_window.duration", + }, + }, + "slo._internal.time_window.is_rolling": Object { + "terms": Object { + "field": "slo._internal.time_window.is_rolling", + }, + }, "slo.id": Object { "terms": Object { "field": "slo.id", @@ -106,6 +131,36 @@ Object { }, }, "runtime_mappings": Object { + "slo._internal.budgeting_method": Object { + "script": Object { + "source": "emit('occurrences')", + }, + "type": "keyword", + }, + "slo._internal.name": Object { + "script": Object { + "source": "emit('irrelevant')", + }, + "type": "keyword", + }, + "slo._internal.objective.target": Object { + "script": Object { + "source": "emit(0.999)", + }, + "type": "double", + }, + "slo._internal.time_window.duration": Object { + "script": Object { + "source": "emit('7d')", + }, + "type": "keyword", + }, + "slo._internal.time_window.is_rolling": Object { + "script": Object { + "source": "emit(true)", + }, + "type": "boolean", + }, "slo.id": Object { "script": Object { "source": Any, diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/apm_transaction_error_rate.test.ts.snap b/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/apm_transaction_error_rate.test.ts.snap index 51a0ee31581a..3a7826cb9d8b 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/apm_transaction_error_rate.test.ts.snap +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/apm_transaction_error_rate.test.ts.snap @@ -62,6 +62,31 @@ Object { "field": "@timestamp", }, }, + "slo._internal.budgeting_method": Object { + "terms": Object { + "field": "slo._internal.budgeting_method", + }, + }, + "slo._internal.name": Object { + "terms": Object { + "field": "slo._internal.name", + }, + }, + "slo._internal.objective.target": Object { + "terms": Object { + "field": "slo._internal.objective.target", + }, + }, + "slo._internal.time_window.duration": Object { + "terms": Object { + "field": "slo._internal.time_window.duration", + }, + }, + "slo._internal.time_window.is_rolling": Object { + "terms": Object { + "field": "slo._internal.time_window.is_rolling", + }, + }, "slo.id": Object { "terms": Object { "field": "slo.id", @@ -111,6 +136,36 @@ Object { }, }, "runtime_mappings": Object { + "slo._internal.budgeting_method": Object { + "script": Object { + "source": "emit('occurrences')", + }, + "type": "keyword", + }, + "slo._internal.name": Object { + "script": Object { + "source": "emit('irrelevant')", + }, + "type": "keyword", + }, + "slo._internal.objective.target": Object { + "script": Object { + "source": "emit(0.999)", + }, + "type": "double", + }, + "slo._internal.time_window.duration": Object { + "script": Object { + "source": "emit('7d')", + }, + "type": "keyword", + }, + "slo._internal.time_window.is_rolling": Object { + "script": Object { + "source": "emit(true)", + }, + "type": "boolean", + }, "slo.id": Object { "script": Object { "source": Any, diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/kql_custom.test.ts.snap b/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/kql_custom.test.ts.snap index 4fac89747323..5fed8a8e0897 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/kql_custom.test.ts.snap +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/kql_custom.test.ts.snap @@ -141,6 +141,31 @@ Object { "field": "@timestamp", }, }, + "slo._internal.budgeting_method": Object { + "terms": Object { + "field": "slo._internal.budgeting_method", + }, + }, + "slo._internal.name": Object { + "terms": Object { + "field": "slo._internal.name", + }, + }, + "slo._internal.objective.target": Object { + "terms": Object { + "field": "slo._internal.objective.target", + }, + }, + "slo._internal.time_window.duration": Object { + "terms": Object { + "field": "slo._internal.time_window.duration", + }, + }, + "slo._internal.time_window.is_rolling": Object { + "terms": Object { + "field": "slo._internal.time_window.is_rolling", + }, + }, "slo.id": Object { "terms": Object { "field": "slo.id", @@ -171,6 +196,36 @@ Object { }, }, "runtime_mappings": Object { + "slo._internal.budgeting_method": Object { + "script": Object { + "source": "emit('occurrences')", + }, + "type": "keyword", + }, + "slo._internal.name": Object { + "script": Object { + "source": "emit('irrelevant')", + }, + "type": "keyword", + }, + "slo._internal.objective.target": Object { + "script": Object { + "source": "emit(0.999)", + }, + "type": "double", + }, + "slo._internal.time_window.duration": Object { + "script": Object { + "source": "emit('7d')", + }, + "type": "keyword", + }, + "slo._internal.time_window.is_rolling": Object { + "script": Object { + "source": "emit(true)", + }, + "type": "boolean", + }, "slo.id": Object { "script": Object { "source": Any, diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_duration.ts b/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_duration.ts index 68848c7f1aa3..35a9b5088ca7 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_duration.ts +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_duration.ts @@ -5,11 +5,7 @@ * 2.0. */ -import { - AggregationsCalendarInterval, - MappingRuntimeFieldType, - TransformPutTransformRequest, -} from '@elastic/elasticsearch/lib/api/types'; +import { TransformPutTransformRequest } from '@elastic/elasticsearch/lib/api/types'; import { InvalidTransformError } from '../../../errors'; import { ALL_VALUE, apmTransactionDurationIndicatorSchema } from '../../../types/schema'; import { @@ -23,7 +19,7 @@ import { TransformGenerator } from '.'; const APM_SOURCE_INDEX = 'metrics-apm*'; -export class ApmTransactionDurationTransformGenerator implements TransformGenerator { +export class ApmTransactionDurationTransformGenerator extends TransformGenerator { public getTransformParams(slo: SLO): TransformPutTransformRequest { if (!apmTransactionDurationIndicatorSchema.is(slo.indicator)) { throw new InvalidTransformError(`Cannot handle SLO of indicator type: ${slo.indicator.type}`); @@ -33,7 +29,7 @@ export class ApmTransactionDurationTransformGenerator implements TransformGenera this.buildTransformId(slo), this.buildSource(slo, slo.indicator), this.buildDestination(), - this.buildGroupBy(), + this.buildCommonGroupBy(slo), this.buildAggregations(slo.indicator) ); } @@ -78,20 +74,7 @@ export class ApmTransactionDurationTransformGenerator implements TransformGenera return { index: APM_SOURCE_INDEX, - runtime_mappings: { - 'slo.id': { - type: 'keyword' as MappingRuntimeFieldType, - script: { - source: `emit('${slo.id}')`, - }, - }, - 'slo.revision': { - type: 'long' as MappingRuntimeFieldType, - script: { - source: `emit(${slo.revision})`, - }, - }, - }, + runtime_mappings: this.buildCommonRuntimeMappings(slo), query: { bool: { filter: [ @@ -114,27 +97,6 @@ export class ApmTransactionDurationTransformGenerator implements TransformGenera }; } - private buildGroupBy() { - return { - 'slo.id': { - terms: { - field: 'slo.id', - }, - }, - 'slo.revision': { - terms: { - field: 'slo.revision', - }, - }, - '@timestamp': { - date_histogram: { - field: '@timestamp', - calendar_interval: '1m' as AggregationsCalendarInterval, - }, - }, - }; - } - private buildAggregations(indicator: APMTransactionDurationIndicator) { const truncatedThreshold = Math.trunc(indicator.params['threshold.us']); diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_error_rate.ts b/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_error_rate.ts index 41ab541cdb4b..0da9f93e8d14 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_error_rate.ts +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_error_rate.ts @@ -5,11 +5,7 @@ * 2.0. */ -import { - AggregationsCalendarInterval, - MappingRuntimeFieldType, - TransformPutTransformRequest, -} from '@elastic/elasticsearch/lib/api/types'; +import { TransformPutTransformRequest } from '@elastic/elasticsearch/lib/api/types'; import { InvalidTransformError } from '../../../errors'; import { ALL_VALUE, apmTransactionErrorRateIndicatorSchema } from '../../../types/schema'; import { getSLOTransformTemplate } from '../../../assets/transform_templates/slo_transform_template'; @@ -25,7 +21,7 @@ const APM_SOURCE_INDEX = 'metrics-apm*'; const ALLOWED_STATUS_CODES = ['2xx', '3xx', '4xx', '5xx']; const DEFAULT_GOOD_STATUS_CODES = ['2xx', '3xx', '4xx']; -export class ApmTransactionErrorRateTransformGenerator implements TransformGenerator { +export class ApmTransactionErrorRateTransformGenerator extends TransformGenerator { public getTransformParams(slo: SLO): TransformPutTransformRequest { if (!apmTransactionErrorRateIndicatorSchema.is(slo.indicator)) { throw new InvalidTransformError(`Cannot handle SLO of indicator type: ${slo.indicator.type}`); @@ -35,7 +31,7 @@ export class ApmTransactionErrorRateTransformGenerator implements TransformGener this.buildTransformId(slo), this.buildSource(slo, slo.indicator), this.buildDestination(), - this.buildGroupBy(), + this.buildCommonGroupBy(slo), this.buildAggregations(slo, slo.indicator) ); } @@ -80,20 +76,7 @@ export class ApmTransactionErrorRateTransformGenerator implements TransformGener return { index: APM_SOURCE_INDEX, - runtime_mappings: { - 'slo.id': { - type: 'keyword' as MappingRuntimeFieldType, - script: { - source: `emit('${slo.id}')`, - }, - }, - 'slo.revision': { - type: 'long' as MappingRuntimeFieldType, - script: { - source: `emit(${slo.revision})`, - }, - }, - }, + runtime_mappings: this.buildCommonRuntimeMappings(slo), query: { bool: { filter: [ @@ -116,27 +99,6 @@ export class ApmTransactionErrorRateTransformGenerator implements TransformGener }; } - private buildGroupBy() { - return { - 'slo.id': { - terms: { - field: 'slo.id', - }, - }, - 'slo.revision': { - terms: { - field: 'slo.revision', - }, - }, - '@timestamp': { - date_histogram: { - field: '@timestamp', - calendar_interval: '1m' as AggregationsCalendarInterval, - }, - }, - }; - } - private buildAggregations(slo: SLO, indicator: APMTransactionErrorRateIndicator) { const goodStatusCodesFilter = this.getGoodStatusCodesFilter(indicator.params.good_status_codes); diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/kql_custom.ts b/x-pack/plugins/observability/server/services/slo/transform_generators/kql_custom.ts index b444a196960d..c2228b6a3095 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_generators/kql_custom.ts +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/kql_custom.ts @@ -5,11 +5,7 @@ * 2.0. */ -import { - AggregationsCalendarInterval, - MappingRuntimeFieldType, - TransformPutTransformRequest, -} from '@elastic/elasticsearch/lib/api/types'; +import { TransformPutTransformRequest } from '@elastic/elasticsearch/lib/api/types'; import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; import { InvalidTransformError } from '../../../errors'; @@ -23,7 +19,7 @@ import { } from '../../../assets/constants'; import { KQLCustomIndicator, SLO } from '../../../types/models'; -export class KQLCustomTransformGenerator implements TransformGenerator { +export class KQLCustomTransformGenerator extends TransformGenerator { public getTransformParams(slo: SLO): TransformPutTransformRequest { if (!kqlCustomIndicatorSchema.is(slo.indicator)) { throw new InvalidTransformError(`Cannot handle SLO of indicator type: ${slo.indicator.type}`); @@ -33,7 +29,7 @@ export class KQLCustomTransformGenerator implements TransformGenerator { this.buildTransformId(slo), this.buildSource(slo, slo.indicator), this.buildDestination(), - this.buildGroupBy(), + this.buildCommonGroupBy(slo), this.buildAggregations(slo, slo.indicator) ); } @@ -46,20 +42,7 @@ export class KQLCustomTransformGenerator implements TransformGenerator { const filter = getElastichsearchQueryOrThrow(indicator.params.query_filter); return { index: indicator.params.index, - runtime_mappings: { - 'slo.id': { - type: 'keyword' as MappingRuntimeFieldType, - script: { - source: `emit('${slo.id}')`, - }, - }, - 'slo.revision': { - type: 'long' as MappingRuntimeFieldType, - script: { - source: `emit(${slo.revision})`, - }, - }, - }, + runtime_mappings: this.buildCommonRuntimeMappings(slo), query: filter, }; } @@ -71,27 +54,6 @@ export class KQLCustomTransformGenerator implements TransformGenerator { }; } - private buildGroupBy() { - return { - 'slo.id': { - terms: { - field: 'slo.id', - }, - }, - 'slo.revision': { - terms: { - field: 'slo.revision', - }, - }, - '@timestamp': { - date_histogram: { - field: '@timestamp', - calendar_interval: '1m' as AggregationsCalendarInterval, - }, - }, - }; - } - private buildAggregations(slo: SLO, indicator: KQLCustomIndicator) { const numerator = getElastichsearchQueryOrThrow(indicator.params.numerator); const denominator = getElastichsearchQueryOrThrow(indicator.params.denominator); diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/transform_generator.ts b/x-pack/plugins/observability/server/services/slo/transform_generators/transform_generator.ts index 3965e809373c..cd0ceaa602c2 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_generators/transform_generator.ts +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/transform_generator.ts @@ -5,9 +5,147 @@ * 2.0. */ -import { TransformPutTransformRequest } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { MappingRuntimeFieldType } from '@elastic/elasticsearch/lib/api/types'; +import { + AggregationsCalendarInterval, + TransformPutTransformRequest, +} from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { + calendarAlignedTimeWindowSchema, + rollingTimeWindowSchema, + timeslicesBudgetingMethodSchema, +} from '../../../types/schema'; import { SLO } from '../../../types/models'; -export interface TransformGenerator { - getTransformParams(slo: SLO): TransformPutTransformRequest; +export abstract class TransformGenerator { + public abstract getTransformParams(slo: SLO): TransformPutTransformRequest; + + public buildCommonRuntimeMappings(slo: SLO) { + return { + 'slo.id': { + type: 'keyword' as MappingRuntimeFieldType, + script: { + source: `emit('${slo.id}')`, + }, + }, + 'slo.revision': { + type: 'long' as MappingRuntimeFieldType, + script: { + source: `emit(${slo.revision})`, + }, + }, + 'slo._internal.name': { + type: 'keyword' as MappingRuntimeFieldType, + script: { + source: `emit('${slo.name}')`, + }, + }, + 'slo._internal.budgeting_method': { + type: 'keyword' as MappingRuntimeFieldType, + script: { + source: `emit('${slo.budgeting_method}')`, + }, + }, + 'slo._internal.objective.target': { + type: 'double' as MappingRuntimeFieldType, + script: { + source: `emit(${slo.objective.target})`, + }, + }, + ...(timeslicesBudgetingMethodSchema.is(slo.budgeting_method) && { + 'slo._internal.objective.timeslice_target': { + type: 'double' as MappingRuntimeFieldType, + script: { + source: `emit(${slo.objective.timeslice_target})`, + }, + }, + 'slo._internal.objective.timeslice_window': { + type: 'keyword' as MappingRuntimeFieldType, + script: { + source: `emit('${slo.objective.timeslice_window?.format()}')`, + }, + }, + }), + 'slo._internal.time_window.duration': { + type: 'keyword' as MappingRuntimeFieldType, + script: { + source: `emit('${slo.time_window.duration.format()}')`, + }, + }, + ...(calendarAlignedTimeWindowSchema.is(slo.time_window) && { + 'slo._internal.time_window.is_rolling': { + type: 'boolean' as MappingRuntimeFieldType, + script: { + source: `emit(false)`, + }, + }, + }), + ...(rollingTimeWindowSchema.is(slo.time_window) && { + 'slo._internal.time_window.is_rolling': { + type: 'boolean' as MappingRuntimeFieldType, + script: { + source: `emit(true)`, + }, + }, + }), + }; + } + + public buildCommonGroupBy(slo: SLO) { + return { + 'slo.id': { + terms: { + field: 'slo.id', + }, + }, + 'slo.revision': { + terms: { + field: 'slo.revision', + }, + }, + 'slo._internal.name': { + terms: { + field: 'slo._internal.name', + }, + }, + 'slo._internal.budgeting_method': { + terms: { + field: 'slo._internal.budgeting_method', + }, + }, + 'slo._internal.objective.target': { + terms: { + field: 'slo._internal.objective.target', + }, + }, + 'slo._internal.time_window.duration': { + terms: { + field: 'slo._internal.time_window.duration', + }, + }, + 'slo._internal.time_window.is_rolling': { + terms: { + field: 'slo._internal.time_window.is_rolling', + }, + }, + ...(timeslicesBudgetingMethodSchema.is(slo.budgeting_method) && { + 'slo._internal.objective.timeslice_target': { + terms: { + field: 'slo._internal.objective.timeslice_target', + }, + }, + 'slo._internal.objective.timeslice_window': { + terms: { + field: 'slo._internal.objective.timeslice_window', + }, + }, + }), + '@timestamp': { + date_histogram: { + field: '@timestamp', + calendar_interval: '1m' as AggregationsCalendarInterval, + }, + }, + }; + } } diff --git a/x-pack/plugins/observability/server/services/slo/transform_manager.test.ts b/x-pack/plugins/observability/server/services/slo/transform_manager.test.ts index 1951888d734f..696e04f5c13b 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_manager.test.ts +++ b/x-pack/plugins/observability/server/services/slo/transform_manager.test.ts @@ -138,13 +138,13 @@ describe('TransformManager', () => { }); }); -class DummyTransformGenerator implements TransformGenerator { +class DummyTransformGenerator extends TransformGenerator { getTransformParams(slo: SLO): TransformPutTransformRequest { return {} as TransformPutTransformRequest; } } -class FailTransformGenerator implements TransformGenerator { +class FailTransformGenerator extends TransformGenerator { getTransformParams(slo: SLO): TransformPutTransformRequest { throw new Error('Some error'); } diff --git a/x-pack/plugins/observability/server/types/models/duration.test.ts b/x-pack/plugins/observability/server/types/models/duration.test.ts index 4383c8e3ddd7..6f6b2dcb0c2d 100644 --- a/x-pack/plugins/observability/server/types/models/duration.test.ts +++ b/x-pack/plugins/observability/server/types/models/duration.test.ts @@ -20,6 +20,18 @@ describe('Duration', () => { expect(() => new Duration(1, 'z' as DurationUnit)).toThrow('invalid duration unit'); }); + describe('format', () => { + it('formats the duration correctly', () => { + expect(new Duration(1, DurationUnit.m).format()).toBe('1m'); + expect(new Duration(1, DurationUnit.h).format()).toBe('1h'); + expect(new Duration(1, DurationUnit.d).format()).toBe('1d'); + expect(new Duration(1, DurationUnit.w).format()).toBe('1w'); + expect(new Duration(1, DurationUnit.M).format()).toBe('1M'); + expect(new Duration(1, DurationUnit.Q).format()).toBe('1Q'); + expect(new Duration(1, DurationUnit.Y).format()).toBe('1Y'); + }); + }); + describe('isShorterThan', () => { it('returns true when the current duration is shorter than the other duration', () => { const short = new Duration(1, DurationUnit.m); diff --git a/x-pack/plugins/observability/server/types/models/duration.ts b/x-pack/plugins/observability/server/types/models/duration.ts index e34a748e30ba..aafed067f0ba 100644 --- a/x-pack/plugins/observability/server/types/models/duration.ts +++ b/x-pack/plugins/observability/server/types/models/duration.ts @@ -33,6 +33,10 @@ class Duration { const currentDurationMoment = moment.duration(this.value, toMomentUnitOfTime(this.unit)); return currentDurationMoment.asSeconds() < otherDurationMoment.asSeconds(); } + + format(): string { + return `${this.value}${this.unit}`; + } } const toMomentUnitOfTime = (unit: DurationUnit): moment.unitOfTime.Diff => { diff --git a/x-pack/plugins/observability/server/types/schema/duration.ts b/x-pack/plugins/observability/server/types/schema/duration.ts index b4fa5065063f..c7a3815140a6 100644 --- a/x-pack/plugins/observability/server/types/schema/duration.ts +++ b/x-pack/plugins/observability/server/types/schema/duration.ts @@ -24,7 +24,7 @@ const durationType = new t.Type( return t.failure(input, context); } }), - (duration: Duration): string => `${duration.value}${duration.unit}` + (duration: Duration): string => duration.format() ); export { durationType }; diff --git a/x-pack/plugins/observability/server/ui_settings.ts b/x-pack/plugins/observability/server/ui_settings.ts index a5b111569e31..f272db404b7b 100644 --- a/x-pack/plugins/observability/server/ui_settings.ts +++ b/x-pack/plugins/observability/server/ui_settings.ts @@ -197,13 +197,13 @@ export const uiSettings: Record = { [apmServiceInventoryOptimizedSorting]: { category: [observabilityFeatureId], name: i18n.translate('xpack.observability.apmServiceInventoryOptimizedSorting', { - defaultMessage: 'Optimize APM Service Inventory page load performance', + defaultMessage: 'Optimize services list load performance in APM', }), description: i18n.translate( 'xpack.observability.apmServiceInventoryOptimizedSortingDescription', { defaultMessage: - '{technicalPreviewLabel} Default APM Service Inventory page sort (for Services without Machine Learning applied) to sort by Service Name. {feedbackLink}.', + '{technicalPreviewLabel} Default APM Service Inventory and Storage Explorer pages sort (for Services without Machine Learning applied) to sort by Service Name. {feedbackLink}.', values: { technicalPreviewLabel: `[${technicalPreviewLabel}]`, feedbackLink: feedbackLink({ href: 'https://ela.st/feedback-apm-page-performance' }), diff --git a/x-pack/plugins/profiling/common/base64.ts b/x-pack/plugins/profiling/common/base64.ts new file mode 100644 index 000000000000..0d724c142271 --- /dev/null +++ b/x-pack/plugins/profiling/common/base64.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const safeBase64Decoder = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 0, 0, 0, 0, 63, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, + 45, 46, 47, 48, 49, 50, 51, 0, 0, 0, 0, 0, +]; + +export const safeBase64Encoder = + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234456789-_'; + +/* eslint no-bitwise: ["error", { "allow": ["&"] }] */ +export function charCodeAt(input: string, i: number): number { + return safeBase64Decoder[input.charCodeAt(i) & 0x7f]; +} diff --git a/x-pack/plugins/profiling/common/profiling.test.ts b/x-pack/plugins/profiling/common/profiling.test.ts index e51478bc62fc..df014bada46b 100644 --- a/x-pack/plugins/profiling/common/profiling.test.ts +++ b/x-pack/plugins/profiling/common/profiling.test.ts @@ -6,12 +6,23 @@ */ import { + createStackFrameID, createStackFrameMetadata, FrameType, + getAddressFromStackFrameID, getCalleeFunction, getCalleeSource, + getFileIDFromStackFrameID, } from './profiling'; +describe('Stack frame operations', () => { + test('decode stack frame ID', () => { + const frameID = createStackFrameID('ABCDEFGHIJKLMNOPQRSTUw', 123456789); + expect(getAddressFromStackFrameID(frameID)).toEqual(123456789); + expect(getFileIDFromStackFrameID(frameID)).toEqual('ABCDEFGHIJKLMNOPQRSTUw'); + }); +}); + describe('Stack frame metadata operations', () => { test('metadata has executable and function names', () => { const metadata = createStackFrameMetadata({ diff --git a/x-pack/plugins/profiling/common/profiling.ts b/x-pack/plugins/profiling/common/profiling.ts index 73c0e908ea27..a7a5a890c6b5 100644 --- a/x-pack/plugins/profiling/common/profiling.ts +++ b/x-pack/plugins/profiling/common/profiling.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { charCodeAt, safeBase64Encoder } from './base64'; + export type StackTraceID = string; export type StackFrameID = string; export type FileID = string; @@ -16,6 +18,37 @@ export function createStackFrameID(fileID: FileID, addressOrLine: number): Stack return buf.toString('base64url'); } +/* eslint no-bitwise: ["error", { "allow": ["&"] }] */ +export function getFileIDFromStackFrameID(frameID: StackFrameID): FileID { + return frameID.slice(0, 21) + safeBase64Encoder[frameID.charCodeAt(21) & 0x30]; +} + +/* eslint no-bitwise: ["error", { "allow": ["<<=", "&"] }] */ +export function getAddressFromStackFrameID(frameID: StackFrameID): number { + let address = charCodeAt(frameID, 21) & 0xf; + address <<= 6; + address += charCodeAt(frameID, 22); + address <<= 6; + address += charCodeAt(frameID, 23); + address <<= 6; + address += charCodeAt(frameID, 24); + address <<= 6; + address += charCodeAt(frameID, 25); + address <<= 6; + address += charCodeAt(frameID, 26); + address <<= 6; + address += charCodeAt(frameID, 27); + address <<= 6; + address += charCodeAt(frameID, 28); + address <<= 6; + address += charCodeAt(frameID, 29); + address <<= 6; + address += charCodeAt(frameID, 30); + address <<= 6; + address += charCodeAt(frameID, 31); + return address; +} + export enum FrameType { Unsymbolized = 0, Python, diff --git a/x-pack/plugins/profiling/common/run_length_encoding.test.ts b/x-pack/plugins/profiling/common/run_length_encoding.test.ts new file mode 100644 index 000000000000..22f3b5141b0a --- /dev/null +++ b/x-pack/plugins/profiling/common/run_length_encoding.test.ts @@ -0,0 +1,166 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { runLengthDecode, runLengthDecodeBase64Url, runLengthEncode } from './run_length_encoding'; + +describe('Run-length encoding operations', () => { + test('run length is fully reversible', () => { + const tests: number[][] = [[], [0], [0, 1, 2, 3], [0, 1, 1, 2, 2, 2, 3, 3, 3, 3]]; + + for (const t of tests) { + expect(runLengthDecode(runLengthEncode(t))).toEqual(t); + } + }); + + test('runLengthDecode with optional parameter', () => { + const tests: Array<{ + bytes: Buffer; + expected: number[]; + }> = [ + { + bytes: Buffer.from([0x5, 0x0, 0x2, 0x2]), + expected: [0, 0, 0, 0, 0, 2, 2], + }, + { + bytes: Buffer.from([0x1, 0x8]), + expected: [8], + }, + ]; + + for (const t of tests) { + expect(runLengthDecode(t.bytes, t.expected.length)).toEqual(t.expected); + } + }); + + test('runLengthDecode with larger output than available input', () => { + const bytes = Buffer.from([0x5, 0x0, 0x2, 0x2]); + const decoded = [0, 0, 0, 0, 0, 2, 2]; + const expected = decoded.concat(Array(decoded.length).fill(0)); + + expect(runLengthDecode(bytes, expected.length)).toEqual(expected); + }); + + test('runLengthDecode without optional parameter', () => { + const tests: Array<{ + bytes: Buffer; + expected: number[]; + }> = [ + { + bytes: Buffer.from([0x5, 0x0, 0x2, 0x2]), + expected: [0, 0, 0, 0, 0, 2, 2], + }, + { + bytes: Buffer.from([0x1, 0x8]), + expected: [8], + }, + ]; + + for (const t of tests) { + expect(runLengthDecode(t.bytes)).toEqual(t.expected); + } + }); + + test('runLengthDecode works for very long runs', () => { + const tests: Array<{ + bytes: Buffer; + expected: number[]; + }> = [ + { + bytes: Buffer.from([0x5, 0x2, 0xff, 0x0]), + expected: [2, 2, 2, 2, 2].concat(Array(255).fill(0)), + }, + { + bytes: Buffer.from([0xff, 0x2, 0x1, 0x2]), + expected: Array(256).fill(2), + }, + ]; + + for (const t of tests) { + expect(runLengthDecode(t.bytes)).toEqual(t.expected); + } + }); + + test('runLengthEncode works for very long runs', () => { + const tests: Array<{ + numbers: number[]; + expected: Buffer; + }> = [ + { + numbers: [2, 2, 2, 2, 2].concat(Array(255).fill(0)), + expected: Buffer.from([0x5, 0x2, 0xff, 0x0]), + }, + { + numbers: Array(256).fill(2), + expected: Buffer.from([0xff, 0x2, 0x1, 0x2]), + }, + ]; + + for (const t of tests) { + expect(runLengthEncode(t.numbers)).toEqual(t.expected); + } + }); + + test('runLengthDecodeBase64Url', () => { + const tests: Array<{ + data: string; + expected: number[]; + }> = [ + { + data: 'CQM', + expected: [3, 3, 3, 3, 3, 3, 3, 3, 3], + }, + { + data: 'EgMHBA', + expected: Array(18).fill(3).concat(Array(7).fill(4)), + }, + { + data: 'CAMfBQIDEAQ', + expected: Array(8) + .fill(3) + .concat(Array(31).fill(5)) + .concat([3, 3]) + .concat(Array(16).fill(4)), + }, + ]; + + for (const t of tests) { + expect(runLengthDecodeBase64Url(t.data, t.data.length, t.expected.length)).toEqual( + t.expected + ); + } + }); + + test('runLengthDecodeBase64Url with larger output than available input', () => { + const data = Buffer.from([0x5, 0x0, 0x3, 0x2]).toString('base64url'); + const decoded = [0, 0, 0, 0, 0, 2, 2, 2]; + const expected = decoded.concat(Array(decoded.length).fill(0)); + + expect(runLengthDecodeBase64Url(data, data.length, expected.length)).toEqual(expected); + }); + + test('runLengthDecodeBase64Url works for very long runs', () => { + const tests: Array<{ + data: string; + expected: number[]; + }> = [ + { + data: Buffer.from([0x5, 0x2, 0xff, 0x0]).toString('base64url'), + expected: [2, 2, 2, 2, 2].concat(Array(255).fill(0)), + }, + { + data: Buffer.from([0xff, 0x2, 0x1, 0x2]).toString('base64url'), + expected: Array(256).fill(2), + }, + ]; + + for (const t of tests) { + expect(runLengthDecodeBase64Url(t.data, t.data.length, t.expected.length)).toEqual( + t.expected + ); + } + }); +}); diff --git a/x-pack/plugins/profiling/common/run_length_encoding.ts b/x-pack/plugins/profiling/common/run_length_encoding.ts new file mode 100644 index 000000000000..28a34a94acd5 --- /dev/null +++ b/x-pack/plugins/profiling/common/run_length_encoding.ts @@ -0,0 +1,199 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { charCodeAt } from './base64'; + +// runLengthEncode run-length encodes the input array. +// +// The input is a list of uint8s. The output is a binary stream of +// 2-byte pairs (first byte is the length and the second byte is the +// binary representation of the object) in reverse order. +// +// E.g. uint8 array [0, 0, 0, 0, 0, 2, 2, 2] is converted into the byte +// array [5, 0, 3, 2]. +export function runLengthEncode(input: number[]): Buffer { + const output: number[] = []; + + if (input.length === 0) { + return Buffer.from(output); + } + + let count = 1; + let current = input[0]; + + for (let i = 1; i < input.length; i++) { + const next = input[i]; + + if (next === current && count < 255) { + count++; + continue; + } + + output.push(count, current); + + count = 1; + current = next; + } + + output.push(count, current); + + return Buffer.from(output); +} + +function copyNumber(target: number[], value: number, offset: number, end: number) { + for (let i = offset; i < end; i++) { + target[i] = value; + } +} + +// runLengthDecode decodes a run-length encoding for the input array. +// +// The input is a binary stream of 2-byte pairs (first byte is the length and the +// second byte is the binary representation of the object). The output is a list of +// uint8s. +// +// E.g. byte array [5, 0, 3, 2] is converted into an uint8 array like +// [0, 0, 0, 0, 0, 2, 2, 2]. +export function runLengthDecode(input: Buffer, outputSize?: number): number[] { + let size; + + if (typeof outputSize === 'undefined') { + size = 0; + for (let i = 0; i < input.length; i += 2) { + size += input[i]; + } + } else { + size = outputSize; + } + + const output: number[] = new Array(size); + + let idx = 0; + for (let i = 0; i < input.length; i += 2) { + for (let j = 0; j < input[i]; j++) { + output[idx] = input[i + 1]; + idx++; + } + } + + // Due to truncation of the frame types for stacktraces longer than 255, + // the expected output size and the actual decoded size can be different. + // Ordinarily, these two values should be the same. + // + // We have decided to fill in the remainder of the output array with zeroes + // as a reasonable default. Without this step, the output array would have + // undefined values. + copyNumber(output, 0, idx, size); + + return output; +} + +// runLengthDecodeBase64Url decodes a run-length encoding for the +// base64-encoded input string. +// +// The input is a base64-encoded string. The output is a list of uint8s. +// +// E.g. string 'BQADAg' is converted into an uint8 array like +// [0, 0, 0, 0, 0, 2, 2, 2]. +// +// The motivating intent for this method is to unpack a base64-encoded +// run-length encoding without using intermediate storage. +// +// This method relies on these assumptions and details: +// - array encoded using run-length and base64 always returns string of length +// 0, 3, or 6 (mod 8) +// - since original array is composed of uint8s, we ignore Unicode codepoints +// - JavaScript bitwise operators operate on 32-bits so decoding must be done +// in 32-bit chunks + +/* eslint no-bitwise: ["error", { "allow": ["<<", ">>", ">>=", "&", "|"] }] */ +export function runLengthDecodeBase64Url(input: string, size: number, capacity: number): number[] { + const output = new Array(capacity); + const multipleOf8 = Math.floor(size / 8); + const remainder = size % 8; + + let n = 0; + let count = 0; + let value = 0; + let i = 0; + let j = 0; + + for (i = 0; i < multipleOf8; i += 8) { + n = + (charCodeAt(input, i) << 26) | + (charCodeAt(input, i + 1) << 20) | + (charCodeAt(input, i + 2) << 14) | + (charCodeAt(input, i + 3) << 8) | + (charCodeAt(input, i + 4) << 2) | + (charCodeAt(input, i + 5) >> 4); + + count = (n >> 24) & 0xff; + value = (n >> 16) & 0xff; + + copyNumber(output, value, j, j + count); + j += count; + + count = (n >> 8) & 0xff; + value = n & 0xff; + + copyNumber(output, value, j, j + count); + j += count; + + n = + ((charCodeAt(input, i + 5) & 0xf) << 12) | + (charCodeAt(input, i + 6) << 6) | + charCodeAt(input, i + 7); + + count = (n >> 8) & 0xff; + value = n & 0xff; + + copyNumber(output, value, j, j + count); + j += count; + } + + if (remainder === 6) { + n = + (charCodeAt(input, i) << 26) | + (charCodeAt(input, i + 1) << 20) | + (charCodeAt(input, i + 2) << 14) | + (charCodeAt(input, i + 3) << 8) | + (charCodeAt(input, i + 4) << 2) | + (charCodeAt(input, i + 5) >> 4); + + count = (n >> 24) & 0xff; + value = (n >> 16) & 0xff; + + copyNumber(output, value, j, j + count); + j += count; + + count = (n >> 8) & 0xff; + value = n & 0xff; + + copyNumber(output, value, j, j + count); + j += count; + } else if (remainder === 3) { + n = (charCodeAt(input, i) << 12) | (charCodeAt(input, i + 1) << 6) | charCodeAt(input, i + 2); + n >>= 2; + + count = (n >> 8) & 0xff; + value = n & 0xff; + + copyNumber(output, value, j, j + count); + j += count; + } + + // Due to truncation of the frame types for stacktraces longer than 255, + // the expected output size and the actual decoded size can be different. + // Ordinarily, these two values should be the same. + // + // We have decided to fill in the remainder of the output array with zeroes + // as a reasonable default. Without this step, the output array would have + // undefined values. + copyNumber(output, 0, j, capacity); + + return output; +} diff --git a/x-pack/plugins/profiling/public/utils/formatters/as_number.ts b/x-pack/plugins/profiling/public/utils/formatters/as_number.ts index f7b67bafbf7f..365cd876ad69 100644 --- a/x-pack/plugins/profiling/public/utils/formatters/as_number.ts +++ b/x-pack/plugins/profiling/public/utils/formatters/as_number.ts @@ -11,18 +11,18 @@ export function asNumber(value: number): string { } value = Math.round(value * 100) / 100; - if (value < 0.01) { + if (Math.abs(value) < 0.01) { return '~0.00'; } - if (value < 1e3) { + if (Math.abs(value) < 1e3) { return value.toString(); } - if (value < 1e6) { + if (Math.abs(value) < 1e6) { return `${asNumber(value / 1e3)}k`; } - if (value < 1e9) { + if (Math.abs(value) < 1e9) { return `${asNumber(value / 1e6)}m`; } diff --git a/x-pack/plugins/profiling/server/routes/stacktrace.test.ts b/x-pack/plugins/profiling/server/routes/stacktrace.test.ts index 5dd3f1985a35..91b55312928c 100644 --- a/x-pack/plugins/profiling/server/routes/stacktrace.test.ts +++ b/x-pack/plugins/profiling/server/routes/stacktrace.test.ts @@ -6,12 +6,8 @@ */ import { createStackFrameID, StackTrace } from '../../common/profiling'; -import { - decodeStackTrace, - EncodedStackTrace, - runLengthDecode, - runLengthEncode, -} from './stacktrace'; +import { runLengthEncode } from '../../common/run_length_encoding'; +import { decodeStackTrace, EncodedStackTrace } from './stacktrace'; enum fileID { A = 'aQpJmTLWydNvOapSFZOwKg', @@ -89,100 +85,4 @@ describe('Stack trace operations', () => { expect(decodeStackTrace(t.original)).toEqual(t.expected); } }); - - test('run length is fully reversible', () => { - const tests: number[][] = [[], [0], [0, 1, 2, 3], [0, 1, 1, 2, 2, 2, 3, 3, 3, 3]]; - - for (const t of tests) { - expect(runLengthDecode(runLengthEncode(t))).toEqual(t); - } - }); - - test('runLengthDecode with optional parameter', () => { - const tests: Array<{ - bytes: Buffer; - expected: number[]; - }> = [ - { - bytes: Buffer.from([0x5, 0x0, 0x2, 0x2]), - expected: [0, 0, 0, 0, 0, 2, 2], - }, - { - bytes: Buffer.from([0x1, 0x8]), - expected: [8], - }, - ]; - - for (const t of tests) { - expect(runLengthDecode(t.bytes, t.expected.length)).toEqual(t.expected); - } - }); - - test('runLengthDecode with larger output than available input', () => { - const bytes = Buffer.from([0x5, 0x0, 0x2, 0x2]); - const decoded = [0, 0, 0, 0, 0, 2, 2]; - const expected = decoded.concat(Array(decoded.length).fill(0)); - - expect(runLengthDecode(bytes, expected.length)).toEqual(expected); - }); - - test('runLengthDecode without optional parameter', () => { - const tests: Array<{ - bytes: Buffer; - expected: number[]; - }> = [ - { - bytes: Buffer.from([0x5, 0x0, 0x2, 0x2]), - expected: [0, 0, 0, 0, 0, 2, 2], - }, - { - bytes: Buffer.from([0x1, 0x8]), - expected: [8], - }, - ]; - - for (const t of tests) { - expect(runLengthDecode(t.bytes)).toEqual(t.expected); - } - }); - - test('runLengthDecode works for very long runs', () => { - const tests: Array<{ - bytes: Buffer; - expected: number[]; - }> = [ - { - bytes: Buffer.from([0x5, 0x2, 0xff, 0x0]), - expected: [2, 2, 2, 2, 2].concat(Array(255).fill(0)), - }, - { - bytes: Buffer.from([0xff, 0x2, 0x1, 0x2]), - expected: Array(256).fill(2), - }, - ]; - - for (const t of tests) { - expect(runLengthDecode(t.bytes)).toEqual(t.expected); - } - }); - - test('runLengthEncode works for very long runs', () => { - const tests: Array<{ - numbers: number[]; - expected: Buffer; - }> = [ - { - numbers: [2, 2, 2, 2, 2].concat(Array(255).fill(0)), - expected: Buffer.from([0x5, 0x2, 0xff, 0x0]), - }, - { - numbers: Array(256).fill(2), - expected: Buffer.from([0xff, 0x2, 0x1, 0x2]), - }, - ]; - - for (const t of tests) { - expect(runLengthEncode(t.numbers)).toEqual(t.expected); - } - }); }); diff --git a/x-pack/plugins/profiling/server/routes/stacktrace.ts b/x-pack/plugins/profiling/server/routes/stacktrace.ts index 0f513023fa82..8c5b668e2535 100644 --- a/x-pack/plugins/profiling/server/routes/stacktrace.ts +++ b/x-pack/plugins/profiling/server/routes/stacktrace.ts @@ -21,11 +21,14 @@ import { emptyStackFrame, Executable, FileID, + getAddressFromStackFrameID, + getFileIDFromStackFrameID, StackFrame, StackFrameID, StackTrace, StackTraceID, } from '../../common/profiling'; +import { runLengthDecodeBase64Url } from '../../common/run_length_encoding'; import { ProfilingESClient } from '../utils/create_profiling_es_client'; import { withProfilingSpan } from '../utils/with_profiling_span'; import { DownsampledEventsIndex } from './downsampling'; @@ -52,87 +55,6 @@ export type EncodedStackTrace = DedotObject<{ [ProfilingESField.StacktraceFrameTypes]: string; }>; -// runLengthEncode run-length encodes the input array. -// -// The input is a list of uint8s. The output is a binary stream of -// 2-byte pairs (first byte is the length and the second byte is the -// binary representation of the object) in reverse order. -// -// E.g. uint8 array [0, 0, 0, 0, 0, 2, 2, 2] is converted into the byte -// array [5, 0, 3, 2]. -export function runLengthEncode(input: number[]): Buffer { - const output: number[] = []; - - if (input.length === 0) { - return Buffer.from(output); - } - - let count = 1; - let current = input[0]; - - for (let i = 1; i < input.length; i++) { - const next = input[i]; - - if (next === current && count < 255) { - count++; - continue; - } - - output.push(count, current); - - count = 1; - current = next; - } - - output.push(count, current); - - return Buffer.from(output); -} - -// runLengthDecode decodes a run-length encoding for the input array. -// -// The input is a binary stream of 2-byte pairs (first byte is the length and the -// second byte is the binary representation of the object). The output is a list of -// uint8s. -// -// E.g. byte array [5, 0, 3, 2] is converted into an uint8 array like -// [0, 0, 0, 0, 0, 2, 2, 2]. -export function runLengthDecode(input: Buffer, outputSize?: number): number[] { - let size; - - if (typeof outputSize === 'undefined') { - size = 0; - for (let i = 0; i < input.length; i += 2) { - size += input[i]; - } - } else { - size = outputSize; - } - - const output: number[] = new Array(size); - - let idx = 0; - for (let i = 0; i < input.length; i += 2) { - for (let j = 0; j < input[i]; j++) { - output[idx] = input[i + 1]; - idx++; - } - } - - // Due to truncation of the frame types for stacktraces longer than 255, - // the expected output size and the actual decoded size can be different. - // Ordinarily, these two values should be the same. - // - // We have decided to fill in the remainder of the output array with zeroes - // as a reasonable default. Without this step, the output array would have - // undefined values. - for (let i = idx; i < size; i++) { - output[i] = 0; - } - - return output; -} - // decodeStackTrace unpacks an encoded stack trace from Elasticsearch export function decodeStackTrace(input: EncodedStackTrace): StackTrace { const inputFrameIDs = input.Stacktrace.frame.ids; @@ -152,19 +74,15 @@ export function decodeStackTrace(input: EncodedStackTrace): StackTrace { // However, since the file ID is base64-encoded using 21.33 bytes // (16 * 4 / 3), then the 22 bytes have an extra 4 bits from the // address (see diagram in definition of EncodedStackTrace). - for (let i = 0; i < countsFrameIDs; i++) { - const pos = i * BASE64_FRAME_ID_LENGTH; + for (let i = 0, pos = 0; i < countsFrameIDs; i++, pos += BASE64_FRAME_ID_LENGTH) { const frameID = inputFrameIDs.slice(pos, pos + BASE64_FRAME_ID_LENGTH); - const buf = Buffer.from(frameID, 'base64url'); - - fileIDs[i] = buf.toString('base64url', 0, 16); - addressOrLines[i] = Number(buf.readBigUInt64BE(16)); frameIDs[i] = frameID; + fileIDs[i] = getFileIDFromStackFrameID(frameID); + addressOrLines[i] = getAddressFromStackFrameID(frameID); } // Step 2: Convert the run-length byte encoding into a list of uint8s. - const types = Buffer.from(inputFrameTypes, 'base64url'); - const typeIDs = runLengthDecode(types, countsFrameIDs); + const typeIDs = runLengthDecodeBase64Url(inputFrameTypes, inputFrameTypes.length, countsFrameIDs); return { AddressOrLines: addressOrLines, diff --git a/x-pack/plugins/remote_clusters/__jest__/client_integration/list/remote_clusters_list.test.js b/x-pack/plugins/remote_clusters/__jest__/client_integration/list/remote_clusters_list.test.js index 63367cfd6d00..4f404ee65349 100644 --- a/x-pack/plugins/remote_clusters/__jest__/client_integration/list/remote_clusters_list.test.js +++ b/x-pack/plugins/remote_clusters/__jest__/client_integration/list/remote_clusters_list.test.js @@ -34,7 +34,7 @@ describe('', () => { const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/rollup/public/test/client_integration/job_create_clone.test.js b/x-pack/plugins/rollup/public/test/client_integration/job_create_clone.test.js index 7a4eae8d0841..a087cabe64d9 100644 --- a/x-pack/plugins/rollup/public/test/client_integration/job_create_clone.test.js +++ b/x-pack/plugins/rollup/public/test/client_integration/job_create_clone.test.js @@ -26,7 +26,7 @@ describe('Cloning a rollup job through create job wizard', () => { let startMock; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); startMock = coreMock.createStart(); setHttp(startMock.http); initDocumentation(docLinksServiceMock.createStartContract()); diff --git a/x-pack/plugins/rollup/public/test/client_integration/job_create_date_histogram.test.js b/x-pack/plugins/rollup/public/test/client_integration/job_create_date_histogram.test.js index 565badc85403..be3c1f09b625 100644 --- a/x-pack/plugins/rollup/public/test/client_integration/job_create_date_histogram.test.js +++ b/x-pack/plugins/rollup/public/test/client_integration/job_create_date_histogram.test.js @@ -23,7 +23,7 @@ describe('Create Rollup Job, step 2: Date histogram', () => { let startMock; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); startMock = coreMock.createStart(); setHttp(startMock.http); initDocumentation(docLinksServiceMock.createStartContract()); diff --git a/x-pack/plugins/rollup/public/test/client_integration/job_create_histogram.test.js b/x-pack/plugins/rollup/public/test/client_integration/job_create_histogram.test.js index 1166462a833d..af35dc9063c1 100644 --- a/x-pack/plugins/rollup/public/test/client_integration/job_create_histogram.test.js +++ b/x-pack/plugins/rollup/public/test/client_integration/job_create_histogram.test.js @@ -23,7 +23,7 @@ describe('Create Rollup Job, step 4: Histogram', () => { let startMock; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); startMock = coreMock.createStart(); setHttp(startMock.http); initDocumentation(docLinksServiceMock.createStartContract()); diff --git a/x-pack/plugins/rollup/public/test/client_integration/job_create_logistics.test.js b/x-pack/plugins/rollup/public/test/client_integration/job_create_logistics.test.js index 540b9b7bb0db..c22da0d2fa47 100644 --- a/x-pack/plugins/rollup/public/test/client_integration/job_create_logistics.test.js +++ b/x-pack/plugins/rollup/public/test/client_integration/job_create_logistics.test.js @@ -23,7 +23,7 @@ describe('Create Rollup Job, step 1: Logistics', () => { let startMock; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); startMock = coreMock.createStart(); setHttp(startMock.http); initDocumentation(docLinksServiceMock.createStartContract()); diff --git a/x-pack/plugins/rollup/public/test/client_integration/job_create_metrics.test.js b/x-pack/plugins/rollup/public/test/client_integration/job_create_metrics.test.js index 9f8b108049e2..6e4ffea0367a 100644 --- a/x-pack/plugins/rollup/public/test/client_integration/job_create_metrics.test.js +++ b/x-pack/plugins/rollup/public/test/client_integration/job_create_metrics.test.js @@ -23,7 +23,7 @@ describe('Create Rollup Job, step 5: Metrics', () => { let startMock; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); startMock = coreMock.createStart(); setHttp(startMock.http); initDocumentation(docLinksServiceMock.createStartContract()); diff --git a/x-pack/plugins/rollup/public/test/client_integration/job_create_review.test.js b/x-pack/plugins/rollup/public/test/client_integration/job_create_review.test.js index 4927233b958b..36d33d6ab187 100644 --- a/x-pack/plugins/rollup/public/test/client_integration/job_create_review.test.js +++ b/x-pack/plugins/rollup/public/test/client_integration/job_create_review.test.js @@ -35,7 +35,7 @@ describe('Create Rollup Job, step 6: Review', () => { let component; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); startMock = coreMock.createStart(); setHttp(startMock.http); }); diff --git a/x-pack/plugins/rollup/public/test/client_integration/job_create_terms.test.js b/x-pack/plugins/rollup/public/test/client_integration/job_create_terms.test.js index e4998d7e41ca..6535b88311db 100644 --- a/x-pack/plugins/rollup/public/test/client_integration/job_create_terms.test.js +++ b/x-pack/plugins/rollup/public/test/client_integration/job_create_terms.test.js @@ -22,7 +22,7 @@ describe('Create Rollup Job, step 3: Terms', () => { let startMock; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); startMock = coreMock.createStart(); setHttp(startMock.http); initDocumentation(docLinksServiceMock.createStartContract()); diff --git a/x-pack/plugins/rollup/public/test/client_integration/job_list.test.js b/x-pack/plugins/rollup/public/test/client_integration/job_list.test.js index ccb177c17a14..5cd82054ce0e 100644 --- a/x-pack/plugins/rollup/public/test/client_integration/job_list.test.js +++ b/x-pack/plugins/rollup/public/test/client_integration/job_list.test.js @@ -38,7 +38,7 @@ describe('', () => { let startMock; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); startMock = coreMock.createStart(); setHttp(startMock.http); initDocumentation(docLinksServiceMock.createStartContract()); diff --git a/x-pack/plugins/rollup/public/test/client_integration/job_list_clone.test.js b/x-pack/plugins/rollup/public/test/client_integration/job_list_clone.test.js index 03833cb3a02e..20728477de7f 100644 --- a/x-pack/plugins/rollup/public/test/client_integration/job_list_clone.test.js +++ b/x-pack/plugins/rollup/public/test/client_integration/job_list_clone.test.js @@ -31,7 +31,7 @@ describe('Smoke test cloning an existing rollup job from job list', () => { let startMock; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); startMock = coreMock.createStart(); setHttp(startMock.http); }); diff --git a/x-pack/plugins/runtime_fields/public/components/runtime_field_editor/runtime_field_editor.test.tsx b/x-pack/plugins/runtime_fields/public/components/runtime_field_editor/runtime_field_editor.test.tsx index 4a7f8d07db7e..7dece57a4ea1 100644 --- a/x-pack/plugins/runtime_fields/public/components/runtime_field_editor/runtime_field_editor.test.tsx +++ b/x-pack/plugins/runtime_fields/public/components/runtime_field_editor/runtime_field_editor.test.tsx @@ -37,7 +37,7 @@ describe('Runtime field editor', () => { const lastOnChangeCall = (): FormState[] => onChange.mock.calls[onChange.mock.calls.length - 1]; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/security/public/analytics/analytics_service.test.ts b/x-pack/plugins/security/public/analytics/analytics_service.test.ts index be13fa25c1c8..19643e68b420 100644 --- a/x-pack/plugins/security/public/analytics/analytics_service.test.ts +++ b/x-pack/plugins/security/public/analytics/analytics_service.test.ts @@ -57,7 +57,7 @@ describe('AnalyticsService', () => { }); it('throttle reporting of the authentication type events', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); const mockCore = coreMock.createStart(); mockCore.http.post.mockResolvedValue({ signature: 'some-signature', timestamp: 1234 }); diff --git a/x-pack/plugins/security/public/management/roles/__fixtures__/kibana_features.ts b/x-pack/plugins/security/public/management/roles/__fixtures__/kibana_features.ts index 282bf7beb68b..f33b8659fb27 100644 --- a/x-pack/plugins/security/public/management/roles/__fixtures__/kibana_features.ts +++ b/x-pack/plugins/security/public/management/roles/__fixtures__/kibana_features.ts @@ -226,4 +226,31 @@ export const kibanaFeatures = [ }, ], }), + createFeature({ + id: 'with_require_all_spaces_sub_features', + name: 'Require all spaces Sub Features', + subFeatures: [ + { + name: 'Require all spaces Sub Feature', + requireAllSpaces: true, + privilegeGroups: [ + { + groupType: 'mutually_exclusive', + privileges: [ + { + id: 'cool_toggle_1', + name: 'Cool toggle 1', + includeIn: 'read', + savedObject: { + all: [], + read: [], + }, + ui: ['cool_toggle_1-ui'], + }, + ], + }, + ], + }, + ], + }), ]; diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privilege_form.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privilege_form.test.tsx index af5ef0a0ccb0..7c81bb4a00de 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privilege_form.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privilege_form.test.tsx @@ -259,7 +259,7 @@ describe('field level security', () => { }); test('does not query for available fields when a request is already in flight', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); const testProps = { ...props, diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table.test.tsx index f3b63bf32b5f..7f5ac97e41ed 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table.test.tsx @@ -107,6 +107,10 @@ describe('FeatureTable', () => { primaryFeaturePrivilege: 'none', subFeaturePrivileges: [], }, + with_require_all_spaces_sub_features: { + primaryFeaturePrivilege: 'none', + subFeaturePrivileges: [], + }, }); }); @@ -157,6 +161,14 @@ describe('FeatureTable', () => { } : { subFeaturePrivileges: [] }), }, + with_require_all_spaces_sub_features: { + primaryFeaturePrivilege: 'all', + ...(canCustomizeSubFeaturePrivileges + ? { + subFeaturePrivileges: ['cool_toggle_1'], + } + : { subFeaturePrivileges: [] }), + }, }); }); @@ -208,6 +220,10 @@ describe('FeatureTable', () => { } : { subFeaturePrivileges: [] }), }, + with_require_all_spaces_sub_features: { + primaryFeaturePrivilege: 'none', + subFeaturePrivileges: [], + }, }); }); @@ -302,6 +318,10 @@ describe('FeatureTable', () => { primaryFeaturePrivilege: 'read', subFeaturePrivileges: ['cool_all'], }, + with_require_all_spaces_sub_features: { + primaryFeaturePrivilege: 'none', + subFeaturePrivileges: [], + }, }); }); @@ -684,6 +704,10 @@ describe('FeatureTable', () => { 'cool_all', ], }, + with_require_all_spaces_sub_features: { + primaryFeaturePrivilege: 'none', + subFeaturePrivileges: [], + }, }); }); @@ -722,6 +746,10 @@ describe('FeatureTable', () => { primaryFeaturePrivilege: 'all', subFeaturePrivileges: [], }, + with_require_all_spaces_sub_features: { + primaryFeaturePrivilege: 'none', + subFeaturePrivileges: [], + }, }); }); @@ -760,6 +788,10 @@ describe('FeatureTable', () => { primaryFeaturePrivilege: 'read', subFeaturePrivileges: [], }, + with_require_all_spaces_sub_features: { + primaryFeaturePrivilege: 'none', + subFeaturePrivileges: [], + }, }); }); @@ -888,6 +920,10 @@ describe('FeatureTable', () => { primaryFeaturePrivilege: 'none', subFeaturePrivileges: [], }, + with_require_all_spaces_sub_features: { + primaryFeaturePrivilege: 'none', + subFeaturePrivileges: [], + }, }); }); diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table.tsx index 505dd8e70024..33f9f2879b4f 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table.tsx @@ -250,6 +250,7 @@ export class FeatureTable extends Component { selectedFeaturePrivileges={ this.props.role.kibana[this.props.privilegeIndex].feature[feature.id] ?? [] } + allSpacesSelected={this.props.allSpacesSelected} disabled={this.props.disabled} licenseAllowsSubFeatPrivCustomization={ this.props.canCustomizeSubFeaturePrivileges diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table_expanded_row.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table_expanded_row.test.tsx index 8e435dc43ef2..c7ab5a2be789 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table_expanded_row.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table_expanded_row.test.tsx @@ -49,6 +49,7 @@ describe('FeatureTableExpandedRow', () => { selectedFeaturePrivileges={['minimal_read']} onChange={jest.fn()} licenseAllowsSubFeatPrivCustomization={false} + allSpacesSelected={false} /> ); @@ -86,6 +87,7 @@ describe('FeatureTableExpandedRow', () => { selectedFeaturePrivileges={['none']} onChange={jest.fn()} licenseAllowsSubFeatPrivCustomization={true} + allSpacesSelected={false} /> ); @@ -118,6 +120,7 @@ describe('FeatureTableExpandedRow', () => { selectedFeaturePrivileges={['minimal_read']} onChange={jest.fn()} licenseAllowsSubFeatPrivCustomization={true} + allSpacesSelected={false} /> ); @@ -153,6 +156,7 @@ describe('FeatureTableExpandedRow', () => { selectedFeaturePrivileges={['read']} onChange={jest.fn()} licenseAllowsSubFeatPrivCustomization={true} + allSpacesSelected={false} /> ); @@ -186,6 +190,7 @@ describe('FeatureTableExpandedRow', () => { selectedFeaturePrivileges={['read']} onChange={jest.fn()} licenseAllowsSubFeatPrivCustomization={true} + allSpacesSelected={false} /> ); @@ -223,6 +228,7 @@ describe('FeatureTableExpandedRow', () => { selectedFeaturePrivileges={['read']} onChange={onChange} licenseAllowsSubFeatPrivCustomization={true} + allSpacesSelected={false} /> ); @@ -263,6 +269,7 @@ describe('FeatureTableExpandedRow', () => { selectedFeaturePrivileges={['minimal_read', 'cool_read', 'cool_toggle_2']} onChange={onChange} licenseAllowsSubFeatPrivCustomization={true} + allSpacesSelected={false} /> ); @@ -272,4 +279,76 @@ describe('FeatureTableExpandedRow', () => { expect(onChange).toHaveBeenCalledWith('with_sub_features', ['read']); }); + + it('require all spaces enabled and allSpacesSelected is false: option is disabled', () => { + const role = createRole([ + { + base: [], + feature: { + with_require_all_spaces_sub_features: ['cool_toggle_1'], + }, + spaces: ['foo'], + }, + ]); + + const kibanaPrivileges = createKibanaPrivileges(kibanaFeatures); + const calculator = new PrivilegeFormCalculator(kibanaPrivileges, role); + const feature = kibanaPrivileges.getSecuredFeature('with_require_all_spaces_sub_features'); + const onChange = jest.fn(); + + const wrapper = mountWithIntl( + + ); + + act(() => { + findTestSubject(wrapper, 'customizeSubFeaturePrivileges').simulate('click'); + }); + + const object = wrapper.find('SubFeatureForm'); + expect(object.props()).toMatchObject({ disabled: true }); + }); + + it('require all spaces enabled and allSpacesSelected is true: option is enabled', () => { + const role = createRole([ + { + base: [], + feature: { + with_require_all_spaces_sub_features: ['cool_toggle_1'], + }, + spaces: ['foo'], + }, + ]); + + const kibanaPrivileges = createKibanaPrivileges(kibanaFeatures); + const calculator = new PrivilegeFormCalculator(kibanaPrivileges, role); + const feature = kibanaPrivileges.getSecuredFeature('with_require_all_spaces_sub_features'); + const onChange = jest.fn(); + + const wrapper = mountWithIntl( + + ); + + act(() => { + findTestSubject(wrapper, 'customizeSubFeaturePrivileges').simulate('click'); + }); + + const object = wrapper.find('SubFeatureForm'); + expect(object.props()).toMatchObject({ disabled: false }); + }); }); diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table_expanded_row.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table_expanded_row.tsx index a0726ad2ef56..e5e28c454737 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table_expanded_row.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table_expanded_row.tsx @@ -21,6 +21,7 @@ interface Props { privilegeCalculator: PrivilegeFormCalculator; privilegeIndex: number; selectedFeaturePrivileges: string[]; + allSpacesSelected: boolean; disabled?: boolean; licenseAllowsSubFeatPrivCustomization: boolean; onChange: (featureId: string, featurePrivileges: string[]) => void; @@ -32,6 +33,7 @@ export const FeatureTableExpandedRow = ({ privilegeIndex, privilegeCalculator, selectedFeaturePrivileges, + allSpacesSelected, disabled, licenseAllowsSubFeatPrivCustomization, }: Props) => { @@ -110,6 +112,8 @@ export const FeatureTableExpandedRow = ({
{feature.getSubFeatures().map((subFeature) => { + const isDisabledDueToSpaceSelection = subFeature.requireAllSpaces && !allSpacesSelected; + return ( onChange(feature.id, updatedPrivileges)} selectedFeaturePrivileges={selectedFeaturePrivileges} - disabled={disabled || !isCustomizing} + disabled={disabled || !isCustomizing || isDisabledDueToSpaceSelection} /> ); diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/sub_feature_form.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/sub_feature_form.tsx index d123773fed1c..8af3bcdb2fef 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/sub_feature_form.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/sub_feature_form.tsx @@ -5,7 +5,14 @@ * 2.0. */ -import { EuiButtonGroup, EuiCheckbox, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; +import { + EuiButtonGroup, + EuiCheckbox, + EuiFlexGroup, + EuiFlexItem, + EuiIconTip, + EuiText, +} from '@elastic/eui'; import React from 'react'; import { i18n } from '@kbn/i18n'; @@ -33,6 +40,27 @@ export const SubFeatureForm = (props: Props) => { .getPrivilegeGroups() .filter((group) => group.privileges.length > 0); + const getTooltip = () => { + if (!props.subFeature.privilegesTooltip) { + return null; + } + const tooltipContent = ( + +

{props.subFeature.privilegesTooltip}

+
+ ); + return ( + + ); + }; + if (groupsWithPrivileges.length === 0) { return null; } @@ -40,7 +68,9 @@ export const SubFeatureForm = (props: Props) => { return ( - {props.subFeature.name} + + {props.subFeature.name} {getTooltip()} + {groupsWithPrivileges.map(renderPrivilegeGroup)} diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/privilege_summary_calculator.test.ts b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/privilege_summary_calculator.test.ts index a6714cb7a2d8..61be3af6eb1c 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/privilege_summary_calculator.test.ts +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/privilege_summary_calculator.test.ts @@ -53,6 +53,11 @@ describe('PrivilegeSummaryCalculator', () => { primary: undefined, subFeature: [], }, + with_require_all_spaces_sub_features: { + hasCustomizedSubFeaturePrivileges: false, + primary: undefined, + subFeature: [], + }, }); }); @@ -99,6 +104,13 @@ describe('PrivilegeSummaryCalculator', () => { }), subFeature: ['cool_all', 'cool_read', 'cool_toggle_1', 'cool_toggle_2'], }, + with_require_all_spaces_sub_features: { + hasCustomizedSubFeaturePrivileges: false, + primary: expect.objectContaining({ + id: 'all', + }), + subFeature: ['cool_toggle_1'], + }, }); }); @@ -155,6 +167,13 @@ describe('PrivilegeSummaryCalculator', () => { 'cool_excluded_toggle', ], }, + with_require_all_spaces_sub_features: { + hasCustomizedSubFeaturePrivileges: false, + primary: expect.objectContaining({ + id: 'all', + }), + subFeature: ['cool_toggle_1'], + }, }); }); @@ -214,6 +233,13 @@ describe('PrivilegeSummaryCalculator', () => { 'cool_excluded_toggle', ], }, + with_require_all_spaces_sub_features: { + hasCustomizedSubFeaturePrivileges: false, + primary: expect.objectContaining({ + id: 'all', + }), + subFeature: ['cool_toggle_1'], + }, }); }); @@ -255,6 +281,13 @@ describe('PrivilegeSummaryCalculator', () => { }), subFeature: ['cool_all', 'cool_read', 'cool_toggle_1', 'cool_toggle_2'], }, + with_require_all_spaces_sub_features: { + hasCustomizedSubFeaturePrivileges: false, + primary: expect.objectContaining({ + id: 'all', + }), + subFeature: ['cool_toggle_1'], + }, }); }); @@ -294,6 +327,11 @@ describe('PrivilegeSummaryCalculator', () => { primary: undefined, subFeature: [], }, + with_require_all_spaces_sub_features: { + hasCustomizedSubFeaturePrivileges: false, + primary: undefined, + subFeature: [], + }, }); }); @@ -333,6 +371,11 @@ describe('PrivilegeSummaryCalculator', () => { primary: undefined, subFeature: [], }, + with_require_all_spaces_sub_features: { + hasCustomizedSubFeaturePrivileges: false, + primary: undefined, + subFeature: [], + }, }); }); }); diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/privilege_summary_table.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/privilege_summary_table.test.tsx index d54866c88f7e..62ad9bc83b1c 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/privilege_summary_table.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/privilege_summary_table.test.tsx @@ -90,6 +90,15 @@ const expectNoPrivileges = (displayedPrivileges: any, expectSubFeatures: boolean }), }, }, + with_require_all_spaces_sub_features: { + '*': { + hasCustomizedSubFeaturePrivileges: false, + primaryFeaturePrivilege: 'None', + ...maybeExpectSubFeaturePrivileges(expectSubFeatures, { + 'Require all spaces Sub Feature': [], + }), + }, + }, }); }; @@ -248,6 +257,15 @@ describe('PrivilegeSummaryTable', () => { }), }, }, + with_require_all_spaces_sub_features: { + '*': { + hasCustomizedSubFeaturePrivileges: false, + primaryFeaturePrivilege: 'All', + ...maybeExpectSubFeaturePrivileges(allowSubFeaturePrivileges, { + 'Require all spaces Sub Feature': ['Cool toggle 1'], + }), + }, + }, }); }); @@ -310,6 +328,15 @@ describe('PrivilegeSummaryTable', () => { }), }, }, + with_require_all_spaces_sub_features: { + '*': { + hasCustomizedSubFeaturePrivileges: false, + primaryFeaturePrivilege: 'All', + ...maybeExpectSubFeaturePrivileges(allowSubFeaturePrivileges, { + 'Require all spaces Sub Feature': ['Cool toggle 1'], + }), + }, + }, }); }); @@ -370,6 +397,15 @@ describe('PrivilegeSummaryTable', () => { }), }, }, + with_require_all_spaces_sub_features: { + 'default, space-1': { + hasCustomizedSubFeaturePrivileges: false, + primaryFeaturePrivilege: 'All', + ...maybeExpectSubFeaturePrivileges(allowSubFeaturePrivileges, { + 'Require all spaces Sub Feature': ['Cool toggle 1'], + }), + }, + }, }); }); @@ -432,6 +468,15 @@ describe('PrivilegeSummaryTable', () => { }), }, }, + with_require_all_spaces_sub_features: { + 'default, space-1': { + hasCustomizedSubFeaturePrivileges: false, + primaryFeaturePrivilege: 'None', + ...maybeExpectSubFeaturePrivileges(allowSubFeaturePrivileges, { + 'Require all spaces Sub Feature': [], + }), + }, + }, }); }); @@ -522,6 +567,22 @@ describe('PrivilegeSummaryTable', () => { }), }, }, + with_require_all_spaces_sub_features: { + '*': { + hasCustomizedSubFeaturePrivileges: false, + primaryFeaturePrivilege: 'Read', + ...maybeExpectSubFeaturePrivileges(allowSubFeaturePrivileges, { + 'Require all spaces Sub Feature': ['Cool toggle 1'], + }), + }, + 'default, space-1': { + hasCustomizedSubFeaturePrivileges: false, + primaryFeaturePrivilege: 'All', + ...maybeExpectSubFeaturePrivileges(allowSubFeaturePrivileges, { + 'Require all spaces Sub Feature': ['Cool toggle 1'], + }), + }, + }, }); }); @@ -614,6 +675,22 @@ describe('PrivilegeSummaryTable', () => { }), }, }, + with_require_all_spaces_sub_features: { + '*': { + hasCustomizedSubFeaturePrivileges: false, + primaryFeaturePrivilege: 'Read', + ...maybeExpectSubFeaturePrivileges(allowSubFeaturePrivileges, { + 'Require all spaces Sub Feature': ['Cool toggle 1'], + }), + }, + 'default, space-1': { + hasCustomizedSubFeaturePrivileges: false, + primaryFeaturePrivilege: 'Read', + ...maybeExpectSubFeaturePrivileges(allowSubFeaturePrivileges, { + 'Require all spaces Sub Feature': ['Cool toggle 1'], + }), + }, + }, }); }); @@ -706,6 +783,22 @@ describe('PrivilegeSummaryTable', () => { }), }, }, + with_require_all_spaces_sub_features: { + '*': { + hasCustomizedSubFeaturePrivileges: false, + primaryFeaturePrivilege: 'None', + ...maybeExpectSubFeaturePrivileges(allowSubFeaturePrivileges, { + 'Require all spaces Sub Feature': [], + }), + }, + 'default, space-1': { + hasCustomizedSubFeaturePrivileges: false, + primaryFeaturePrivilege: 'Read', + ...maybeExpectSubFeaturePrivileges(allowSubFeaturePrivileges, { + 'Require all spaces Sub Feature': ['Cool toggle 1'], + }), + }, + }, }); }); @@ -800,6 +893,22 @@ describe('PrivilegeSummaryTable', () => { }), }, }, + with_require_all_spaces_sub_features: { + '*': { + hasCustomizedSubFeaturePrivileges: false, + primaryFeaturePrivilege: 'None', + ...maybeExpectSubFeaturePrivileges(allowSubFeaturePrivileges, { + 'Require all spaces Sub Feature': [], + }), + }, + 'default, space-1': { + hasCustomizedSubFeaturePrivileges: false, + primaryFeaturePrivilege: 'None', + ...maybeExpectSubFeaturePrivileges(allowSubFeaturePrivileges, { + 'Require all spaces Sub Feature': [], + }), + }, + }, }); }); @@ -925,6 +1034,29 @@ describe('PrivilegeSummaryTable', () => { }), }, }, + with_require_all_spaces_sub_features: { + '*': { + hasCustomizedSubFeaturePrivileges: false, + primaryFeaturePrivilege: 'Read', + ...maybeExpectSubFeaturePrivileges(allowSubFeaturePrivileges, { + 'Require all spaces Sub Feature': ['Cool toggle 1'], + }), + }, + default: { + hasCustomizedSubFeaturePrivileges: false, + primaryFeaturePrivilege: 'All', + ...maybeExpectSubFeaturePrivileges(allowSubFeaturePrivileges, { + 'Require all spaces Sub Feature': ['Cool toggle 1'], + }), + }, + 'space-1, space-2': { + hasCustomizedSubFeaturePrivileges: false, + primaryFeaturePrivilege: 'Read', + ...maybeExpectSubFeaturePrivileges(allowSubFeaturePrivileges, { + 'Require all spaces Sub Feature': ['Cool toggle 1'], + }), + }, + }, }); }); }); diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.test.tsx index 8f10acb2403a..71876eeed963 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.test.tsx @@ -79,6 +79,10 @@ describe('PrivilegeSpaceForm', () => { "primaryFeaturePrivilege": "none", "subFeaturePrivileges": Array [], }, + "with_require_all_spaces_sub_features": Object { + "primaryFeaturePrivilege": "none", + "subFeaturePrivileges": Array [], + }, "with_sub_features": Object { "primaryFeaturePrivilege": "none", "subFeaturePrivileges": Array [], @@ -129,6 +133,12 @@ describe('PrivilegeSpaceForm', () => { "primaryFeaturePrivilege": "all", "subFeaturePrivileges": Array [], }, + "with_require_all_spaces_sub_features": Object { + "primaryFeaturePrivilege": "all", + "subFeaturePrivileges": Array [ + "cool_toggle_1", + ], + }, "with_sub_features": Object { "primaryFeaturePrivilege": "all", "subFeaturePrivileges": Array [ @@ -185,6 +195,10 @@ describe('PrivilegeSpaceForm', () => { "primaryFeaturePrivilege": "none", "subFeaturePrivileges": Array [], }, + "with_require_all_spaces_sub_features": Object { + "primaryFeaturePrivilege": "none", + "subFeaturePrivileges": Array [], + }, "with_sub_features": Object { "primaryFeaturePrivilege": "read", "subFeaturePrivileges": Array [ @@ -286,6 +300,10 @@ describe('PrivilegeSpaceForm', () => { "primaryFeaturePrivilege": "none", "subFeaturePrivileges": Array [], }, + "with_require_all_spaces_sub_features": Object { + "primaryFeaturePrivilege": "none", + "subFeaturePrivileges": Array [], + }, "with_sub_features": Object { "primaryFeaturePrivilege": "read", "subFeaturePrivileges": Array [ @@ -346,6 +364,7 @@ describe('PrivilegeSpaceForm', () => { with_excluded_sub_features: ['read'], no_sub_features: ['read'], with_sub_features: ['read'], + with_require_all_spaces_sub_features: ['read'], }, spaces: ['foo'], }, @@ -451,6 +470,7 @@ describe('PrivilegeSpaceForm', () => { with_excluded_sub_features: ['read'], no_sub_features: ['read'], with_sub_features: ['read'], + with_require_all_spaces_sub_features: ['read'], }, spaces: ['foo'], }, @@ -493,6 +513,7 @@ describe('PrivilegeSpaceForm', () => { no_sub_features: ['all'], no_sub_features_disabled_read: ['all'], with_sub_features: ['all'], + with_require_all_spaces_sub_features: ['all'], }, spaces: ['foo'], }, @@ -569,6 +590,7 @@ describe('PrivilegeSpaceForm', () => { no_sub_features: ['read'], no_sub_features_require_all_space: ['read'], with_sub_features: ['read'], + with_require_all_spaces_sub_features: ['read'], }, spaces: ['foo'], }, @@ -613,6 +635,7 @@ describe('PrivilegeSpaceForm', () => { with_excluded_sub_features: ['all'], no_sub_features: ['all'], with_sub_features: ['all'], + with_require_all_spaces_sub_features: ['all'], }, spaces: ['foo'], }, @@ -671,6 +694,7 @@ describe('PrivilegeSpaceForm', () => { no_sub_features: ['all'], no_sub_features_require_all_space: ['all'], with_sub_features: ['all'], + with_require_all_spaces_sub_features: ['all'], }, spaces: ['*'], }, diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx index a26b9587d450..d0120d64fa5e 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx @@ -24,6 +24,7 @@ import { EuiText, EuiTitle, } from '@elastic/eui'; +import { remove } from 'lodash'; import React, { Component, Fragment } from 'react'; import { i18n } from '@kbn/i18n'; @@ -472,9 +473,22 @@ export class PrivilegeSpaceForm extends Component { const primaryFeaturePrivilege = securedFeature ?.getPrimaryFeaturePrivileges({ includeMinimalFeaturePrivileges: true }) .find((pfp) => privileges.includes(pfp.id)) ?? { disabled: false, requireAllSpaces: false }; + + const areAllSpacesSelected = selectedSpaceIds.includes(ALL_SPACES_ID); + if (securedFeature) { + securedFeature.getSubFeatures().forEach((subFeature) => { + subFeature.privileges.forEach((currentPrivilege) => { + if (privileges.includes(currentPrivilege.id)) { + if (subFeature.requireAllSpaces && !areAllSpacesSelected) { + remove(privileges, (privilege) => privilege === currentPrivilege.id); + } + } + }); + }); + } const newFeaturePrivileges = primaryFeaturePrivilege?.disabled || - (primaryFeaturePrivilege?.requireAllSpaces && !selectedSpaceIds.includes(ALL_SPACES_ID)) + (primaryFeaturePrivilege?.requireAllSpaces && !areAllSpacesSelected) ? [] // The primary feature privilege cannot be selected; remove that and any selected sub-feature privileges, too : privileges; return { @@ -536,7 +550,12 @@ export class PrivilegeSpaceForm extends Component { newPrivileges = [nextFeaturePrivilege.id]; feature.getSubFeaturePrivileges().forEach((psf) => { if (Array.isArray(privileges) && privileges.includes(psf.id)) { - newPrivileges.push(psf.id); + if ( + !psf.requireAllSpaces || + (psf.requireAllSpaces && this.state.selectedSpaceIds.includes(ALL_SPACES_ID)) + ) { + newPrivileges.push(psf.id); + } } }); } diff --git a/x-pack/plugins/security/public/management/roles/model/secured_sub_feature.ts b/x-pack/plugins/security/public/management/roles/model/secured_sub_feature.ts index 1f76b17a39e5..8c312ee7ea77 100644 --- a/x-pack/plugins/security/public/management/roles/model/secured_sub_feature.ts +++ b/x-pack/plugins/security/public/management/roles/model/secured_sub_feature.ts @@ -13,6 +13,7 @@ import { SubFeaturePrivilegeGroup } from './sub_feature_privilege_group'; export class SecuredSubFeature extends SubFeature { public readonly privileges: SubFeaturePrivilege[]; + public readonly privilegesTooltip: string; constructor( config: SubFeatureConfig, @@ -20,6 +21,8 @@ export class SecuredSubFeature extends SubFeature { ) { super(config); + this.privilegesTooltip = config.privilegesTooltip || ''; + this.privileges = []; for (const privilege of this.privilegeIterator()) { this.privileges.push(privilege); diff --git a/x-pack/plugins/security/public/management/roles/model/sub_feature_privilege.ts b/x-pack/plugins/security/public/management/roles/model/sub_feature_privilege.ts index 45a51a45533c..b5897654a6a3 100644 --- a/x-pack/plugins/security/public/management/roles/model/sub_feature_privilege.ts +++ b/x-pack/plugins/security/public/management/roles/model/sub_feature_privilege.ts @@ -20,4 +20,8 @@ export class SubFeaturePrivilege extends KibanaPrivilege { public get name() { return this.subPrivilegeConfig.name; } + + public get requireAllSpaces() { + return this.subPrivilegeConfig.requireAllSpaces ?? false; + } } diff --git a/x-pack/plugins/security/public/session/session_timeout.test.ts b/x-pack/plugins/security/public/session/session_timeout.test.ts index e43c1af6ac9c..41e890739029 100644 --- a/x-pack/plugins/security/public/session/session_timeout.test.ts +++ b/x-pack/plugins/security/public/session/session_timeout.test.ts @@ -25,7 +25,7 @@ import type { SessionInfo } from '../../common/types'; import { createSessionExpiredMock } from './session_expired.mock'; import { SessionTimeout, startTimer } from './session_timeout'; -jest.useFakeTimers(); +jest.useFakeTimers('legacy'); jest.spyOn(window, 'addEventListener'); jest.spyOn(window, 'removeEventListener'); diff --git a/x-pack/plugins/security/server/audit/audit_service.test.ts b/x-pack/plugins/security/server/audit/audit_service.test.ts index dfd42c2260c5..2cef6908355d 100644 --- a/x-pack/plugins/security/server/audit/audit_service.test.ts +++ b/x-pack/plugins/security/server/audit/audit_service.test.ts @@ -25,7 +25,7 @@ import { RECORD_USAGE_INTERVAL, } from './audit_service'; -jest.useFakeTimers(); +jest.useFakeTimers('legacy'); const logger = loggingSystemMock.createLogger(); const license = licenseMock.create(); diff --git a/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/alerting.test.ts b/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/alerting.test.ts index cd18a28e0d37..ceb0dab8a7f7 100644 --- a/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/alerting.test.ts +++ b/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/alerting.test.ts @@ -233,6 +233,7 @@ describe(`feature_privilege_builder`, () => { "alerting:1.0.0-zeta1:alert-type/my-feature/rule/unmuteAlert", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/snooze", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/bulkEdit", + "alerting:1.0.0-zeta1:alert-type/my-feature/rule/bulkDelete", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/unsnooze", ] `); @@ -332,6 +333,7 @@ describe(`feature_privilege_builder`, () => { "alerting:1.0.0-zeta1:alert-type/my-feature/rule/unmuteAlert", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/snooze", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/bulkEdit", + "alerting:1.0.0-zeta1:alert-type/my-feature/rule/bulkDelete", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/unsnooze", "alerting:1.0.0-zeta1:alert-type/my-feature/alert/get", "alerting:1.0.0-zeta1:alert-type/my-feature/alert/find", @@ -391,6 +393,7 @@ describe(`feature_privilege_builder`, () => { "alerting:1.0.0-zeta1:alert-type/my-feature/rule/unmuteAlert", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/snooze", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/bulkEdit", + "alerting:1.0.0-zeta1:alert-type/my-feature/rule/bulkDelete", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/unsnooze", "alerting:1.0.0-zeta1:readonly-alert-type/my-feature/rule/get", "alerting:1.0.0-zeta1:readonly-alert-type/my-feature/rule/getRuleState", @@ -499,6 +502,7 @@ describe(`feature_privilege_builder`, () => { "alerting:1.0.0-zeta1:alert-type/my-feature/rule/unmuteAlert", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/snooze", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/bulkEdit", + "alerting:1.0.0-zeta1:alert-type/my-feature/rule/bulkDelete", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/unsnooze", "alerting:1.0.0-zeta1:readonly-alert-type/my-feature/rule/get", "alerting:1.0.0-zeta1:readonly-alert-type/my-feature/rule/getRuleState", diff --git a/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/alerting.ts b/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/alerting.ts index a11a4fa77bcd..19dcd2d3a38c 100644 --- a/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/alerting.ts +++ b/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/alerting.ts @@ -42,6 +42,7 @@ const writeOperations: Record = { 'unmuteAlert', 'snooze', 'bulkEdit', + 'bulkDelete', 'unsnooze', ], alert: ['update'], diff --git a/x-pack/plugins/security/server/elasticsearch/elasticsearch_service.test.ts b/x-pack/plugins/security/server/elasticsearch/elasticsearch_service.test.ts index 219158bdda47..224ffeab0a0f 100644 --- a/x-pack/plugins/security/server/elasticsearch/elasticsearch_service.test.ts +++ b/x-pack/plugins/security/server/elasticsearch/elasticsearch_service.test.ts @@ -88,7 +88,7 @@ describe('ElasticsearchService', () => { }); it('`watchOnlineStatus$` allows to schedule retry', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); // Both ES and license are available. mockLicense.isEnabled.mockReturnValue(true); @@ -146,7 +146,7 @@ describe('ElasticsearchService', () => { }); it('`watchOnlineStatus$` cancels scheduled retry if status changes before retry timeout fires', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); // Both ES and license are available. mockLicense.isEnabled.mockReturnValue(true); diff --git a/x-pack/plugins/security/server/lib/role_utils.ts b/x-pack/plugins/security/server/lib/role_utils.ts index c85207908182..3ec3247bf61a 100644 --- a/x-pack/plugins/security/server/lib/role_utils.ts +++ b/x-pack/plugins/security/server/lib/role_utils.ts @@ -101,6 +101,22 @@ export const validateKibanaPrivileges = ( } } + kibanaFeature.subFeatures.forEach((subFeature) => { + if ( + subFeature.requireAllSpaces && + !forAllSpaces && + subFeature.privilegeGroups.some((group) => + group.privileges.some((privilege) => feature.includes(privilege.id)) + ) + ) { + errors.push( + `Sub-feature privilege [${kibanaFeature.name} - ${ + subFeature.name + }] requires all spaces to be selected but received [${priv.spaces.join(',')}]` + ); + } + }); + return errors; }); }); diff --git a/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.test.ts b/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.test.ts index 842a3b74853b..ed1648c069d4 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.test.ts @@ -466,4 +466,86 @@ describe('validateKibanaPrivileges', () => { `Feature [foo] does not support privilege [read].`, ]); }); + + const fooSubFeature = new KibanaFeature({ + id: 'foo', + name: 'Foo', + privileges: { + all: { + savedObject: { + all: [], + read: [], + }, + ui: [], + }, + read: { + disabled: true, + savedObject: { + all: [], + read: [], + }, + ui: [], + }, + }, + subFeatures: [ + { + name: 'Require All Spaces Enabled', + requireAllSpaces: true, + privilegeGroups: [ + { + groupType: 'mutually_exclusive', + privileges: [ + { + id: 'test', + name: 'foo', + includeIn: 'none', + ui: ['test-ui'], + savedObject: { + all: [], + read: [], + }, + }, + ], + }, + ], + }, + ], + app: [], + category: { id: 'foo', label: 'foo' }, + }); + + test('returns no error when subfeature requireAllSpaces enabled and all spaces selected', () => { + expect( + validateKibanaPrivileges( + [fooSubFeature], + [ + { + spaces: ['*'], + base: [], + feature: { + foo: ['all', 'test'], + }, + }, + ] + ).validationErrors + ).toEqual([]); + }); + test('returns error when subfeature requireAllSpaces enabled but not all spaces selected', () => { + expect( + validateKibanaPrivileges( + [fooSubFeature], + [ + { + spaces: ['foo-space'], + base: [], + feature: { + foo: ['all', 'test'], + }, + }, + ] + ).validationErrors + ).toEqual([ + 'Sub-feature privilege [Foo - Require All Spaces Enabled] requires all spaces to be selected but received [foo-space]', + ]); + }); }); diff --git a/x-pack/plugins/security/server/user_profile/user_profile_service.test.ts b/x-pack/plugins/security/server/user_profile/user_profile_service.test.ts index 76d90f23a2e8..5b5a602d99ec 100644 --- a/x-pack/plugins/security/server/user_profile/user_profile_service.test.ts +++ b/x-pack/plugins/security/server/user_profile/user_profile_service.test.ts @@ -405,7 +405,7 @@ describe('UserProfileService', () => { }); it('retries activation if initially fails with 409 error', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); const failureReason = new errors.ResponseError( securityMock.createApiResponse({ statusCode: 409, body: 'some message' }) @@ -449,7 +449,7 @@ describe('UserProfileService', () => { }); it('fails if activation max retries exceeded', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); const failureReason = new errors.ResponseError( securityMock.createApiResponse({ statusCode: 409, body: 'some message' }) diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/risk_score/all/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/risk_score/all/index.ts index cefd43fe5f99..2c1743e262ea 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/risk_score/all/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/risk_score/all/index.ts @@ -9,10 +9,13 @@ import type { IEsSearchRequest, IEsSearchResponse } from '@kbn/data-plugin/commo import type { ESQuery } from '../../../../typed_json'; import type { Inspect, Maybe, SortField, TimerangeInput } from '../../../common'; +import type { RiskScoreEntity } from '../common'; export interface RiskScoreRequestOptions extends IEsSearchRequest { defaultIndex: string[]; + riskScoreEntity: RiskScoreEntity; timerange?: TimerangeInput; + includeAlertsCount?: boolean; onlyLatest?: boolean; pagination?: { cursorStart: number; @@ -47,6 +50,7 @@ export interface HostRiskScore { name: string; risk: RiskStats; }; + alertsCount?: number; } export interface UserRiskScore { @@ -55,6 +59,7 @@ export interface UserRiskScore { name: string; risk: RiskStats; }; + alertsCount?: number; } export interface RuleRisk { @@ -73,6 +78,7 @@ export const enum RiskScoreFields { userName = 'user.name', userRiskScore = 'user.risk.calculated_score_norm', userRisk = 'user.risk.calculated_level', + alertsCount = 'alertsCount', } export interface RiskScoreItem { @@ -85,6 +91,8 @@ export interface RiskScoreItem { [RiskScoreFields.hostRiskScore]: Maybe; [RiskScoreFields.userRiskScore]: Maybe; + + [RiskScoreFields.alertsCount]: Maybe; } export const enum RiskSeverity { diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.test.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.test.tsx index 67d9be181754..2be5960d92a3 100644 --- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.test.tsx @@ -58,7 +58,7 @@ describe('DraggableWrapper', () => { const mount = useMountAppended(); beforeEach(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterEach(() => { @@ -66,7 +66,9 @@ describe('DraggableWrapper', () => { if (portal != null) { portal.innerHTML = ''; } + }); + afterAll(() => { jest.useRealTimers(); }); diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/ml_popover.tsx b/x-pack/plugins/security_solution/public/common/components/ml_popover/ml_popover.tsx index 9483c549485d..ee16179c9c73 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/ml_popover.tsx +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/ml_popover.tsx @@ -165,7 +165,7 @@ export const MlPopover = React.memo(() => { rel="noopener noreferrer" target="_blank" > - {'Anomaly Detection with Machine Learning'} + {i18n.ANOMALY_DETECTION_DOCS} ), }} diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/translations.ts b/x-pack/plugins/security_solution/public/common/components/ml_popover/translations.ts index 2fa117806000..960734936c1d 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/translations.ts +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/translations.ts @@ -39,5 +39,12 @@ export const MODULE_NOT_COMPATIBLE_TITLE = (incompatibleJobCount: number) => i18n.translate('xpack.securitySolution.components.mlPopup.moduleNotCompatibleTitle', { values: { incompatibleJobCount }, defaultMessage: - '{incompatibleJobCount} {incompatibleJobCount, plural, =1 {job} other {jobs}} are currently unavailable', + '{incompatibleJobCount} {incompatibleJobCount, plural, =1 {job is} other {jobs are}} currently unavailable', }); + +export const ANOMALY_DETECTION_DOCS = i18n.translate( + 'xpack.securitySolution.entityAnalytics.anomalies.AnomalyDetectionDocsTitle', + { + defaultMessage: 'Anomaly Detection with Machine Learning', + } +); diff --git a/x-pack/plugins/security_solution/public/common/components/top_n/helpers.test.tsx b/x-pack/plugins/security_solution/public/common/components/top_n/helpers.test.tsx index 6712782b5d7b..209c0a5ace40 100644 --- a/x-pack/plugins/security_solution/public/common/components/top_n/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/top_n/helpers.test.tsx @@ -21,11 +21,11 @@ import { } from './helpers'; import { SourcererScopeName } from '../../store/sourcerer/model'; -/** the following `TimelineId`s are detection alert tables */ -const detectionAlertsTimelines = [TableId.alertsOnAlertsPage, TableId.alertsOnRuleDetailsPage]; +/** the following scopes are detection alert tables */ +const detectionAlertsTables = [TableId.alertsOnAlertsPage, TableId.alertsOnRuleDetailsPage]; -/** the following `TimelineId`s are NOT detection alert tables */ -const otherTimelines = [ +/** the following scopes are NOT detection alert tables */ +const otherScopes = [ TableId.hostsPageEvents, TableId.hostsPageSessions, TableId.networkPageEvents, @@ -36,7 +36,7 @@ const otherTimelines = [ TableId.kubernetesPageSessions, ]; -const othersWithoutActive = otherTimelines.filter((x) => x !== TimelineId.active); +const othersWithoutActive = otherScopes.filter((x) => x !== TimelineId.active); const hostNameFilter: Filter = { meta: { @@ -169,13 +169,13 @@ describe('getOptions', () => { }); describe('isDetectionsAlertsTable', () => { - detectionAlertsTimelines.forEach((tableId) => + detectionAlertsTables.forEach((tableId) => test(`it returns true for detections alerts table '${tableId}'`, () => { expect(isDetectionsAlertsTable(tableId)).toEqual(true); }) ); - otherTimelines.forEach((tableId) => + otherScopes.forEach((tableId) => test(`it returns false for (NON alert table) timeline '${tableId}'`, () => { expect(isDetectionsAlertsTable(tableId)).toEqual(false); }) @@ -183,7 +183,7 @@ describe('isDetectionsAlertsTable', () => { }); describe('shouldIgnoreAlertFilters', () => { - detectionAlertsTimelines.forEach((tableId) => { + detectionAlertsTables.forEach((tableId) => { test(`it returns true when the view is 'raw' for detections alerts table '${tableId}'`, () => { const view = 'raw'; expect(shouldIgnoreAlertFilters({ tableId, view })).toEqual(true); @@ -195,7 +195,7 @@ describe('shouldIgnoreAlertFilters', () => { }); }); - otherTimelines.forEach((tableId) => { + otherScopes.forEach((tableId) => { test(`it returns false when the view is 'raw' for (NON alert table) timeline'${tableId}'`, () => { const view = 'raw'; expect(shouldIgnoreAlertFilters({ tableId, view })).toEqual(false); @@ -209,7 +209,7 @@ describe('shouldIgnoreAlertFilters', () => { }); describe('removeIgnoredAlertFilters', () => { - detectionAlertsTimelines.forEach((tableId) => { + detectionAlertsTables.forEach((tableId) => { test(`it removes the ignored alert filters when the view is 'raw' for detections alerts table '${tableId}'`, () => { const view = 'raw'; expect(removeIgnoredAlertFilters({ filters: allFilters, tableId, view })).toEqual([ @@ -223,7 +223,7 @@ describe('removeIgnoredAlertFilters', () => { }); }); - otherTimelines.forEach((tableId) => { + otherScopes.forEach((tableId) => { test(`it does NOT remove any filters when the view is 'raw' for (NON alert table) '${tableId}'`, () => { const view = 'alert'; expect(removeIgnoredAlertFilters({ filters: allFilters, tableId, view })).toEqual(allFilters); @@ -237,7 +237,7 @@ describe('removeIgnoredAlertFilters', () => { }); describe('getSourcererScopeName', () => { - detectionAlertsTimelines.forEach((tableId) => { + detectionAlertsTables.forEach((tableId) => { test(`it returns the 'default' SourcererScopeName when the view is 'raw' for detections alerts table '${tableId}'`, () => { const view = 'raw'; expect(getSourcererScopeName({ scopeId: tableId, view })).toEqual(SourcererScopeName.default); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/__snapshots__/kpi_user_authentications_area.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/__snapshots__/kpi_user_authentications_area.test.ts.snap index 1dcaab239de8..e6560a8f2c33 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/__snapshots__/kpi_user_authentications_area.test.ts.snap +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/__snapshots__/kpi_user_authentications_area.test.ts.snap @@ -7,17 +7,17 @@ Object { Object { "id": "security-solution-my-test", "name": "indexpattern-datasource-current-indexpattern", - "type": "{dataViewId}", + "type": "index-pattern", }, Object { "id": "security-solution-my-test", "name": "indexpattern-datasource-layer-31213ae3-905b-4e88-b987-0cccb1f3209f", - "type": "{dataViewId}", + "type": "index-pattern", }, Object { "id": "security-solution-my-test", "name": "indexpattern-datasource-layer-4590dafb-4ac7-45aa-8641-47a3ff0b817c", - "type": "{dataViewId}", + "type": "index-pattern", }, ], "state": Object { diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_area.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_area.ts index da6bdf139a1c..ce7be2aa9f36 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_area.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_area.ts @@ -183,18 +183,18 @@ export const kpiUserAuthenticationsAreaLensAttributes: LensAttributes = { }, references: [ { - type: '{dataViewId}', - id: 'security-solution-default', + type: 'index-pattern', + id: '{dataViewId}', name: 'indexpattern-datasource-current-indexpattern', }, { - type: '{dataViewId}', - id: 'security-solution-default', + type: 'index-pattern', + id: '{dataViewId}', name: 'indexpattern-datasource-layer-31213ae3-905b-4e88-b987-0cccb1f3209f', }, { - type: '{dataViewId}', - id: 'security-solution-default', + type: 'index-pattern', + id: '{dataViewId}', name: 'indexpattern-datasource-layer-4590dafb-4ac7-45aa-8641-47a3ff0b817c', }, ], diff --git a/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.test.ts b/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.test.ts index bb65e5def279..dba14afd0ff2 100644 --- a/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.test.ts +++ b/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.test.ts @@ -10,8 +10,16 @@ import { useKibana } from '../../lib/kibana'; import { useMatrixHistogram, useMatrixHistogramCombined } from '.'; import { MatrixHistogramType } from '../../../../common/search_strategy'; import { TestProviders } from '../../mock/test_providers'; +import { useTrackHttpRequest } from '../../lib/apm/use_track_http_request'; jest.mock('../../lib/kibana'); +jest.mock('../../lib/apm/use_track_http_request'); + +const mockEndTracking = jest.fn(); +const mockStartTracking = jest.fn(() => ({ endTracking: mockEndTracking })); +(useTrackHttpRequest as jest.Mock).mockReturnValue({ + startTracking: mockStartTracking, +}); const basicResponse = { isPartial: false, @@ -42,7 +50,7 @@ describe('useMatrixHistogram', () => { }; afterEach(() => { - (useKibana().services.data.search.search as jest.Mock).mockClear(); + jest.clearAllMocks(); }); it('should update request when props has changed', async () => { @@ -156,6 +164,58 @@ describe('useMatrixHistogram', () => { act(() => rerender()); expect(abortSpy).toHaveBeenCalledTimes(3); }); + + describe('trackHttpRequest', () => { + it('should start tracking when request starts', () => { + renderHook(useMatrixHistogram, { + initialProps: props, + wrapper: TestProviders, + }); + + expect(mockStartTracking).toHaveBeenCalledWith({ + name: `securitySolutionUI matrixHistogram ${MatrixHistogramType.events}`, + }); + }); + + it('should end tracking success when the request succeeds', () => { + (useKibana().services.data.search.search as jest.Mock).mockReturnValueOnce({ + subscribe: ({ next }: { next: Function }) => next(basicResponse), + }); + + renderHook(useMatrixHistogram, { + initialProps: props, + wrapper: TestProviders, + }); + + expect(mockEndTracking).toHaveBeenCalledWith('success'); + }); + + it('should end tracking error when the partial request is invalid', () => { + (useKibana().services.data.search.search as jest.Mock).mockReturnValueOnce({ + subscribe: ({ next }: { next: Function }) => next(null), + }); + + renderHook(useMatrixHistogram, { + initialProps: props, + wrapper: TestProviders, + }); + + expect(mockEndTracking).toHaveBeenCalledWith('invalid'); + }); + + it('should end tracking error when the request fails', () => { + (useKibana().services.data.search.search as jest.Mock).mockReturnValueOnce({ + subscribe: ({ error }: { error: Function }) => error('some error'), + }); + + renderHook(useMatrixHistogram, { + initialProps: props, + wrapper: TestProviders, + }); + + expect(mockEndTracking).toHaveBeenCalledWith('error'); + }); + }); }); describe('useMatrixHistogramCombined', () => { diff --git a/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts b/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts index c770713b602b..85512855580c 100644 --- a/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts +++ b/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts @@ -28,6 +28,8 @@ import { getInspectResponse } from '../../../helpers'; import type { InspectResponse } from '../../../types'; import * as i18n from './translations'; import { useAppToasts } from '../../hooks/use_app_toasts'; +import { useTrackHttpRequest } from '../../lib/apm/use_track_http_request'; +import { APP_UI_ID } from '../../../../common/constants'; export type Buckets = Array<{ key: string; @@ -71,6 +73,7 @@ export const useMatrixHistogram = ({ const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); const [loading, setLoading] = useState(false); + const { startTracking } = useTrackHttpRequest(); const [matrixHistogramRequest, setMatrixHistogramRequest] = useState({ @@ -102,11 +105,14 @@ export const useMatrixHistogram = ({ buckets: [], }); - const hostsSearch = useCallback( + const search = useCallback( (request: MatrixHistogramRequestOptions) => { const asyncSearch = async () => { abortCtrl.current = new AbortController(); setLoading(true); + const { endTracking } = startTracking({ + name: `${APP_UI_ID} matrixHistogram ${histogramType}`, + }); searchSubscription$.current = data.search .search(request, { @@ -130,10 +136,12 @@ export const useMatrixHistogram = ({ totalCount: histogramBuckets.reduce((acc, bucket) => bucket.doc_count + acc, 0), buckets: histogramBuckets, })); + endTracking('success'); searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); addWarning(i18n.ERROR_MATRIX_HISTOGRAM); + endTracking('invalid'); searchSubscription$.current.unsubscribe(); } }, @@ -142,6 +150,7 @@ export const useMatrixHistogram = ({ addError(msg, { title: errorMessage ?? i18n.FAIL_MATRIX_HISTOGRAM, }); + endTracking('error'); searchSubscription$.current.unsubscribe(); }, }); @@ -151,7 +160,7 @@ export const useMatrixHistogram = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, errorMessage, addError, addWarning, histogramType] + [data.search, histogramType, addWarning, addError, errorMessage, startTracking] ); useEffect(() => { @@ -189,13 +198,13 @@ export const useMatrixHistogram = ({ useEffect(() => { // We want to search if it is not skipped, stackByField ends with ip and include missing data if (!skip) { - hostsSearch(matrixHistogramRequest); + search(matrixHistogramRequest); } return () => { searchSubscription$.current.unsubscribe(); abortCtrl.current.abort(); }; - }, [matrixHistogramRequest, hostsSearch, skip]); + }, [matrixHistogramRequest, search, skip]); useEffect(() => { if (skip) { @@ -207,7 +216,7 @@ export const useMatrixHistogram = ({ const runMatrixHistogramSearch = useCallback( (to: string, from: string) => { - hostsSearch({ + search({ ...matrixHistogramRequest, timerange: { interval: '12h', @@ -216,7 +225,7 @@ export const useMatrixHistogram = ({ }, }); }, - [matrixHistogramRequest, hostsSearch] + [matrixHistogramRequest, search] ); return [loading, matrixHistogramResponse, runMatrixHistogramSearch]; diff --git a/x-pack/plugins/security_solution/public/common/mock/global_state.ts b/x-pack/plugins/security_solution/public/common/mock/global_state.ts index 967eca53bce1..20191f7ce312 100644 --- a/x-pack/plugins/security_solution/public/common/mock/global_state.ts +++ b/x-pack/plugins/security_solution/public/common/mock/global_state.ts @@ -346,24 +346,26 @@ export const mockGlobalState: State = { start: '2020-07-07T08:20:18.966Z', end: '2020-07-08T08:20:18.966Z', }, - sessionViewConfig: null, - show: false, + resolveTimelineConfig: undefined, pinnedEventIds: {}, pinnedEventsSaveObject: {}, - itemsPerPageOptions: [5, 10, 20], + sessionViewConfig: null, + show: false, sort: [ { columnId: '@timestamp', columnType: 'date', esTypes: ['date'], - sortDirection: Direction.desc, + sortDirection: 'desc', }, ], - isSaving: false, + status: TimelineStatus.draft, version: null, - status: TimelineStatus.active, - isSelectAllChecked: false, selectedEventIds: {}, + isSelectAllChecked: false, + filters: [], + isSaving: false, + itemsPerPageOptions: [10, 25, 50, 100], }, }, insertTimeline: null, diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/additional_filters_action/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/additional_filters_action/index.test.tsx index ec4fdb5cb6e8..9155415165c5 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/additional_filters_action/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/additional_filters_action/index.test.tsx @@ -11,7 +11,7 @@ import { render, screen, fireEvent } from '@testing-library/react'; import { AdditionalFiltersAction } from '.'; import { TestProviders } from '../../../../common/mock/test_providers'; -jest.useFakeTimers(); +jest.useFakeTimers('legacy'); jest.mock('../../../../common/lib/kibana'); describe('AdditionalFiltersAction', () => { diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/validators.test.ts b/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/validators.test.ts index 74f1bb89bb8c..a4e7baf3c30b 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/validators.test.ts +++ b/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/validators.test.ts @@ -7,7 +7,7 @@ import { debounceAsync } from './validators'; -jest.useFakeTimers(); +jest.useFakeTimers('legacy'); describe('debounceAsync', () => { let fn: jest.Mock; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/select_rule_type/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/select_rule_type/index.tsx index 01569edd6907..52e96c088c20 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/select_rule_type/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/select_rule_type/index.tsx @@ -18,7 +18,6 @@ import { isNewTermsRule, } from '../../../../../common/detection_engine/utils'; import type { FieldHook } from '../../../../shared_imports'; -import { useKibana } from '../../../../common/lib/kibana'; import * as i18n from './translations'; import { MlCardDescription } from './ml_card_description'; @@ -50,9 +49,6 @@ export const SelectRuleType: React.FC = ({ const setThreshold = useCallback(() => setType('threshold'), [setType]); const setThreatMatch = useCallback(() => setType('threat_match'), [setType]); const setNewTerms = useCallback(() => setType('new_terms'), [setType]); - const licensingUrl = useKibana().services.application.getUrlForApp('kibana', { - path: '#/management/stack/license_management', - }); const eqlSelectableConfig = useMemo( () => ({ @@ -130,12 +126,7 @@ export const SelectRuleType: React.FC = ({ data-test-subj="machineLearningRuleType" title={i18n.ML_TYPE_TITLE} titleSize="xs" - description={ - - } + description={} icon={} isDisabled={mlSelectableConfig.isDisabled && !mlSelectableConfig.isSelected} selectable={mlSelectableConfig} diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/select_rule_type/ml_card_description.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/select_rule_type/ml_card_description.tsx index 51ad3e1d83e9..f017bc2d52a8 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/select_rule_type/ml_card_description.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/select_rule_type/ml_card_description.tsx @@ -13,7 +13,6 @@ import React from 'react'; import { ML_TYPE_DESCRIPTION } from './translations'; interface MlCardDescriptionProps { - subscriptionUrl: string; hasValidLicense?: boolean; } @@ -22,7 +21,6 @@ const SmallText = styled.span` `; const MlCardDescriptionComponent: React.FC = ({ - subscriptionUrl, hasValidLicense = false, }) => ( @@ -34,7 +32,7 @@ const MlCardDescriptionComponent: React.FC = ({ defaultMessage="Access to ML requires a {subscriptionsLink}." values={{ subscriptionsLink: ( - + { }); it('should show long running hint message if pending and >15s have passed', () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); render(); expect(renderResult.queryByTestId('test-longRunningCommandHint')).toBeNull(); @@ -55,7 +55,7 @@ describe('When using CommandExecutionOutput component', () => { }); it('should remove long running hint message if command completes', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); render(); act(() => { diff --git a/x-pack/plugins/security_solution/public/management/components/package_action_item/package_action.formatter.test.ts b/x-pack/plugins/security_solution/public/management/components/package_action_item/package_action.formatter.test.ts new file mode 100644 index 000000000000..dc5c443e15e2 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/components/package_action_item/package_action.formatter.test.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { FleetServerAgentComponentUnit } from '@kbn/fleet-plugin/common/types'; + +import { PackageActionFormatter, titles, descriptions } from './package_action_formatter'; +import { ENDPOINT_ERROR_CODES } from '../../../../common/endpoint/constants'; + +describe('PackageActionFormatter', () => { + it('correctly formats es connection error', () => { + const unit: FleetServerAgentComponentUnit = { + id: 'test-id', + type: 'input', + status: 'failed', + message: 'test message', + payload: { + error: { + code: ENDPOINT_ERROR_CODES.ES_CONNECTION_ERROR, + message: 'an error message', + }, + }, + }; + const docLinks = { es_connection: 'somedoclink' }; + const formatter = new PackageActionFormatter(unit, docLinks); + expect(formatter.key).toBe('es_connection'); + expect(formatter.title).toBe(titles.get('es_connection')); + expect(formatter.description).toBe(descriptions.get('es_connection')); + expect(formatter.linkUrl).toBe(docLinks.es_connection); + }); + + it('correct formats generic error', () => { + const unit: FleetServerAgentComponentUnit = { + id: 'test-id', + type: 'input', + status: 'failed', + message: 'test message', + }; + const docLinks = { es_connection: 'somedoclink' }; + const formatter = new PackageActionFormatter(unit, docLinks); + expect(formatter.key).toBe('policy_failure'); + expect(formatter.title).toBe(titles.get('policy_failure')); + expect(formatter.description).toBe(descriptions.get('policy_failure')); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/components/package_action_item/package_action_formatter.ts b/x-pack/plugins/security_solution/public/management/components/package_action_item/package_action_formatter.ts index ac4aebf65c49..9af6983ecfcd 100644 --- a/x-pack/plugins/security_solution/public/management/components/package_action_item/package_action_formatter.ts +++ b/x-pack/plugins/security_solution/public/management/components/package_action_item/package_action_formatter.ts @@ -7,6 +7,10 @@ import { i18n } from '@kbn/i18n'; import type { DocLinks } from '@kbn/doc-links'; +import type { + FleetServerAgentComponentUnit, + FleetServerAgentComponentStatus, +} from '@kbn/fleet-plugin/common/types'; import { ENDPOINT_ERROR_CODES } from '../../../../common/endpoint/constants'; @@ -78,13 +82,12 @@ export class PackageActionFormatter { public linkText?: string; constructor( - code: number, - message: string, + unit: FleetServerAgentComponentUnit, private docLinks: DocLinks['securitySolution']['packageActionTroubleshooting'] ) { - this.key = this.getKeyFromErrorCode(code); + this.key = this.getKeyFromErrorCode(unit.payload?.error?.code, unit.status); this.title = titles.get(this.key) ?? this.key; - this.description = descriptions.get(this.key) || message; + this.description = descriptions.get(this.key) || unit.payload?.error?.message; this.linkText = linkTexts.get(this.key); } @@ -94,10 +97,13 @@ export class PackageActionFormatter { ]; } - private getKeyFromErrorCode(code: number): PackageActions { + private getKeyFromErrorCode( + code: number, + status: FleetServerAgentComponentStatus + ): PackageActions { if (code === ENDPOINT_ERROR_CODES.ES_CONNECTION_ERROR) { return 'es_connection'; - } else if (code === 124) { + } else if (status === 'failed') { return 'policy_failure'; } else { throw new Error(`Invalid error code ${code}`); diff --git a/x-pack/plugins/security_solution/public/management/components/policy_response/policy_response_wrapper.test.tsx b/x-pack/plugins/security_solution/public/management/components/policy_response/integration_tests/policy_response_wrapper.test.tsx similarity index 93% rename from x-pack/plugins/security_solution/public/management/components/policy_response/policy_response_wrapper.test.tsx rename to x-pack/plugins/security_solution/public/management/components/policy_response/integration_tests/policy_response_wrapper.test.tsx index 3c6d4f66d59c..f23e3bb005fb 100644 --- a/x-pack/plugins/security_solution/public/management/components/policy_response/policy_response_wrapper.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/policy_response/integration_tests/policy_response_wrapper.test.tsx @@ -7,28 +7,26 @@ import React from 'react'; import userEvent from '@testing-library/user-event'; -import type { AppContextTestRender } from '../../../common/mock/endpoint'; -import { createAppRootMockRenderer } from '../../../common/mock/endpoint'; -import type { PolicyResponseWrapperProps } from './policy_response_wrapper'; -import { PolicyResponseWrapper } from './policy_response_wrapper'; -import { HostPolicyResponseActionStatus } from '../../../../common/search_strategy'; -import { useGetEndpointPolicyResponse } from '../../hooks/endpoint/use_get_endpoint_policy_response'; +import type { AppContextTestRender } from '../../../../common/mock/endpoint'; +import { createAppRootMockRenderer } from '../../../../common/mock/endpoint'; +import type { PolicyResponseWrapperProps } from '../policy_response_wrapper'; +import { PolicyResponseWrapper } from '../policy_response_wrapper'; +import { HostPolicyResponseActionStatus } from '../../../../../common/search_strategy'; +import { useGetEndpointPolicyResponse } from '../../../hooks/endpoint/use_get_endpoint_policy_response'; import type { HostPolicyResponse, HostPolicyResponseAppliedAction, -} from '../../../../common/endpoint/types'; -import { EndpointDocGenerator } from '../../../../common/endpoint/generate_data'; -import { useGetEndpointDetails } from '../../hooks'; +} from '../../../../../common/endpoint/types'; +import { EndpointDocGenerator } from '../../../../../common/endpoint/generate_data'; +import { useGetEndpointDetails } from '../../../hooks'; import { descriptions, LINUX_DEADLOCK_MESSAGE, policyResponseTitles, -} from './policy_response_friendly_names'; +} from '../policy_response_friendly_names'; -jest.setTimeout(10000); - -jest.mock('../../hooks/endpoint/use_get_endpoint_policy_response'); -jest.mock('../../hooks/endpoint/use_get_endpoint_details'); +jest.mock('../../../hooks/endpoint/use_get_endpoint_policy_response'); +jest.mock('../../../hooks/endpoint/use_get_endpoint_details'); describe('when on the policy response', () => { const docGenerator = new EndpointDocGenerator(); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_generic_errors_list.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_generic_errors_list.tsx index cac9c2a7d0c2..babb4c78af14 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_generic_errors_list.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_generic_errors_list.tsx @@ -23,11 +23,10 @@ export const EndpointGenericErrorsList = memo( const globalEndpointErrors = useMemo(() => { const errors: PackageActionFormatter[] = []; packageErrors.forEach((unit) => { - if (unit.payload && unit.payload.error) { + if (unit.status === 'failed') { errors.push( new PackageActionFormatter( - unit.payload.error.code, - unit.payload.error.message, + unit, docLinks.links.securitySolution.packageActionTroubleshooting ) ); diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/index.test.tsx index 37d5cabca6ce..b47741a21910 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/index.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/index.test.tsx @@ -204,4 +204,32 @@ describe('EntityAnalyticsAnomalies', () => { expect(getByTestId('anomalies-table-column-count').textContent).toEqual('Count'); // 'Count' is always rendered by only displayed on mobile }); + + it('renders a warning message when jobs are incompatible', () => { + const jobCount: AnomaliesCount = { + job: { + isInstalled: true, + datafeedState: 'started', + jobState: 'opened', + isCompatible: false, + } as SecurityJob, + name: 'v3_windows_anomalous_script', + count: 0, + entity: AnomalyEntity.User, + }; + + mockUseNotableAnomaliesSearch.mockReturnValue({ + isLoading: false, + data: [jobCount], + refetch: jest.fn(), + }); + + const { getByTestId } = render( + + + + ); + + expect(getByTestId('incompatible_jobs_warnings')).toBeInTheDocument(); + }); }); diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/index.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/index.tsx index beeae25f64b2..c3333a3e1395 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/index.tsx @@ -5,9 +5,17 @@ * 2.0. */ import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiInMemoryTable, EuiPanel } from '@elastic/eui'; +import { + EuiCallOut, + EuiFlexGroup, + EuiFlexItem, + EuiInMemoryTable, + EuiPanel, + EuiSpacer, +} from '@elastic/eui'; import { MLJobsAwaitingNodeWarning, ML_PAGES, useMlHref } from '@kbn/ml-plugin/public'; +import { FormattedMessage } from '@kbn/i18n-react'; import { HeaderSection } from '../../../../common/components/header_section'; import { useQueryToggle } from '../../../../common/containers/query_toggle'; import { LastUpdatedAt } from '../../../../common/components/last_updated_at'; @@ -43,7 +51,7 @@ export const ENTITY_ANALYTICS_ANOMALIES_PANEL = 'entity_analytics_anomalies'; export const EntityAnalyticsAnomalies = () => { const { - services: { ml, http }, + services: { ml, http, docLinks }, } = useKibana(); const jobsUrl = useMlHref(ml, http.basePath.get(), { @@ -112,6 +120,11 @@ export const EntityAnalyticsAnomalies = () => { [data] ); + const incompatibleJobCount = useMemo( + () => data.filter(({ job }) => job && !job.isCompatible).length, + [data] + ); + return ( { + + {incompatibleJobCount > 0 && ( + <> + +

+ + {i18n.ANOMALY_DETECTION_DOCS} + + ), + }} + /> +

+
+ + + + )} {toggleStatus && ( + i18n.translate('xpack.securitySolution.entityAnalytics.anomalies.moduleNotCompatibleTitle', { + values: { incompatibleJobCount }, + defaultMessage: + '{incompatibleJobCount} {incompatibleJobCount, plural, =1 {job is} other {jobs are}} currently unavailable', + }); + +export const ANOMALY_DETECTION_DOCS = i18n.translate( + 'xpack.securitySolution.entityAnalytics.anomalies.AnomalyDetectionDocsTitle', + { + defaultMessage: 'Anomaly Detection with Machine Learning', + } +); diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/columns.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/columns.tsx index 055a172e54b7..a19168b5e864 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/columns.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/columns.tsx @@ -7,19 +7,28 @@ import React from 'react'; import type { EuiBasicTableColumn } from '@elastic/eui'; -import { EuiIcon, EuiToolTip } from '@elastic/eui'; +import { EuiLink, EuiIcon, EuiToolTip } from '@elastic/eui'; +import { get } from 'lodash/fp'; import { UsersTableType } from '../../../../users/store/model'; import { getEmptyTagValue } from '../../../../common/components/empty_value'; import { HostDetailsLink, UserDetailsLink } from '../../../../common/components/links'; import { HostsTableType } from '../../../../hosts/store/model'; import { RiskScore } from '../../../../common/components/severity/common'; -import type { HostRiskScore, RiskSeverity } from '../../../../../common/search_strategy'; +import type { + HostRiskScore, + RiskSeverity, + UserRiskScore, +} from '../../../../../common/search_strategy'; import { RiskScoreEntity, RiskScoreFields } from '../../../../../common/search_strategy'; import * as i18n from './translations'; +import { FormattedCount } from '../../../../common/components/formatted_number'; -type HostRiskScoreColumns = Array>; +type HostRiskScoreColumns = Array>; -export const getRiskScoreColumns = (riskEntity: RiskScoreEntity): HostRiskScoreColumns => [ +export const getRiskScoreColumns = ( + riskEntity: RiskScoreEntity, + openEntityInTimeline: (entityName: string) => void +): HostRiskScoreColumns => [ { field: riskEntity === RiskScoreEntity.host ? 'host.name' : 'user.name', name: i18n.ENTITY_NAME(riskEntity), @@ -75,4 +84,20 @@ export const getRiskScoreColumns = (riskEntity: RiskScoreEntity): HostRiskScoreC return getEmptyTagValue(); }, }, + { + field: RiskScoreFields.alertsCount, + width: '15%', + name: i18n.ALERTS, + truncateText: false, + mobileOptions: { show: true }, + render: (alertCount: number, risk) => ( + openEntityInTimeline(get('host.name', risk) ?? get('user.name', risk))} + > + + + ), + }, ]; diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.test.tsx index 89011082a2f8..70faf174ae32 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.test.tsx @@ -9,6 +9,7 @@ import { render } from '@testing-library/react'; import React from 'react'; import { TestProviders } from '../../../../common/mock'; import { EntityAnalyticsRiskScores } from '.'; +import type { UserRiskScore } from '../../../../../common/search_strategy'; import { RiskScoreEntity, RiskSeverity } from '../../../../../common/search_strategy'; import type { SeverityCount } from '../../../../common/components/severity/types'; import { useRiskScore, useRiskScoreKpi } from '../../../../risk_score/containers'; @@ -116,5 +117,38 @@ describe.each([RiskScoreEntity.host, RiskScoreEntity.user])( expect(queryByTestId('entity_analytics_content')).not.toBeInTheDocument(); }); + + it('renders alerts count', () => { + mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: jest.fn() }); + mockUseRiskScoreKpi.mockReturnValue({ + severityCount: mockSeverityCount, + loading: false, + }); + const alertsCount = 999; + const data: UserRiskScore[] = [ + { + '@timestamp': '1234567899', + user: { + name: 'testUsermame', + risk: { + rule_risks: [], + calculated_level: RiskSeverity.high, + calculated_score_norm: 75, + multipliers: [], + }, + }, + alertsCount, + }, + ]; + mockUseRiskScore.mockReturnValue({ ...defaultProps, data }); + + const { queryByTestId } = render( + + + + ); + + expect(queryByTestId('risk-score-alerts')).toHaveTextContent(alertsCount.toString()); + }); } ); diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.tsx index 066c4f29d278..13899e88f38f 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.tsx @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { useDispatch } from 'react-redux'; @@ -40,6 +40,7 @@ import { Loader } from '../../../../common/components/loader'; import { Panel } from '../../../../common/components/panel'; import * as commonI18n from '../common/translations'; import { usersActions } from '../../../../users/store'; +import { useNavigateToTimeline } from '../../detection_response/hooks/use_navigate_to_timeline'; const HOST_RISK_TABLE_QUERY_ID = 'hostRiskDashboardTable'; const HOST_RISK_KPI_QUERY_ID = 'headerHostRiskScoreKpiQuery'; @@ -90,8 +91,24 @@ const EntityAnalyticsRiskScoresComponent = ({ riskEntity }: { riskEntity: RiskSc [dispatch, riskEntity] ); + const { openHostInTimeline, openUserInTimeline } = useNavigateToTimeline(); + + const openEntityInTimeline = useCallback( + (entityName: string) => { + if (riskEntity === RiskScoreEntity.host) { + openHostInTimeline({ hostName: entityName }); + } else if (riskEntity === RiskScoreEntity.user) { + openUserInTimeline({ userName: entityName }); + } + }, + [riskEntity, openHostInTimeline, openUserInTimeline] + ); + const { toggleStatus, setToggleStatus } = useQueryToggle(entity.tableQueryId); - const columns = useMemo(() => getRiskScoreColumns(riskEntity), [riskEntity]); + const columns = useMemo( + () => getRiskScoreColumns(riskEntity, openEntityInTimeline), + [riskEntity, openEntityInTimeline] + ); const [selectedSeverity, setSelectedSeverity] = useState([]); const getSecuritySolutionLinkProps = useGetSecuritySolutionLinkProps(); @@ -146,6 +163,7 @@ const EntityAnalyticsRiskScoresComponent = ({ riskEntity }: { riskEntity: RiskSc }, timerange, riskEntity, + includeAlertsCount: true, }); useQueryInspector({ diff --git a/x-pack/plugins/security_solution/public/resolver/test_utilities/extend_jest.ts b/x-pack/plugins/security_solution/public/resolver/test_utilities/extend_jest.ts index 7bb7180712c9..64977ffd5c06 100644 --- a/x-pack/plugins/security_solution/public/resolver/test_utilities/extend_jest.ts +++ b/x-pack/plugins/security_solution/public/resolver/test_utilities/extend_jest.ts @@ -5,6 +5,8 @@ * 2.0. */ +import type { MatcherHintOptions } from 'jest-matcher-utils'; + /** * Typescript won't allow global namespace stuff unless you're in a module. * This wouldn't otherwise be a module. The code runs as soon as it's imported. @@ -36,7 +38,7 @@ expect.extend({ ): Promise<{ pass: boolean; message: () => string }> { // Used in printing out the pass or fail message const matcherName = 'toYieldEqualTo'; - const options: jest.MatcherHintOptions = { + const options: MatcherHintOptions = { comment: 'deep equality with any yielded value', isNot: this.isNot, promise: this.promise, @@ -102,7 +104,7 @@ expect.extend({ ): Promise<{ pass: boolean; message: () => string }> { // Used in printing out the pass or fail message const matcherName = 'toYieldObjectEqualTo'; - const options: jest.MatcherHintOptions = { + const options: MatcherHintOptions = { comment: 'subset equality with any yielded value', isNot: this.isNot, promise: this.promise, diff --git a/x-pack/plugins/security_solution/public/risk_score/components/translations.ts b/x-pack/plugins/security_solution/public/risk_score/components/translations.ts index 2016dd1d5799..8d845c6d9998 100644 --- a/x-pack/plugins/security_solution/public/risk_score/components/translations.ts +++ b/x-pack/plugins/security_solution/public/risk_score/components/translations.ts @@ -50,3 +50,7 @@ export const getRiskEntityTranslation = ( return riskEntity === RiskScoreEntity.host ? HOST : USER; }; + +export const ALERTS = i18n.translate('xpack.securitySolution.riskScore.overview.alerts', { + defaultMessage: 'Alerts', +}); diff --git a/x-pack/plugins/security_solution/public/risk_score/containers/all/index.test.tsx b/x-pack/plugins/security_solution/public/risk_score/containers/all/index.test.tsx index c9a8f448c352..dc2b8dac5849 100644 --- a/x-pack/plugins/security_solution/public/risk_score/containers/all/index.test.tsx +++ b/x-pack/plugins/security_solution/public/risk_score/containers/all/index.test.tsx @@ -168,6 +168,8 @@ describe.each([RiskScoreEntity.host, RiskScoreEntity.user])( expect(mockSearch).toHaveBeenCalledWith({ defaultIndex: [`ml_${riskEntity}_risk_score_latest_default`], factoryQueryType: `${riskEntity}sRiskScore`, + riskScoreEntity: riskEntity, + includeAlertsCount: false, }); }); diff --git a/x-pack/plugins/security_solution/public/risk_score/containers/all/index.tsx b/x-pack/plugins/security_solution/public/risk_score/containers/all/index.tsx index b700f83f7f70..977bc10caecc 100644 --- a/x-pack/plugins/security_solution/public/risk_score/containers/all/index.tsx +++ b/x-pack/plugins/security_solution/public/risk_score/containers/all/index.tsx @@ -45,6 +45,7 @@ export interface RiskScoreState): RiskScoreState => { const spaceId = useSpaceId(); const defaultIndex = spaceId @@ -158,6 +160,8 @@ export const useRiskScore =
diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/pane/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/pane/index.test.tsx index 59579924e3d8..0195e4d5c62f 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/pane/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/pane/index.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { render } from '@testing-library/react'; +import { render, waitFor } from '@testing-library/react'; import React from 'react'; import { TestProviders } from '../../../../common/mock'; @@ -15,6 +15,15 @@ import { useGetUserCasesPermissions } from '../../../../common/lib/kibana'; jest.mock('../../../../common/lib/kibana'); const originalKibanaLib = jest.requireActual('../../../../common/lib/kibana'); +jest.mock('@kbn/i18n-react', () => { + const originalModule = jest.requireActual('@kbn/i18n-react'); + const FormattedRelative = jest.fn().mockImplementation(() => '20 hours ago'); + + return { + ...originalModule, + FormattedRelative, + }; +}); // Restore the useGetUserCasesPermissions so the calling functions can receive a valid permissions object // The returned permissions object will indicate that the user does not have permissions by default @@ -30,21 +39,25 @@ jest.mock('../../../../common/hooks/use_resolve_conflict', () => { }); describe('Pane', () => { - test('renders with display block by default', () => { + test('renders with display block by default', async () => { const EmptyComponent = render( ); - expect(EmptyComponent.getByTestId('flyout-pane')).toHaveStyle('display: block'); + await waitFor(() => { + expect(EmptyComponent.getByTestId('flyout-pane')).toHaveStyle('display: block'); + }); }); - test('renders with display none when visibility is set to false', () => { + test('renders with display none when visibility is set to false', async () => { const EmptyComponent = render( ); - expect(EmptyComponent.getByTestId('flyout-pane')).toHaveStyle('display: none'); + await waitFor(() => { + expect(EmptyComponent.getByTestId('flyout-pane')).toHaveStyle('display: none'); + }); }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/header_actions.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/header_actions.test.tsx index cd90f1cfb8e7..a851c9040599 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/header_actions.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/header_actions.test.tsx @@ -15,7 +15,7 @@ import type { ColumnHeaderOptions, HeaderActionProps, } from '../../../../../../common/types/timeline'; -import { TimelineTabs } from '../../../../../../common/types/timeline'; +import { TimelineId, TimelineTabs } from '../../../../../../common/types/timeline'; import { timelineActions } from '../../../../store/timeline'; import { getColumnHeader } from '../column_headers/helpers'; @@ -35,7 +35,7 @@ jest.mock('../../../../../common/hooks/use_selector', () => ({ })); const columnId = 'test-field'; -const timelineId = 'test-timeline'; +const timelineId = TimelineId.test; /* eslint-disable jsx-a11y/click-events-have-key-events */ mockTriggersActionsUi.getFieldBrowser.mockImplementation( diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.test.tsx index 08484bdb34c9..5807a9667942 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.test.tsx @@ -7,7 +7,7 @@ import { mount } from 'enzyme'; import React from 'react'; -import { TableId } from '../../../../../../common/types/timeline'; +import { TableId, TimelineId } from '../../../../../../common/types/timeline'; import { TestProviders, mockTimelineModel, mockTimelineData } from '../../../../../common/mock'; import { Actions, isAlert } from '.'; import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; @@ -97,7 +97,7 @@ const defaultProps = { setEventsLoading: () => {}, showCheckboxes: true, showNotes: false, - timelineId: 'test', + timelineId: TimelineId.test, toggleShowNotes: () => {}, }; @@ -271,7 +271,7 @@ describe('Actions', () => { ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/index.test.tsx index 227327fd8e85..e2be31e9d5dd 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/index.test.tsx @@ -19,6 +19,7 @@ import { HeaderComponent } from '.'; import { getNewSortDirectionOnClick, getNextSortDirection, getSortDirection } from './helpers'; import { Direction } from '../../../../../../../common/search_strategy'; import { useDeepEqualSelector } from '../../../../../../common/hooks/use_selector'; +import { TimelineId } from '../../../../../../../common/types'; const mockDispatch = jest.fn(); jest.mock('react-redux', () => { @@ -48,7 +49,7 @@ describe('Header', () => { sortDirection: Direction.desc, }, ]; - const timelineId = 'test'; + const timelineId = TimelineId.test; beforeEach(() => { (useDeepEqualSelector as jest.Mock).mockReturnValue({ isLoading: false }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx index cffa38e3435b..32251df6318f 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx @@ -18,7 +18,6 @@ import { kibanaObservable, mockGlobalState, mockTimelineData, - mockTimelineModel, SUB_PLUGINS_REDUCER, } from '../../../../common/mock'; import { TestProviders } from '../../../../common/mock/test_providers'; @@ -29,13 +28,14 @@ import type { Props } from '.'; import { StatefulBody } from '.'; import type { Sort } from './sort'; import { getDefaultControlColumn } from './control_columns'; -import { useMountAppended } from '../../../../common/utils/use_mount_appended'; import { timelineActions } from '../../../store/timeline'; import { TimelineId, TimelineTabs } from '../../../../../common/types/timeline'; import { defaultRowRenderers } from './renderers'; import type { State } from '../../../../common/store'; import { createStore } from '../../../../common/store'; import { tGridReducer } from '@kbn/timelines-plugin/public'; +import { mount } from 'enzyme'; +import type { UseFieldBrowserOptionsProps } from '../../fields_browser'; jest.mock('../../../../common/hooks/use_app_toasts'); jest.mock('../../../../common/components/user_privileges', () => { @@ -49,6 +49,11 @@ jest.mock('../../../../common/components/user_privileges', () => { }; }); +const mockUseFieldBrowserOptions = jest.fn(); +jest.mock('../../fields_browser', () => ({ + useFieldBrowserOptions: (props: UseFieldBrowserOptionsProps) => mockUseFieldBrowserOptions(props), +})); + jest.mock('../../../../common/lib/kibana', () => { const originalModule = jest.requireActual('../../../../common/lib/kibana'); const mockCasesContract = jest.requireActual('@kbn/cases-plugin/public/mocks'); @@ -67,6 +72,7 @@ jest.mock('../../../../common/lib/kibana', () => { data: { search: jest.fn(), query: jest.fn(), + dataViews: jest.fn(), }, uiSettings: { get: jest.fn(), @@ -108,11 +114,6 @@ jest.mock('react-redux', () => { }; }); -jest.mock('../../../../common/hooks/use_selector', () => ({ - useShallowEqualSelector: () => mockTimelineModel, - useDeepEqualSelector: () => mockTimelineModel, -})); - jest.mock('../../../../common/components/link_to'); // Prevent Resolver from rendering @@ -129,9 +130,61 @@ jest.mock('../../fields_browser/create_field_button', () => ({ useCreateFieldButton: () => <>, })); -// SKIP: https://github.com/elastic/kibana/issues/143718 -describe.skip('Body', () => { - const mount = useMountAppended(); +jest.mock('@elastic/eui', () => { + const original = jest.requireActual('@elastic/eui'); + return { + ...original, + EuiScreenReaderOnly: () => <>, + }; +}); +jest.mock('suricata-sid-db', () => { + return { + db: [], + }; +}); +jest.mock('react-beautiful-dnd', () => { + const original = jest.requireActual('react-beautiful-dnd'); + return { + ...original, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + Droppable: ({ children }: { children: any }) => + children( + { + draggableProps: { + style: {}, + }, + innerRef: jest.fn(), + }, + {} + ), + // eslint-disable-next-line @typescript-eslint/no-explicit-any + Draggable: ({ children }: { children: any }) => + children( + { + draggableProps: { + style: {}, + }, + innerRef: jest.fn(), + }, + {} + ), + DraggableProvided: () => <>, + DraggableStateSnapshot: () => <>, + DraggingStyle: () => <>, + NotDraggingStyle: () => <>, + }; +}); + +describe('Body', () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const getWrapper = async (childrenComponent: JSX.Element, store?: any) => { + const wrapper = mount(childrenComponent, { + wrappingComponent: TestProviders, + wrappingComponentProps: store ?? {}, + }); + await waitFor(() => wrapper.find('[data-test-subj="suricataRefs"]').exists()); + return wrapper; + }; const mockRefetch = jest.fn(); let appToastsMock: jest.Mocked>; @@ -145,8 +198,8 @@ describe.skip('Body', () => { const props: Props = { activePage: 0, browserFields: mockBrowserFields, - data: mockTimelineData, - id: 'timeline-test', + data: [mockTimelineData[0]], + id: TimelineId.test, refetch: mockRefetch, renderCellValue: DefaultCellRenderer, rowRenderers: defaultRowRenderers, @@ -162,58 +215,58 @@ describe.skip('Body', () => { mockDispatch.mockClear(); }); - test('it renders the column headers', () => { - const wrapper = mount( - - - - ); - + test('it renders the column headers', async () => { + const wrapper = await getWrapper(); expect(wrapper.find('[data-test-subj="column-headers"]').first().exists()).toEqual(true); }); - test('it renders the scroll container', () => { - const wrapper = mount( - - - - ); - + test('it renders the scroll container', async () => { + const wrapper = await getWrapper(); expect(wrapper.find('[data-test-subj="timeline-body"]').first().exists()).toEqual(true); }); - test('it renders events', () => { - const wrapper = mount( - - - - ); - + test('it renders events', async () => { + const wrapper = await getWrapper(); expect(wrapper.find('[data-test-subj="events"]').first().exists()).toEqual(true); }); test('it renders a tooltip for timestamp', async () => { + const { storage } = createSecuritySolutionStorageMock(); const headersJustTimestamp = defaultHeaders.filter((h) => h.id === '@timestamp'); - const testProps = { ...props, columnHeaders: headersJustTimestamp }; - const wrapper = mount( - - - + const state: State = { + ...mockGlobalState, + timeline: { + ...mockGlobalState.timeline, + timelineById: { + ...mockGlobalState.timeline.timelineById, + [TimelineId.test]: { + ...mockGlobalState.timeline.timelineById[TimelineId.test], + id: TimelineId.test, + columns: headersJustTimestamp, + }, + }, + }, + }; + + const store = createStore( + state, + SUB_PLUGINS_REDUCER, + { dataTable: tGridReducer }, + kibanaObservable, + storage ); - wrapper.update(); - await waitFor(() => { - wrapper.update(); - headersJustTimestamp.forEach(() => { - expect( - wrapper - .find('[data-test-subj="data-driven-columns"]') - .first() - .find('[data-test-subj="localized-date-tool-tip"]') - .exists() - ).toEqual(true); - }); + const wrapper = await getWrapper(, { store }); + + headersJustTimestamp.forEach(() => { + expect( + wrapper + .find('[data-test-subj="data-driven-columns"]') + .first() + .find('[data-test-subj="localized-date-tool-tip"]') + .exists() + ).toEqual(true); }); - }, 20000); + }); }); describe('action on event', () => { const addaNoteToEvent = (wrapper: ReturnType, note: string) => { @@ -231,14 +284,11 @@ describe.skip('Body', () => { mockDispatch.mockClear(); }); - test('Add a note to an event', () => { - const wrapper = mount( - - - - ); - addaNoteToEvent(wrapper, 'hello world'); + test('Add a note to an event', async () => { + const wrapper = await getWrapper(); + addaNoteToEvent(wrapper, 'hello world'); + wrapper.update(); expect(mockDispatch).toHaveBeenNthCalledWith( 3, expect.objectContaining({ @@ -263,7 +313,7 @@ describe.skip('Body', () => { ); }); - test('Add two notes to an event', () => { + test('Add two notes to an event', async () => { const { storage } = createSecuritySolutionStorageMock(); const state: State = { ...mockGlobalState, @@ -288,13 +338,10 @@ describe.skip('Body', () => { storage ); - const Proxy = (proxyProps: Props) => ( - - - - ); + const Proxy = (proxyProps: Props) => ; + + const wrapper = await getWrapper(, { store }); - const wrapper = mount(); addaNoteToEvent(wrapper, 'hello world'); mockDispatch.mockClear(); addaNoteToEvent(wrapper, 'new hello world'); @@ -328,11 +375,7 @@ describe.skip('Body', () => { mockDispatch.mockReset(); }); test('call the right reduce action to show event details for query tab', async () => { - const wrapper = mount( - - - - ); + const wrapper = await getWrapper(); wrapper.find(`[data-test-subj="expand-event"]`).first().simulate('click'); wrapper.update(); @@ -353,11 +396,7 @@ describe.skip('Body', () => { }); test('call the right reduce action to show event details for pinned tab', async () => { - const wrapper = mount( - - - - ); + const wrapper = await getWrapper(); wrapper.find(`[data-test-subj="expand-event"]`).first().simulate('click'); wrapper.update(); @@ -378,11 +417,7 @@ describe.skip('Body', () => { }); test('call the right reduce action to show event details for notes tab', async () => { - const wrapper = mount( - - - - ); + const wrapper = await getWrapper(); wrapper.find(`[data-test-subj="expand-event"]`).first().simulate('click'); wrapper.update(); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_row_renderer.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_row_renderer.test.tsx index 10d196596b7b..ea06fd70bbbf 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_row_renderer.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_row_renderer.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { render, screen } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import { shallow } from 'enzyme'; import { cloneDeep } from 'lodash'; import React from 'react'; @@ -48,6 +48,11 @@ describe('get_column_renderer', () => { let auditd: Ecs; const mount = useMountAppended(); + const getWrapper = async (childrenComponent: JSX.Element) => { + const wrapper = mount(childrenComponent); + await waitFor(() => wrapper.find('[data-test-subj="suricataRefs"]').exists()); + return wrapper; + }; beforeEach(() => { nonSuricata = cloneDeep(mockTimelineData[0].ecs); suricata = cloneDeep(mockTimelineData[2].ecs); @@ -68,14 +73,15 @@ describe('get_column_renderer', () => { expect(wrapper).toMatchSnapshot(); }); - test('should render plain row data when it is a non suricata row', () => { + test('should render plain row data when it is a non suricata row', async () => { const rowRenderer = getRowRenderer({ data: nonSuricata, rowRenderers: defaultRowRenderers }); const row = rowRenderer?.renderRow({ data: nonSuricata, isDraggable: true, scopeId: TimelineId.test, }); - const wrapper = mount( + + const wrapper = await getWrapper( {row} @@ -83,14 +89,14 @@ describe('get_column_renderer', () => { expect(wrapper.text()).toEqual(''); }); - test('should render a suricata row data when it is a suricata row', () => { + test('should render a suricata row data when it is a suricata row', async () => { const rowRenderer = getRowRenderer({ data: suricata, rowRenderers: defaultRowRenderers }); const row = rowRenderer?.renderRow({ data: suricata, isDraggable: true, scopeId: TimelineId.test, }); - const wrapper = mount( + const wrapper = await getWrapper( {row} @@ -100,7 +106,7 @@ describe('get_column_renderer', () => { ); }); - test('should render a suricata row data if event.category is network_traffic', () => { + test('should render a suricata row data if event.category is network_traffic', async () => { suricata.event = { ...suricata.event, ...{ category: ['network_traffic'] } }; const rowRenderer = getRowRenderer({ data: suricata, rowRenderers: defaultRowRenderers }); const row = rowRenderer?.renderRow({ @@ -108,7 +114,7 @@ describe('get_column_renderer', () => { isDraggable: true, scopeId: TimelineId.test, }); - const wrapper = mount( + const wrapper = await getWrapper( {row} @@ -118,7 +124,7 @@ describe('get_column_renderer', () => { ); }); - test('should render a zeek row data if event.category is network_traffic', () => { + test('should render a zeek row data if event.category is network_traffic', async () => { zeek.event = { ...zeek.event, ...{ category: ['network_traffic'] } }; const rowRenderer = getRowRenderer({ data: zeek, rowRenderers: defaultRowRenderers }); const row = rowRenderer?.renderRow({ @@ -126,7 +132,7 @@ describe('get_column_renderer', () => { isDraggable: true, scopeId: TimelineId.test, }); - const wrapper = mount( + const wrapper = await getWrapper( {row} @@ -136,7 +142,7 @@ describe('get_column_renderer', () => { ); }); - test('should render a system row data if event.category is network_traffic', () => { + test('should render a system row data if event.category is network_traffic', async () => { system.event = { ...system.event, ...{ category: ['network_traffic'] } }; const rowRenderer = getRowRenderer({ data: system, rowRenderers: defaultRowRenderers }); const row = rowRenderer?.renderRow({ @@ -144,7 +150,7 @@ describe('get_column_renderer', () => { isDraggable: true, scopeId: TimelineId.test, }); - const wrapper = mount( + const wrapper = await getWrapper( {row} @@ -154,7 +160,7 @@ describe('get_column_renderer', () => { ); }); - test('should render a auditd row data if event.category is network_traffic', () => { + test('should render a auditd row data if event.category is network_traffic', async () => { auditd.event = { ...auditd.event, ...{ category: ['network_traffic'] } }; const rowRenderer = getRowRenderer({ data: auditd, rowRenderers: defaultRowRenderers }); const row = rowRenderer?.renderRow({ @@ -162,7 +168,7 @@ describe('get_column_renderer', () => { isDraggable: true, scopeId: TimelineId.test, }); - const wrapper = mount( + const wrapper = await getWrapper( {row} diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_details.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_details.test.tsx index 2a4e82a18399..38155bad81a8 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_details.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_details.test.tsx @@ -14,6 +14,7 @@ import '../../../../../../common/mock/match_media'; import { TestProviders } from '../../../../../../common/mock/test_providers'; import { useMountAppended } from '../../../../../../common/utils/use_mount_appended'; import { SuricataDetails } from './suricata_details'; +import { waitFor } from '@testing-library/react'; jest.mock('../../../../../../common/lib/kibana'); @@ -30,14 +31,19 @@ jest.mock('../../../../../../common/components/link_to'); describe('SuricataDetails', () => { const mount = useMountAppended(); + const getWrapper = async (childrenComponent: JSX.Element) => { + const wrapper = mount(childrenComponent); + await waitFor(() => wrapper.find('[data-test-subj="suricataRefs"]').exists()); // check for presence of query input + return wrapper; + }; describe('rendering', () => { test('it renders the default SuricataDetails', () => { const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); }); - test('it returns text if the data does contain suricata data', () => { - const wrapper = mount( + test('it returns text if the data does contain suricata data', async () => { + const wrapper = await getWrapper( diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_refs.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_refs.tsx index 2300bcfaa5bc..602ca3ebbd57 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_refs.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_refs.tsx @@ -6,7 +6,7 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiLink } from '@elastic/eui'; -import React from 'react'; +import React, { useEffect, useState } from 'react'; import styled from 'styled-components'; import { getLinksFromSignature } from './suricata_links'; @@ -18,21 +18,41 @@ const LinkEuiFlexItem = styled(EuiFlexItem)` LinkEuiFlexItem.displayName = 'LinkEuiFlexItem'; export const SuricataRefs = React.memo<{ signatureId: number }>(({ signatureId }) => { - let comp = <>; - getLinksFromSignature(signatureId).then((links) => { - comp = ( - - {links.map((link) => ( + const [linksFromSignature, setLinksFromSignature] = useState(undefined); + useEffect(() => { + let isSubscribed = true; + async function getLinks() { + if (signatureId != null) { + try { + const links = await getLinksFromSignature(signatureId); + if (isSubscribed && links != null) { + setLinksFromSignature(links); + } + } catch (exc) { + setLinksFromSignature(undefined); + } + } else if (isSubscribed) { + setLinksFromSignature(undefined); + } + } + getLinks(); + return () => { + isSubscribed = false; + }; + }, [signatureId]); + + return ( + + {linksFromSignature && + linksFromSignature.map((link) => ( {link} ))} - - ); - }); - return comp; + + ); }); SuricataRefs.displayName = 'SuricataRefs'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_row_renderer.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_row_renderer.test.tsx index 833fd2cff966..67fb4fe5dfce 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_row_renderer.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_row_renderer.test.tsx @@ -8,6 +8,7 @@ import { shallow } from 'enzyme'; import { cloneDeep } from 'lodash/fp'; import React from 'react'; +import { waitFor } from '@testing-library/react'; import { removeExternalLinkText } from '@kbn/securitysolution-io-ts-utils'; import type { Ecs } from '../../../../../../../common/ecs'; @@ -31,16 +32,22 @@ jest.mock('@elastic/eui', () => { jest.mock('../../../../../../common/components/link_to'); describe('suricata_row_renderer', () => { - const mount = useMountAppended(); let nonSuricata: Ecs; let suricata: Ecs; + const mount = useMountAppended(); + + const getWrapper = async (childrenComponent: JSX.Element) => { + const wrapper = mount(childrenComponent); + await waitFor(() => wrapper.find('[data-test-subj="suricataRefs"]').exists()); + return wrapper; + }; beforeEach(() => { nonSuricata = cloneDeep(mockTimelineData[0].ecs); suricata = cloneDeep(mockTimelineData[2].ecs); }); - test('renders correctly against snapshot', () => { + test('renders correctly against snapshot', async () => { const children = suricataRowRenderer.renderRow({ data: nonSuricata, isDraggable: true, @@ -59,13 +66,14 @@ describe('suricata_row_renderer', () => { expect(suricataRowRenderer.isInstance(suricata)).toBe(true); }); - test('should render a suricata row', () => { + test('should render a suricata row', async () => { const children = suricataRowRenderer.renderRow({ data: suricata, isDraggable: true, scopeId: TimelineId.test, }); - const wrapper = mount( + + const wrapper = await getWrapper( {children} @@ -80,14 +88,14 @@ describe('suricata_row_renderer', () => { ); }); - test('should render a suricata row even if it does not have a suricata signature', () => { + test('should render a suricata row even if it does not have a suricata signature', async () => { delete suricata?.suricata?.eve?.alert?.signature; const children = suricataRowRenderer.renderRow({ data: suricata, isDraggable: true, scopeId: TimelineId.test, }); - const wrapper = mount( + const wrapper = await getWrapper( {children} diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/index.test.tsx index ddcc2df231a0..e27871d4c901 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/index.test.tsx @@ -11,6 +11,7 @@ import { TestProviders } from '../../../../common/mock/test_providers'; import { useMountAppended } from '../../../../common/utils/use_mount_appended'; import { DataProviders } from '.'; +import { TimelineId } from '../../../../../common/types'; describe('DataProviders', () => { const mount = useMountAppended(); @@ -54,7 +55,7 @@ describe('DataProviders', () => { test('it may be resized vertically via a resize handle', () => { const wrapper = mount( - + ); @@ -67,7 +68,7 @@ describe('DataProviders', () => { test('it never grows taller than one third (33%) of the view height', () => { const wrapper = mount( - + ); @@ -80,7 +81,7 @@ describe('DataProviders', () => { test('it automatically displays scroll bars when the width or height of the data providers exceeds the drop target', () => { const wrapper = mount( - + ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/header/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/header/index.test.tsx index 196c2655e6ab..eeb383f6a229 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/header/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/header/index.test.tsx @@ -17,6 +17,7 @@ import { useMountAppended } from '../../../../common/utils/use_mount_appended'; import { TimelineHeader } from '.'; import { TimelineStatus, TimelineType } from '../../../../../common/types/timeline'; +import { waitFor } from '@testing-library/react'; const mockUiSettingsForFilterManager = coreMock.createStart().uiSettings; @@ -25,6 +26,11 @@ jest.mock('../../../../common/lib/kibana'); describe('Header', () => { const indexPattern = mockIndexPattern; const mount = useMountAppended(); + const getWrapper = async (childrenComponent: JSX.Element) => { + const wrapper = mount(childrenComponent); + await waitFor(() => wrapper.find('[data-test-subj="timelineCallOutUnauthorized"]').exists()); + return wrapper; + }; const props = { browserFields: {}, dataProviders: mockDataProviders, @@ -48,9 +54,9 @@ describe('Header', () => { expect(wrapper).toMatchSnapshot(); }); - test('it renders the data providers when show is true', () => { + test('it renders the data providers when show is true', async () => { const testProps = { ...props, show: true }; - const wrapper = mount( + const wrapper = await getWrapper( @@ -59,14 +65,14 @@ describe('Header', () => { expect(wrapper.find('[data-test-subj="dataProviders"]').exists()).toEqual(true); }); - test('it renders the unauthorized call out providers', () => { + test('it renders the unauthorized call out providers', async () => { const testProps = { ...props, filterManager: new FilterManager(mockUiSettingsForFilterManager), showCallOutUnauthorizedMsg: true, }; - const wrapper = mount( + const wrapper = await getWrapper( @@ -75,14 +81,14 @@ describe('Header', () => { expect(wrapper.find('[data-test-subj="timelineCallOutUnauthorized"]').exists()).toEqual(true); }); - test('it renders the unauthorized call out with correct icon', () => { + test('it renders the unauthorized call out with correct icon', async () => { const testProps = { ...props, filterManager: new FilterManager(mockUiSettingsForFilterManager), showCallOutUnauthorizedMsg: true, }; - const wrapper = mount( + const wrapper = await getWrapper( @@ -93,14 +99,14 @@ describe('Header', () => { ).toEqual('alert'); }); - test('it renders the unauthorized call out with correct message', () => { + test('it renders the unauthorized call out with correct message', async () => { const testProps = { ...props, filterManager: new FilterManager(mockUiSettingsForFilterManager), showCallOutUnauthorizedMsg: true, }; - const wrapper = mount( + const wrapper = await getWrapper( @@ -113,7 +119,7 @@ describe('Header', () => { ); }); - test('it renders the immutable timeline call out providers', () => { + test('it renders the immutable timeline call out providers', async () => { const testProps = { ...props, filterManager: new FilterManager(mockUiSettingsForFilterManager), @@ -121,7 +127,7 @@ describe('Header', () => { status: TimelineStatus.immutable, }; - const wrapper = mount( + const wrapper = await getWrapper( @@ -130,7 +136,7 @@ describe('Header', () => { expect(wrapper.find('[data-test-subj="timelineImmutableCallOut"]').exists()).toEqual(true); }); - test('it renders the immutable timeline call out with correct icon', () => { + test('it renders the immutable timeline call out with correct icon', async () => { const testProps = { ...props, filterManager: new FilterManager(mockUiSettingsForFilterManager), @@ -138,7 +144,7 @@ describe('Header', () => { status: TimelineStatus.immutable, }; - const wrapper = mount( + const wrapper = await getWrapper( @@ -149,7 +155,7 @@ describe('Header', () => { ).toEqual('alert'); }); - test('it renders the immutable timeline call out with correct message', () => { + test('it renders the immutable timeline call out with correct message', async () => { const testProps = { ...props, filterManager: new FilterManager(mockUiSettingsForFilterManager), @@ -157,7 +163,7 @@ describe('Header', () => { status: TimelineStatus.immutable, }; - const wrapper = mount( + const wrapper = await getWrapper( diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.test.tsx index 4926b11cd9b1..ff129689fb5f 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.test.tsx @@ -14,7 +14,7 @@ import { AddToFavoritesButton, NewTimeline } from './helpers'; import { useCreateTimelineButton } from './use_create_timeline'; import { kibanaObservable, TestProviders } from '../../../../common/mock/test_providers'; import { timelineActions } from '../../../store/timeline'; -import { TimelineStatus, TimelineType } from '../../../../../common/types/timeline'; +import { TimelineId, TimelineStatus, TimelineType } from '../../../../../common/types/timeline'; import { createSecuritySolutionStorageMock, mockGlobalState, @@ -100,7 +100,7 @@ describe('Favorite Button', () => { test('should render favorite button', () => { const wrapper = mount( - + ); @@ -110,7 +110,7 @@ describe('Favorite Button', () => { test('Favorite button should be enabled ', () => { const wrapper = mount( - + ); @@ -123,7 +123,7 @@ describe('Favorite Button', () => { const spy = jest.spyOn(timelineActions, 'updateIsFavorite'); const wrapper = mount( - + ); @@ -142,8 +142,8 @@ describe('Favorite Button', () => { timeline: { ...mockGlobalState.timeline, timelineById: { - test: { - ...mockGlobalState.timeline.timelineById.test, + [TimelineId.test]: { + ...mockGlobalState.timeline.timelineById[TimelineId.test], isFavorite: true, }, }, @@ -156,7 +156,7 @@ describe('Favorite Button', () => { ); const wrapper = mount( - + ); @@ -176,8 +176,8 @@ describe('Favorite Button', () => { timeline: { ...mockGlobalState.timeline, timelineById: { - test: { - ...mockGlobalState.timeline.timelineById.test, + [TimelineId.test]: { + ...mockGlobalState.timeline.timelineById[TimelineId.test], status: TimelineStatus.immutable, timelineType: TimelineType.template, templateTimelineId: 'mock-template-timeline-id', @@ -193,7 +193,7 @@ describe('Favorite Button', () => { ); const wrapper = mount( - + ); expect( @@ -212,8 +212,8 @@ describe('Favorite Button', () => { timeline: { ...mockGlobalState.timeline, timelineById: { - test: { - ...mockGlobalState.timeline.timelineById.test, + [TimelineId.test]: { + ...mockGlobalState.timeline.timelineById[TimelineId.test], status: TimelineStatus.active, timelineType: TimelineType.template, templateTimelineId: 'mock-template-timeline-id', diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx index cc48a58717ee..c53360cb9168 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx @@ -28,6 +28,7 @@ import { mockSourcererScope } from '../../../../common/containers/sourcerer/mock import { Direction } from '../../../../../common/search_strategy'; import { mockCasesContext } from '@kbn/cases-plugin/public/mocks/mock_cases_context'; import * as helpers from '../../../../common/lib/kuery'; +import { waitFor } from '@testing-library/react'; jest.mock('../../../containers', () => ({ useTimelineEvents: jest.fn(), @@ -109,7 +110,11 @@ describe('Timeline', () => { const endDate = '2018-03-24T03:33:52.253Z'; const mount = useMountAppended(); - + const getWrapper = async (childrenComponent: JSX.Element) => { + const wrapper = mount(childrenComponent); + await waitFor(() => wrapper.find('[data-test-subj="timelineHeader"]').exists()); + return wrapper; + }; beforeEach(() => { (useTimelineEvents as jest.Mock).mockReturnValue([ false, @@ -162,8 +167,8 @@ describe('Timeline', () => { spyCombineQueries.mockClear(); }); - test('should trim kqlQueryExpression', () => { - mount( + test('should trim kqlQueryExpression', async () => { + await getWrapper( @@ -184,8 +189,8 @@ describe('Timeline', () => { expect(wrapper.find('QueryTabContentComponent')).toMatchSnapshot(); }); - test('it renders the timeline header', () => { - const wrapper = mount( + test('it renders the timeline header', async () => { + const wrapper = await getWrapper( @@ -194,8 +199,8 @@ describe('Timeline', () => { expect(wrapper.find('[data-test-subj="timelineHeader"]').exists()).toEqual(true); }); - test('it renders the timeline table', () => { - const wrapper = mount( + test('it renders the timeline table', async () => { + const wrapper = await getWrapper( @@ -206,7 +211,7 @@ describe('Timeline', () => { ).toEqual(true); }); - test('it does render the timeline table when the source is loading with no events', () => { + test('it does render the timeline table when the source is loading with no events', async () => { (useSourcererDataView as jest.Mock).mockReturnValue({ browserFields: {}, loading: true, @@ -214,7 +219,7 @@ describe('Timeline', () => { selectedPatterns: [], missingPatterns: [], }); - const wrapper = mount( + const wrapper = await getWrapper( @@ -226,8 +231,8 @@ describe('Timeline', () => { expect(wrapper.find('[data-test-subj="events"]').exists()).toEqual(false); }); - test('it does NOT render the timeline table when start is empty', () => { - const wrapper = mount( + test('it does NOT render the timeline table when start is empty', async () => { + const wrapper = await getWrapper( @@ -239,8 +244,8 @@ describe('Timeline', () => { expect(wrapper.find('[data-test-subj="events"]').exists()).toEqual(false); }); - test('it does NOT render the timeline table when end is empty', () => { - const wrapper = mount( + test('it does NOT render the timeline table when end is empty', async () => { + const wrapper = await getWrapper( @@ -252,8 +257,8 @@ describe('Timeline', () => { expect(wrapper.find('[data-test-subj="events"]').exists()).toEqual(false); }); - test('it does NOT render the paging footer when you do NOT have any data providers', () => { - const wrapper = mount( + test('it does NOT render the paging footer when you do NOT have any data providers', async () => { + const wrapper = await getWrapper( @@ -262,8 +267,8 @@ describe('Timeline', () => { expect(wrapper.find('[data-test-subj="table-pagination"]').exists()).toEqual(false); }); - it('it shows the timeline footer', () => { - const wrapper = mount( + it('it shows the timeline footer', async () => { + const wrapper = await getWrapper( diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/session_tab_content/use_session_view.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/session_tab_content/use_session_view.test.tsx index 773790b4da65..506353a9b704 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/session_tab_content/use_session_view.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/session_tab_content/use_session_view.test.tsx @@ -162,6 +162,7 @@ describe('useSessionView with active timeline and a session id and graph event i height: 1000, sessionEntityId: 'test', loadAlertDetails: mockDetails, + canAccessEndpointManagement: false, }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/session_tab_content/use_session_view.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/session_tab_content/use_session_view.tsx index 8b5add5ae73b..0cb001b6aa3e 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/session_tab_content/use_session_view.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/session_tab_content/use_session_view.tsx @@ -34,6 +34,7 @@ import { useGlobalFullScreen, } from '../../../../common/containers/use_full_screen'; import { detectionsTimelineIds } from '../../../containers/helpers'; +import { useUserPrivileges } from '../../../../common/components/user_privileges'; import { timelineActions, timelineSelectors } from '../../../store/timeline'; import { timelineDefaults } from '../../../store/timeline/defaults'; import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; @@ -266,6 +267,7 @@ export const useSessionView = ({ }, [scopeId]); const { globalFullScreen } = useGlobalFullScreen(); const { timelineFullScreen } = useTimelineFullScreen(); + const { canAccessEndpointManagement } = useUserPrivileges().endpointPrivileges; const defaults = isTimelineScope(scopeId) ? timelineDefaults : tableDefaults; const { sessionViewConfig, activeTab } = useDeepEqualSelector((state) => ({ @@ -310,9 +312,17 @@ export const useSessionView = ({ loadAlertDetails: openDetailsPanel, isFullScreen: fullScreen, height: heightMinusSearchBar, + canAccessEndpointManagement, }) : null; - }, [fullScreen, openDetailsPanel, sessionView, sessionViewConfig, height]); + }, [ + height, + sessionViewConfig, + sessionView, + openDetailsPanel, + fullScreen, + canAccessEndpointManagement, + ]); return { openDetailsPanel, diff --git a/x-pack/plugins/security_solution/server/features.ts b/x-pack/plugins/security_solution/server/features.ts index fc3307a65009..64a082eaea9a 100644 --- a/x-pack/plugins/security_solution/server/features.ts +++ b/x-pack/plugins/security_solution/server/features.ts @@ -185,6 +185,13 @@ export const getKibanaPrivilegesFeaturePrivileges = ( subFeatures: experimentalFeatures.endpointRbacEnabled ? [ { + requireAllSpaces: true, + privilegesTooltip: i18n.translate( + 'xpack.securitySolution.featureRegistry.subFeatures.endpointList.privilegesTooltip', + { + defaultMessage: 'All Spaces is required for Endpoint List access.', + } + ), name: i18n.translate('xpack.securitySolution.featureRegistry.subFeatures.endpointList', { defaultMessage: 'Endpoint List', }), @@ -195,7 +202,7 @@ export const getKibanaPrivilegesFeaturePrivileges = ( { api: [`${APP_ID}-writeEndpointList`, `${APP_ID}-readEndpointList`], id: 'endpoint_list_all', - includeIn: 'all', + includeIn: 'none', name: 'All', savedObject: { all: [], @@ -206,7 +213,7 @@ export const getKibanaPrivilegesFeaturePrivileges = ( { api: [`${APP_ID}-readEndpointList`], id: 'endpoint_list_read', - includeIn: 'read', + includeIn: 'none', name: 'Read', savedObject: { all: [], @@ -219,6 +226,13 @@ export const getKibanaPrivilegesFeaturePrivileges = ( ], }, { + requireAllSpaces: true, + privilegesTooltip: i18n.translate( + 'xpack.securitySolution.featureRegistry.subFeatures.trustedApplications.privilegesTooltip', + { + defaultMessage: 'All Spaces is required for Trusted Applications access.', + } + ), name: i18n.translate( 'xpack.securitySolution.featureRegistry.subFeatures.trustedApplications', { @@ -232,7 +246,7 @@ export const getKibanaPrivilegesFeaturePrivileges = ( { api: [`${APP_ID}-writeTrustedApplications`, `${APP_ID}-readTrustedApplications`], id: 'trusted_applications_all', - includeIn: 'all', + includeIn: 'none', name: 'All', savedObject: { all: [], @@ -243,7 +257,7 @@ export const getKibanaPrivilegesFeaturePrivileges = ( { api: [`${APP_ID}-readTrustedApplications`], id: 'trusted_applications_read', - includeIn: 'read', + includeIn: 'none', name: 'Read', savedObject: { all: [], @@ -256,6 +270,13 @@ export const getKibanaPrivilegesFeaturePrivileges = ( ], }, { + requireAllSpaces: true, + privilegesTooltip: i18n.translate( + 'xpack.securitySolution.featureRegistry.subFeatures.hostIsolationExceptions.privilegesTooltip', + { + defaultMessage: 'All Spaces is required for Host Isolation Exceptions access.', + } + ), name: i18n.translate( 'xpack.securitySolution.featureRegistry.subFeatures.hostIsolationExceptions', { @@ -272,7 +293,7 @@ export const getKibanaPrivilegesFeaturePrivileges = ( `${APP_ID}-readHostIsolationExceptions`, ], id: 'host_isolation_exceptions_all', - includeIn: 'all', + includeIn: 'none', name: 'All', savedObject: { all: [], @@ -283,7 +304,7 @@ export const getKibanaPrivilegesFeaturePrivileges = ( { api: [`${APP_ID}-readHostIsolationExceptions`], id: 'host_isolation_exceptions_read', - includeIn: 'read', + includeIn: 'none', name: 'Read', savedObject: { all: [], @@ -296,6 +317,13 @@ export const getKibanaPrivilegesFeaturePrivileges = ( ], }, { + requireAllSpaces: true, + privilegesTooltip: i18n.translate( + 'xpack.securitySolution.featureRegistry.subFeatures.blockList.privilegesTooltip', + { + defaultMessage: 'All Spaces is required for Blocklist access.', + } + ), name: i18n.translate('xpack.securitySolution.featureRegistry.subFeatures.blockList', { defaultMessage: 'Blocklist', }), @@ -306,7 +334,7 @@ export const getKibanaPrivilegesFeaturePrivileges = ( { api: [`${APP_ID}-writeBlocklist`, `${APP_ID}-readBlocklist`], id: 'blocklist_all', - includeIn: 'all', + includeIn: 'none', name: 'All', savedObject: { all: [], @@ -317,7 +345,7 @@ export const getKibanaPrivilegesFeaturePrivileges = ( { api: [`${APP_ID}-readBlocklist`], id: 'blocklist_read', - includeIn: 'read', + includeIn: 'none', name: 'Read', savedObject: { all: [], @@ -330,6 +358,13 @@ export const getKibanaPrivilegesFeaturePrivileges = ( ], }, { + requireAllSpaces: true, + privilegesTooltip: i18n.translate( + 'xpack.securitySolution.featureRegistry.subFeatures.eventFilters.privilegesTooltip', + { + defaultMessage: 'All Spaces is required for Event Filters access.', + } + ), name: i18n.translate('xpack.securitySolution.featureRegistry.subFeatures.eventFilters', { defaultMessage: 'Event Filters', }), @@ -340,7 +375,7 @@ export const getKibanaPrivilegesFeaturePrivileges = ( { api: [`${APP_ID}-writeEventFilters`, `${APP_ID}-readEventFilters`], id: 'event_filters_all', - includeIn: 'all', + includeIn: 'none', name: 'All', savedObject: { all: [], @@ -351,7 +386,7 @@ export const getKibanaPrivilegesFeaturePrivileges = ( { api: [`${APP_ID}-readEventFilters`], id: 'event_filters_read', - includeIn: 'read', + includeIn: 'none', name: 'Read', savedObject: { all: [], @@ -364,6 +399,13 @@ export const getKibanaPrivilegesFeaturePrivileges = ( ], }, { + requireAllSpaces: true, + privilegesTooltip: i18n.translate( + 'xpack.securitySolution.featureRegistry.subFeatures.policyManagement.privilegesTooltip', + { + defaultMessage: 'All Spaces is required for Policy Management access.', + } + ), name: i18n.translate( 'xpack.securitySolution.featureRegistry.subFeatures.policyManagement', { @@ -377,7 +419,7 @@ export const getKibanaPrivilegesFeaturePrivileges = ( { api: [`${APP_ID}-writePolicyManagement`, `${APP_ID}-readPolicyManagement`], id: 'policy_management_all', - includeIn: 'all', + includeIn: 'none', name: 'All', savedObject: { all: [], @@ -388,7 +430,7 @@ export const getKibanaPrivilegesFeaturePrivileges = ( { api: [`${APP_ID}-readPolicyManagement`], id: 'policy_management_read', - includeIn: 'read', + includeIn: 'none', name: 'Read', savedObject: { all: [], @@ -401,6 +443,13 @@ export const getKibanaPrivilegesFeaturePrivileges = ( ], }, { + requireAllSpaces: true, + privilegesTooltip: i18n.translate( + 'xpack.securitySolution.featureRegistry.subFeatures.actionsLogManagement.privilegesTooltip', + { + defaultMessage: 'All Spaces is required for Actions Log Management access.', + } + ), name: i18n.translate( 'xpack.securitySolution.featureRegistry.subFeatures.actionsLogManagement', { @@ -417,7 +466,7 @@ export const getKibanaPrivilegesFeaturePrivileges = ( `${APP_ID}-readActionsLogManagement`, ], id: 'actions_log_management_all', - includeIn: 'all', + includeIn: 'none', name: 'All', savedObject: { all: [], @@ -428,7 +477,7 @@ export const getKibanaPrivilegesFeaturePrivileges = ( { api: [`${APP_ID}-readActionsLogManagement`], id: 'actions_log_management_read', - includeIn: 'read', + includeIn: 'none', name: 'Read', savedObject: { all: [], @@ -441,6 +490,13 @@ export const getKibanaPrivilegesFeaturePrivileges = ( ], }, { + requireAllSpaces: true, + privilegesTooltip: i18n.translate( + 'xpack.securitySolution.featureRegistry.subFeatures.hostIsolation.privilegesTooltip', + { + defaultMessage: 'All Spaces is required for Host Isolation access.', + } + ), name: i18n.translate('xpack.securitySolution.featureRegistry.subFeatures.hostIsolation', { defaultMessage: 'Host Isolation', }), @@ -451,7 +507,7 @@ export const getKibanaPrivilegesFeaturePrivileges = ( { api: [`${APP_ID}-writeHostIsolation`], id: 'host_isolation_all', - includeIn: 'all', + includeIn: 'none', name: 'All', savedObject: { all: [], @@ -464,6 +520,13 @@ export const getKibanaPrivilegesFeaturePrivileges = ( ], }, { + requireAllSpaces: true, + privilegesTooltip: i18n.translate( + 'xpack.securitySolution.featureRegistry.subFeatures.processOperations.privilegesTooltip', + { + defaultMessage: 'All Spaces is required for Process Operations access.', + } + ), name: i18n.translate( 'xpack.securitySolution.featureRegistry.subFeatures.processOperations', { @@ -477,7 +540,7 @@ export const getKibanaPrivilegesFeaturePrivileges = ( { api: [`${APP_ID}-writeProcessOperations`], id: 'process_operations_all', - includeIn: 'all', + includeIn: 'none', name: 'All', savedObject: { all: [], @@ -490,6 +553,13 @@ export const getKibanaPrivilegesFeaturePrivileges = ( ], }, { + requireAllSpaces: true, + privilegesTooltip: i18n.translate( + 'xpack.securitySolution.featureRegistry.subFeatures.fileOperations.privilegesTooltip', + { + defaultMessage: 'All Spaces is required for File Operations access.', + } + ), name: i18n.translate('xpack.securitySolution.featureRegistr.subFeatures.fileOperations', { defaultMessage: 'File Operations', }), @@ -500,7 +570,7 @@ export const getKibanaPrivilegesFeaturePrivileges = ( { api: [`${APP_ID}-writeFileOperations`], id: 'file_operations_all', - includeIn: 'all', + includeIn: 'none', name: 'All', savedObject: { all: [], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/gather_referenced_exceptions.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/gather_referenced_exceptions.test.ts index 797c45ea45fa..5f903386ab01 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/gather_referenced_exceptions.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/gather_referenced_exceptions.test.ts @@ -10,72 +10,274 @@ import { savedObjectsClientMock } from '@kbn/core/server/mocks'; import { findExceptionList } from '@kbn/lists-plugin/server/services/exception_lists/find_exception_list'; import { getExceptionListSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_schema.mock'; import { getImportRulesSchemaMock } from '../../../../../../common/detection_engine/rule_management/mocks'; -import { getReferencedExceptionLists } from './gather_referenced_exceptions'; +import { + getReferencedExceptionLists, + parseReferencedExceptionsLists, +} from './gather_referenced_exceptions'; jest.mock('@kbn/lists-plugin/server/services/exception_lists/find_exception_list'); -describe('getReferencedExceptionLists', () => { - let savedObjectsClient: jest.Mocked; +describe('get referenced exceptions', () => { + describe('getReferencedExceptions', () => { + let savedObjectsClient: jest.Mocked; - beforeEach(() => { - savedObjectsClient = savedObjectsClientMock.create(); + beforeEach(() => { + savedObjectsClient = savedObjectsClientMock.create(); - (findExceptionList as jest.Mock).mockResolvedValue({ - data: [ - { + (findExceptionList as jest.Mock).mockResolvedValue({ + data: [ + { + ...getExceptionListSchemaMock(), + id: '123', + list_id: 'my-list', + namespace_type: 'single', + type: 'detection', + }, + ], + page: 1, + per_page: 20, + total: 1, + }); + jest.clearAllMocks(); + }); + + it('returns empty object if no rules to search', async () => { + const result = await getReferencedExceptionLists({ + rules: [], + savedObjectsClient, + }); + + expect(result).toEqual({}); + }); + + it('returns found referenced exception lists', async () => { + const result = await getReferencedExceptionLists({ + rules: [ + { + ...getImportRulesSchemaMock(), + exceptions_list: [ + { id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' }, + ], + }, + ], + savedObjectsClient, + }); + + expect(result).toEqual({ + 'my-list': { ...getExceptionListSchemaMock(), id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection', }, - ], - page: 1, - per_page: 20, - total: 1, + }); }); - jest.clearAllMocks(); - }); - it('returns empty object if no rules to search', async () => { - const result = await getReferencedExceptionLists({ - rules: [], - savedObjectsClient, + it('returns found referenced exception lists when first exceptions list is empty array and second list has a value', async () => { + const result = await getReferencedExceptionLists({ + rules: [ + { + ...getImportRulesSchemaMock(), + exceptions_list: [], + }, + { + ...getImportRulesSchemaMock(), + exceptions_list: [ + { id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' }, + ], + }, + ], + savedObjectsClient, + }); + + expect(result).toEqual({ + 'my-list': { + ...getExceptionListSchemaMock(), + id: '123', + list_id: 'my-list', + namespace_type: 'single', + type: 'detection', + }, + }); }); - expect(result).toEqual({}); - }); + it('returns found referenced exception lists when two rules reference same list', async () => { + const result = await getReferencedExceptionLists({ + rules: [ + { + ...getImportRulesSchemaMock(), + exceptions_list: [ + { id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' }, + ], + }, + { + ...getImportRulesSchemaMock(), + exceptions_list: [ + { id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' }, + ], + }, + ], + savedObjectsClient, + }); + + expect(result).toEqual({ + 'my-list': { + ...getExceptionListSchemaMock(), + id: '123', + list_id: 'my-list', + namespace_type: 'single', + type: 'detection', + }, + }); + }); + + it('returns two found referenced exception lists when two rules reference different lists', async () => { + (findExceptionList as jest.Mock).mockResolvedValue({ + data: [ + { + ...getExceptionListSchemaMock(), + id: '123', + list_id: 'my-list', + namespace_type: 'single', + type: 'detection', + }, + { + ...getExceptionListSchemaMock(), + id: '456', + list_id: 'other-list', + namespace_type: 'single', + type: 'detection', + }, + ], + page: 1, + per_page: 20, + total: 2, + }); + + const result = await getReferencedExceptionLists({ + rules: [ + { + ...getImportRulesSchemaMock(), + exceptions_list: [ + { id: '456', list_id: 'other-list', namespace_type: 'single', type: 'detection' }, + ], + }, + { + ...getImportRulesSchemaMock(), + exceptions_list: [ + { id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' }, + ], + }, + ], + savedObjectsClient, + }); + + // the problem with these tests is that they are entirely dependent on + // the result from the saved objects client matching what we put here + // so it essentially just bypasses the code that is not interacting with + // the saved objects client. + expect(result).toEqual({ + 'my-list': { + ...getExceptionListSchemaMock(), + id: '123', + list_id: 'my-list', + namespace_type: 'single', + type: 'detection', + }, + 'other-list': { + ...getExceptionListSchemaMock(), + id: '456', + list_id: 'other-list', + namespace_type: 'single', + type: 'detection', + }, + }); + }); - it('returns found referenced exception lists', async () => { - const result = await getReferencedExceptionLists({ - rules: [ + it('returns empty object if no referenced exception lists found', async () => { + const result = await getReferencedExceptionLists({ + rules: [], + savedObjectsClient, + }); + + expect(result).toEqual({}); + }); + }); + describe('parseReferencdedExceptionsLists', () => { + it('should return parsed lists when exception lists are not empty', () => { + const res = parseReferencedExceptionsLists([ { ...getImportRulesSchemaMock(), exceptions_list: [ { id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' }, ], }, - ], - savedObjectsClient, + ]); + expect(res).toEqual([[], [{ listId: 'my-list', namespaceType: 'single' }]]); }); - - expect(result).toEqual({ - 'my-list': { - ...getExceptionListSchemaMock(), - id: '123', - list_id: 'my-list', - namespace_type: 'single', - type: 'detection', - }, + it('should return parsed lists when one empty exception list and one non-empty list', () => { + const res = parseReferencedExceptionsLists([ + { + ...getImportRulesSchemaMock(), + exceptions_list: [], + }, + { + ...getImportRulesSchemaMock(), + exceptions_list: [ + { id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' }, + ], + }, + ]); + expect(res).toEqual([[], [{ listId: 'my-list', namespaceType: 'single' }]]); }); - }); - it('returns empty object if no referenced exception lists found', async () => { - const result = await getReferencedExceptionLists({ - rules: [], - savedObjectsClient, + it('should return parsed lists when two non-empty exception lists reference same list', () => { + const res = parseReferencedExceptionsLists([ + { + ...getImportRulesSchemaMock(), + exceptions_list: [ + { id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' }, + ], + }, + { + ...getImportRulesSchemaMock(), + exceptions_list: [ + { id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' }, + ], + }, + ]); + expect(res).toEqual([ + [], + [ + { listId: 'my-list', namespaceType: 'single' }, + { listId: 'my-list', namespaceType: 'single' }, + ], + ]); }); - expect(result).toEqual({}); + it('should return parsed lists when two non-empty exception lists reference differet lists', () => { + const res = parseReferencedExceptionsLists([ + { + ...getImportRulesSchemaMock(), + exceptions_list: [ + { id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' }, + ], + }, + { + ...getImportRulesSchemaMock(), + exceptions_list: [ + { id: '456', list_id: 'other-list', namespace_type: 'single', type: 'detection' }, + ], + }, + ]); + expect(res).toEqual([ + [], + [ + { listId: 'my-list', namespaceType: 'single' }, + { listId: 'other-list', namespaceType: 'single' }, + ], + ]); + }); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/gather_referenced_exceptions.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/gather_referenced_exceptions.ts index cbf6050b10b1..6653e8ad54a6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/gather_referenced_exceptions.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/gather_referenced_exceptions.ts @@ -11,29 +11,28 @@ import { getAllListTypes } from '@kbn/lists-plugin/server/services/exception_lis import type { RuleToImport } from '../../../../../../common/detection_engine/rule_management'; /** - * Helper that takes rules, goes through their referenced exception lists and - * searches for them, returning an object with all those found, using list_id as keys - * @param rules {array} - * @param savedObjectsClient {object} - * @returns {Promise} an object with all referenced lists found, using list_id as keys + * splitting out the parsing of the lists from the fetching + * for easier and more compartmentalized testing + * @param rules Array + * @returns [ExceptionListQueryInfo[], ExceptionListQueryInfo[]] */ -export const getReferencedExceptionLists = async ({ - rules, - savedObjectsClient, -}: { - rules: Array; - savedObjectsClient: SavedObjectsClientContract; -}): Promise> => { - const [lists] = rules.reduce((acc, rule) => { - if (!(rule instanceof Error) && rule.exceptions_list != null) { - return [...acc, rule.exceptions_list]; +export const parseReferencedExceptionsLists = ( + rules: Array +): [ExceptionListQueryInfo[], ExceptionListQueryInfo[]] => { + const lists = rules.reduce((acc, rule) => { + if ( + !(rule instanceof Error) && + rule.exceptions_list != null && + rule.exceptions_list.length > 0 + ) { + return [...acc, ...rule.exceptions_list]; } else { return acc; } }, []); - if (lists == null) { - return {}; + if (lists == null || lists.length === 0) { + return [[], []]; } const [agnosticLists, nonAgnosticLists] = lists.reduce< @@ -49,6 +48,23 @@ export const getReferencedExceptionLists = async ({ }, [[], []] ); + return [agnosticLists, nonAgnosticLists]; +}; +/** + * Helper that takes rules, goes through their referenced exception lists and + * searches for them, returning an object with all those found, using list_id as keys + * @param rules {array} + * @param savedObjectsClient {object} + * @returns {Promise} an object with all referenced lists found, using list_id as keys + */ +export const getReferencedExceptionLists = async ({ + rules, + savedObjectsClient, +}: { + rules: Array; + savedObjectsClient: SavedObjectsClientContract; +}): Promise> => { + const [agnosticLists, nonAgnosticLists] = parseReferencedExceptionsLists(rules); return getAllListTypes(agnosticLists, nonAgnosticLists, savedObjectsClient); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_scoped_cluster_client.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_scoped_cluster_client.test.ts index 551562c6b6e9..88410e8db204 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_scoped_cluster_client.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_scoped_cluster_client.test.ts @@ -15,7 +15,7 @@ const esQuery = { describe('wrapScopedClusterClient', () => { beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts index 371e49d26db9..4376b8785e0e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts @@ -25,7 +25,7 @@ const createSearchSourceClientMock = () => { describe('wrapSearchSourceClient', () => { beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.ts index 7a148e80282d..515caf5dcd5e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.ts @@ -92,7 +92,7 @@ export const thresholdExecutor = async ({ : await getThresholdSignalHistory({ from: tuple.from.toISOString(), to: tuple.to.toISOString(), - ruleId: ruleParams.ruleId, + frameworkRuleId: completeRule.alertId, bucketByFields: ruleParams.threshold.field, ruleDataReader, }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/__snapshots__/get_threshold_signal_history.test.ts.snap b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/__snapshots__/get_threshold_signal_history.test.ts.snap index 005ce07afcac..bb9e29f1f5b5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/__snapshots__/get_threshold_signal_history.test.ts.snap +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/__snapshots__/get_threshold_signal_history.test.ts.snap @@ -17,7 +17,7 @@ Object { }, Object { "term": Object { - "signal.rule.rule_id": "threshold-rule", + "kibana.alert.rule.uuid": "threshold-rule", }, }, Object { @@ -91,7 +91,7 @@ Object { }, Object { "term": Object { - "signal.rule.rule_id": "threshold-rule", + "kibana.alert.rule.uuid": "threshold-rule", }, }, Object { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/get_threshold_signal_history.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/get_threshold_signal_history.test.ts index d9db2972a89d..0e896ad49cd8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/get_threshold_signal_history.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/get_threshold_signal_history.test.ts @@ -12,10 +12,10 @@ describe('buildPreviousThresholdAlertRequest', () => { const bucketByFields: string[] = []; const to = 'now'; const from = 'now-6m'; - const ruleId = 'threshold-rule'; + const frameworkRuleId = 'threshold-rule'; expect( - buildPreviousThresholdAlertRequest({ from, to, ruleId, bucketByFields }) + buildPreviousThresholdAlertRequest({ from, to, frameworkRuleId, bucketByFields }) ).toMatchSnapshot(); }); @@ -23,10 +23,10 @@ describe('buildPreviousThresholdAlertRequest', () => { const bucketByFields: string[] = ['host.name', 'user.name']; const to = 'now'; const from = 'now-6m'; - const ruleId = 'threshold-rule'; + const frameworkRuleId = 'threshold-rule'; expect( - buildPreviousThresholdAlertRequest({ from, to, ruleId, bucketByFields }) + buildPreviousThresholdAlertRequest({ from, to, frameworkRuleId, bucketByFields }) ).toMatchSnapshot(); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/get_threshold_signal_history.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/get_threshold_signal_history.ts index 997e6c213f3b..4826cd574a90 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/get_threshold_signal_history.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/get_threshold_signal_history.ts @@ -7,6 +7,7 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { IRuleDataReader } from '@kbn/rule-registry-plugin/server'; +import { ALERT_RULE_UUID } from '@kbn/rule-data-utils'; import type { ThresholdSignalHistory } from '../types'; import { buildThresholdSignalHistory } from './build_signal_history'; import { createErrorsFromShard } from '../utils'; @@ -14,7 +15,7 @@ import { createErrorsFromShard } from '../utils'; interface GetThresholdSignalHistoryParams { from: string; to: string; - ruleId: string; + frameworkRuleId: string; bucketByFields: string[]; ruleDataReader: IRuleDataReader; } @@ -22,7 +23,7 @@ interface GetThresholdSignalHistoryParams { export const getThresholdSignalHistory = async ({ from, to, - ruleId, + frameworkRuleId, bucketByFields, ruleDataReader, }: GetThresholdSignalHistoryParams): Promise<{ @@ -32,7 +33,7 @@ export const getThresholdSignalHistory = async ({ const request = buildPreviousThresholdAlertRequest({ from, to, - ruleId, + frameworkRuleId, bucketByFields, }); @@ -48,12 +49,12 @@ export const getThresholdSignalHistory = async ({ export const buildPreviousThresholdAlertRequest = ({ from, to, - ruleId, + frameworkRuleId, bucketByFields, }: { from: string; to: string; - ruleId: string; + frameworkRuleId: string; bucketByFields: string[]; }): estypes.SearchRequest => { return { @@ -80,7 +81,7 @@ export const buildPreviousThresholdAlertRequest = ({ }, { term: { - 'signal.rule.rule_id': ruleId, + [ALERT_RULE_UUID]: frameworkRuleId, }, }, // We might find a signal that was generated on the interval for old data... make sure to exclude those. diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index df584c8d64e9..dd4993807f7c 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -343,7 +343,8 @@ export class Plugin implements ISecuritySolutionPlugin { const securitySolutionSearchStrategy = securitySolutionSearchStrategyProvider( depsStart.data, endpointContext, - depsStart.spaces?.spacesService?.getSpaceId + depsStart.spaces?.spacesService?.getSpaceId, + ruleDataClient ); plugins.data.search.registerSearchStrategy( diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.test.ts index 4208ea44937b..7abc747eb0c8 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.test.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.test.ts @@ -8,6 +8,7 @@ import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../../../common/constants'; import type { HostsRequestOptions } from '../../../../../../common/search_strategy/security_solution'; +import { RiskScoreEntity } from '../../../../../../common/search_strategy/security_solution'; import * as buildQuery from './query.all_hosts.dsl'; import * as buildRiskQuery from '../../risk_score/all/query.risk_score.dsl'; import { allHosts } from '.'; @@ -128,6 +129,7 @@ describe('allHosts search strategy', () => { expect(buildHostsRiskQuery).toHaveBeenCalledWith({ defaultIndex: ['ml_host_risk_score_latest_test-space'], filterQuery: { terms: { 'host.name': [hostName] } }, + riskScoreEntity: RiskScoreEntity.host, }); }); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.ts index fb4a4aa27353..86a3cfae8b4f 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.ts @@ -19,7 +19,11 @@ import type { } from '../../../../../../common/search_strategy/security_solution/hosts'; import type { HostRiskScore } from '../../../../../../common/search_strategy'; -import { getHostRiskIndex, buildHostNamesFilter } from '../../../../../../common/search_strategy'; +import { + RiskScoreEntity, + getHostRiskIndex, + buildHostNamesFilter, +} from '../../../../../../common/search_strategy'; import { inspectStringifyObject } from '../../../../../utils/build_query'; import type { SecuritySolutionFactory } from '../../types'; @@ -116,6 +120,7 @@ async function getHostRiskData( buildRiskScoreQuery({ defaultIndex: [getHostRiskIndex(spaceId)], filterQuery: buildHostNamesFilter(hostNames), + riskScoreEntity: RiskScoreEntity.host, }) ); return hostRiskResponse; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/all/index.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/all/index.test.ts new file mode 100644 index 000000000000..58b2a55bc159 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/all/index.test.ts @@ -0,0 +1,136 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { IScopedClusterClient } from '@kbn/core-elasticsearch-server'; +import type { KibanaRequest } from '@kbn/core-http-server'; +import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import { riskScore } from '.'; +import type { IEsSearchResponse } from '@kbn/data-plugin/public'; +import { allowedExperimentalValues } from '../../../../../../common/experimental_features'; +import type { + HostRiskScore, + RiskScoreRequestOptions, +} from '../../../../../../common/search_strategy'; +import { RiskScoreEntity, RiskSeverity } from '../../../../../../common/search_strategy'; +import type { EndpointAppContextService } from '../../../../../endpoint/endpoint_app_context_services'; +import type { EndpointAppContext } from '../../../../../endpoint/types'; +import * as buildQuery from './query.risk_score.dsl'; +import { get } from 'lodash/fp'; +import { ruleRegistryMocks } from '@kbn/rule-registry-plugin/server/mocks'; +import type { IRuleDataClient } from '@kbn/rule-registry-plugin/server'; + +export const mockSearchStrategyResponse: IEsSearchResponse = { + rawResponse: { + took: 1, + timed_out: false, + _shards: { + total: 2, + successful: 2, + skipped: 1, + failed: 0, + }, + hits: { + max_score: null, + hits: [ + { + _id: '4', + _index: 'index', + _source: { + '@timestamp': '1234567899', + host: { + name: 'testUsermame', + risk: { + rule_risks: [], + calculated_level: RiskSeverity.high, + calculated_score_norm: 75, + multipliers: [], + }, + }, + }, + }, + ], + }, + }, + isPartial: false, + isRunning: false, + total: 2, + loaded: 2, +}; + +const searchMock = jest.fn(); + +const mockDeps = { + esClient: {} as IScopedClusterClient, + ruleDataClient: { + ...(ruleRegistryMocks.createRuleDataClient('.alerts-security.alerts') as IRuleDataClient), + getReader: jest.fn((_options?: { namespace?: string }) => ({ + search: searchMock, + getDynamicIndexPattern: jest.fn(), + })), + }, + savedObjectsClient: {} as SavedObjectsClientContract, + endpointContext: { + logFactory: { + get: jest.fn().mockReturnValue({ + warn: jest.fn(), + }), + }, + config: jest.fn().mockResolvedValue({}), + experimentalFeatures: { + ...allowedExperimentalValues, + }, + service: {} as EndpointAppContextService, + } as EndpointAppContext, + request: {} as KibanaRequest, +}; + +export const mockOptions: RiskScoreRequestOptions = { + defaultIndex: ['logs-*'], + riskScoreEntity: RiskScoreEntity.host, + includeAlertsCount: true, +}; + +describe('buildRiskScoreQuery search strategy', () => { + const buildKpiRiskScoreQuery = jest.spyOn(buildQuery, 'buildRiskScoreQuery'); + + describe('buildDsl', () => { + test('should build dsl query', () => { + riskScore.buildDsl(mockOptions); + expect(buildKpiRiskScoreQuery).toHaveBeenCalledWith(mockOptions); + }); + }); + + test('should not enhance data when includeAlertsCount is false', async () => { + const result = await riskScore.parse( + { ...mockOptions, includeAlertsCount: false }, + mockSearchStrategyResponse, + mockDeps + ); + + expect(get('data[0].alertsCount', result)).toBeUndefined(); + }); + + test('should enhance data with alerts count', async () => { + const alertsCunt = 9999; + searchMock.mockReturnValue({ + aggregations: { + alertsByEntity: { + buckets: [ + { + key: 'testUsermame', + doc_count: alertsCunt, + }, + ], + }, + }, + }); + + const result = await riskScore.parse(mockOptions, mockSearchStrategyResponse, mockDeps); + + expect(get('data[0].alertsCount', result)).toBe(alertsCunt); + }); +}); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/all/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/all/index.ts index 0d2c01b735a5..5e46ac2b4f44 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/all/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/all/index.ts @@ -5,12 +5,19 @@ * 2.0. */ -import type { IEsSearchResponse } from '@kbn/data-plugin/common'; +import type { IEsSearchResponse, SearchRequest } from '@kbn/data-plugin/common'; +import { get, getOr } from 'lodash/fp'; + +import type { IRuleDataClient } from '@kbn/rule-registry-plugin/server'; import type { SecuritySolutionFactory } from '../../types'; import type { RiskScoreRequestOptions, RiskQueries, + BucketItem, + HostRiskScore, + UserRiskScore, } from '../../../../../../common/search_strategy'; +import { RiskScoreEntity } from '../../../../../../common/search_strategy'; import { inspectStringifyObject } from '../../../../../utils/build_query'; import { buildRiskScoreQuery } from './query.risk_score.dsl'; import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../../../common/constants'; @@ -26,7 +33,14 @@ export const riskScore: SecuritySolutionFactory< return buildRiskScoreQuery(options); }, - parse: async (options: RiskScoreRequestOptions, response: IEsSearchResponse) => { + parse: async ( + options: RiskScoreRequestOptions, + response: IEsSearchResponse, + deps?: { + spaceId?: string; + ruleDataClient?: IRuleDataClient | null; + } + ) => { const inspect = { dsl: [inspectStringifyObject(buildRiskScoreQuery(options))], }; @@ -34,11 +48,65 @@ export const riskScore: SecuritySolutionFactory< const totalCount = getTotalCount(response.rawResponse.hits.total); const hits = response?.rawResponse?.hits?.hits; const data = hits?.map((hit) => hit._source) ?? []; + const nameField = options.riskScoreEntity === RiskScoreEntity.host ? 'host.name' : 'user.name'; + const names = data.map((risk) => get(nameField, risk) ?? ''); + + const enhancedData = + deps && options.includeAlertsCount + ? await enhanceData(data, names, nameField, deps.ruleDataClient, deps.spaceId) + : data; + return { ...response, inspect, totalCount, - data, + data: enhancedData, }; }, }; + +async function enhanceData( + data: Array, + names: string[], + nameField: string, + ruleDataClient?: IRuleDataClient | null, + spaceId?: string +): Promise> { + const ruleDataReader = ruleDataClient?.getReader({ namespace: spaceId }); + const query = getAlertsQueryForEntity(names, nameField); + + const response = await ruleDataReader?.search(query); + const buckets: BucketItem[] = getOr([], 'aggregations.alertsByEntity.buckets', response); + + const alertsCountByEntityName: Record = buckets.reduce( + (acc, { key, doc_count: count }) => ({ + ...acc, + [key]: count, + }), + {} + ); + + return data.map((risk) => ({ + ...risk, + alertsCount: alertsCountByEntityName[get(nameField, risk)] ?? 0, + })); +} + +const getAlertsQueryForEntity = (names: string[], nameField: string): SearchRequest => ({ + size: 0, + query: { + bool: { + filter: [ + { term: { 'kibana.alert.workflow_status': 'open' } }, + { terms: { [nameField]: names } }, + ], + }, + }, + aggs: { + alertsByEntity: { + terms: { + field: nameField, + }, + }, + }, +}); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/types.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/types.ts index 51529bc21d80..f222e2130ee2 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/types.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/types.ts @@ -11,6 +11,7 @@ import type { SavedObjectsClientContract, } from '@kbn/core/server'; import type { IEsSearchResponse, ISearchRequestParams } from '@kbn/data-plugin/common'; +import type { IRuleDataClient } from '@kbn/rule-registry-plugin/server'; import type { FactoryQueryTypes, StrategyRequestType, @@ -29,6 +30,7 @@ export interface SecuritySolutionFactory { endpointContext: EndpointAppContext; request: KibanaRequest; spaceId?: string; + ruleDataClient?: IRuleDataClient | null; } ) => Promise>; } diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/all/index.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/all/index.test.ts index 1f17bbfc870f..18bc75edb530 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/all/index.test.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/all/index.test.ts @@ -14,6 +14,7 @@ import type { UsersRequestOptions } from '../../../../../../common/search_strate import * as buildRiskQuery from '../../risk_score/all/query.risk_score.dsl'; import { get } from 'lodash/fp'; +import { RiskScoreEntity } from '../../../../../../common/search_strategy'; class IndexNotFoundException extends Error { meta: { body: { error: { type: string } } }; @@ -115,6 +116,7 @@ describe('allHosts search strategy', () => { expect(buildHostsRiskQuery).toHaveBeenCalledWith({ defaultIndex: ['ml_user_risk_score_latest_test-space'], filterQuery: { terms: { 'user.name': userName } }, + riskScoreEntity: RiskScoreEntity.user, }); }); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/all/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/all/index.ts index d5c13e0d2b52..c936ad85f79e 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/all/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/all/index.ts @@ -23,7 +23,11 @@ import type { import type { AllUsersAggEsItem } from '../../../../../../common/search_strategy/security_solution/users/common'; import { buildRiskScoreQuery } from '../../risk_score/all/query.risk_score.dsl'; import type { RiskSeverity, UserRiskScore } from '../../../../../../common/search_strategy'; -import { buildUserNamesFilter, getUserRiskIndex } from '../../../../../../common/search_strategy'; +import { + RiskScoreEntity, + buildUserNamesFilter, + getUserRiskIndex, +} from '../../../../../../common/search_strategy'; export const allUsers: SecuritySolutionFactory = { buildDsl: (options: UsersRequestOptions) => { @@ -123,6 +127,7 @@ async function getUserRiskData( buildRiskScoreQuery({ defaultIndex: [getUserRiskIndex(spaceId)], filterQuery: buildUserNamesFilter(userNames), + riskScoreEntity: RiskScoreEntity.user, }) ); return userRiskResponse; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/index.ts index faee7d6b6d8f..1acb6687b8ac 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/index.ts @@ -10,6 +10,7 @@ import type { ISearchStrategy, PluginStart } from '@kbn/data-plugin/server'; import { shimHitsTotal } from '@kbn/data-plugin/server'; import { ENHANCED_ES_SEARCH_STRATEGY } from '@kbn/data-plugin/common'; import type { KibanaRequest } from '@kbn/core/server'; +import type { IRuleDataClient } from '@kbn/rule-registry-plugin/server'; import type { FactoryQueryTypes, StrategyResponseType, @@ -33,7 +34,8 @@ function assertValidRequestType( export const securitySolutionSearchStrategyProvider = ( data: PluginStart, endpointContext: EndpointAppContext, - getSpaceId?: (request: KibanaRequest) => string + getSpaceId?: (request: KibanaRequest) => string, + ruleDataClient?: IRuleDataClient | null ): ISearchStrategy, StrategyResponseType> => { const es = data.search.getSearchStrategy(ENHANCED_ES_SEARCH_STRATEGY); @@ -60,6 +62,7 @@ export const securitySolutionSearchStrategyProvider = &2 echo "Failed to set the reject of queries on Mysql node, exiting."\n\u001b[38;5;130m 98 \u001b[m exit $exit_code\n\u001b[38;5;130m 99 \u001b[melse\n\u001b[38;5;130m 100 \u001b[m echo "Successfully stopped accepting queries."\n\u001b[38;5;130m 101 \u001b[m if [[ $KILL_IMMEDIATELY == 1 ]]; then\n\u001b[38;5;130m 102 \u001b[m\u001b[8Cexit\n\u001b[38;5;130m 103 \u001b[m fi\n\u001b[38;5;130m 104 \u001b[mfi\n\u001b[38;5;130m 105 \n 106 \u001b[mif [[ $GRACE_PERIOD == -1 ]]; then\n\u001b[38;5;130m 107 \u001b[m set_number_grace_seconds\n\u001b[38;5;130m 108 \u001b[mfi\n\u001b[38;5;130m 109 \n 110 \u001b[mwait_for_connections\n\u001b[38;5;130m 111 \u001b[mif [[ $DB_CONNECTIONS_NUMBER != 0 ]]; then\n\u001b[38;5;130m 112 \u001b[m get_number_db_connections\n\u001b[38;5;130m 113 \u001b[m >&2 echo "ERROR: There are still $DB_CONNECTIONS_NUMBER opened DB connections."\n\u001b[38;5;130m 114 \u001b[m exit 3\n\u001b[38;5;130m 115 \u001b[mfi\b\b\u001b[?25h\u001b[?25l\nType :qa! and press to abandon all changes and exit Vim\u0007\u001b[58;9H\u001b[?25h\u0007\u001b[?25l\u001b[59;1H\u001b[K\u001b[59;1H:\u001b[?2004h\u001b[?25hqa!\r\u001b[?25l\u001b[?2004l\u001b[59;1H\u001b[K\u001b[59;1H\u001b[?2004l\u001b[?1l\u001b>\u001b[?25h\u001b[?1049l\u001b[23;0;0t,\u001bkroot@staging-host:~\u001b\\\n', }, tty: { diff --git a/x-pack/plugins/session_view/common/types/process_tree/index.ts b/x-pack/plugins/session_view/common/types/process_tree/index.ts index 68f8924abd70..b228502f61a5 100644 --- a/x-pack/plugins/session_view/common/types/process_tree/index.ts +++ b/x-pack/plugins/session_view/common/types/process_tree/index.ts @@ -72,6 +72,7 @@ export interface IOLine { export interface ProcessStartMarker { event: ProcessEvent; line: number; + maxBytesExceeded?: boolean; } export interface IOFields { diff --git a/x-pack/plugins/session_view/public/components/session_view/index.tsx b/x-pack/plugins/session_view/public/components/session_view/index.tsx index 32cc5dffdea5..18296992de65 100644 --- a/x-pack/plugins/session_view/public/components/session_view/index.tsx +++ b/x-pack/plugins/session_view/public/components/session_view/index.tsx @@ -53,6 +53,7 @@ export const SessionView = ({ jumpToCursor, investigatedAlertId, loadAlertDetails, + canAccessEndpointManagement, }: SessionViewDeps) => { // don't engage jumpTo if jumping to session leader. if (jumpToEntityId === sessionEntityId) { @@ -422,6 +423,7 @@ export const SessionView = ({ isFullscreen={isFullScreen} onJumpToEvent={onJumpToEvent} autoSeekToEntityId={currentJumpToOutputEntityId} + canAccessEndpointManagement={canAccessEndpointManagement} />
); diff --git a/x-pack/plugins/session_view/public/components/tty_player/ansi_helpers.ts b/x-pack/plugins/session_view/public/components/tty_player/ansi_helpers.ts new file mode 100644 index 000000000000..e5ad54f7e526 --- /dev/null +++ b/x-pack/plugins/session_view/public/components/tty_player/ansi_helpers.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Teletype } from '../../../common/types/process_tree'; +import { + PROCESS_DATA_LIMIT_EXCEEDED_START, + PROCESS_DATA_LIMIT_EXCEEDED_END, + VIEW_POLICIES, +} from './translations'; + +export const renderTruncatedMsg = (tty?: Teletype, policiesUrl?: string, processName?: string) => { + if (tty?.columns) { + const lineBreak = '-'.repeat(tty.columns); + const message = ` ⚠ ${PROCESS_DATA_LIMIT_EXCEEDED_START} \x1b[1m${processName}.\x1b[22m ${PROCESS_DATA_LIMIT_EXCEEDED_END}`; + const link = policiesUrl + ? `\x1b[${Math.min( + message.length + 2, + tty.columns - VIEW_POLICIES.length - 4 + )}G\x1b[1m\x1b]8;;${policiesUrl}\x1b\\[ ${VIEW_POLICIES} ]\x1b]8;;\x1b\\\x1b[22m` + : ''; + + return `\n\x1b[33m${lineBreak}\n${message}${link}\n${lineBreak}\x1b[0m\n\n`; + } +}; diff --git a/x-pack/plugins/session_view/public/components/tty_player/hooks.test.tsx b/x-pack/plugins/session_view/public/components/tty_player/hooks.test.tsx index 9f7201492520..b40605ad2cdb 100644 --- a/x-pack/plugins/session_view/public/components/tty_player/hooks.test.tsx +++ b/x-pack/plugins/session_view/public/components/tty_player/hooks.test.tsx @@ -30,7 +30,7 @@ describe('TTYPlayer/hooks', () => { })), }); - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); describe('useIOLines', () => { diff --git a/x-pack/plugins/session_view/public/components/tty_player/hooks.ts b/x-pack/plugins/session_view/public/components/tty_player/hooks.ts index b6891f1dd1d4..30b40beaa094 100644 --- a/x-pack/plugins/session_view/public/components/tty_player/hooks.ts +++ b/x-pack/plugins/session_view/public/components/tty_player/hooks.ts @@ -12,6 +12,7 @@ import { CoreStart } from '@kbn/core/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { SearchAddon } from './xterm_search'; import { useEuiTheme } from '../../hooks'; +import { renderTruncatedMsg } from './ansi_helpers'; import { IOLine, @@ -103,6 +104,15 @@ export const useIOLines = (pages: ProcessEventsPage[] | undefined) => { newMarkers.push(processLineInfo); } + if (process.io.max_bytes_per_process_exceeded) { + const marker = newMarkers.find( + (item) => item.event.process?.entity_id === process.entity_id + ); + if (marker) { + marker.maxBytesExceeded = true; + } + } + const splitLines = process.io.text.split(TTY_LINE_SPLITTER_REGEX); const combinedLines = [splitLines[0]]; @@ -158,6 +168,7 @@ export interface XtermPlayerDeps { hasNextPage?: boolean; fetchNextPage?: () => void; isFetching?: boolean; + policiesUrl?: string; } export const useXtermPlayer = ({ @@ -169,17 +180,20 @@ export const useXtermPlayer = ({ hasNextPage, fetchNextPage, isFetching, + policiesUrl, }: XtermPlayerDeps) => { const { euiTheme } = useEuiTheme(); const { font, colors } = euiTheme; const [currentLine, setCurrentLine] = useState(0); const [playSpeed] = useState(DEFAULT_TTY_PLAYSPEED_MS); // potentially configurable const tty = lines?.[currentLine]?.event.process?.tty; - + const processName = lines?.[currentLine]?.event.process?.name; const [terminal, searchAddon] = useMemo(() => { const term = new Terminal({ theme: { - selection: colors.warning, + selectionBackground: colors.warning, + selectionForeground: colors.ink, + yellow: colors.warning, }, fontFamily: font.familyCode, fontSize: DEFAULT_TTY_FONT_SIZE, @@ -187,6 +201,8 @@ export const useXtermPlayer = ({ convertEol: true, rows: DEFAULT_TTY_ROWS, cols: DEFAULT_TTY_COLS, + allowProposedApi: true, + allowTransparency: true, }); const searchInstance = new SearchAddon(); @@ -203,7 +219,7 @@ export const useXtermPlayer = ({ // even though we set scrollback: 0 above, xterm steals the wheel events and prevents the outer container from scrolling // this handler fixes that const onScroll = (event: WheelEvent) => { - if ((event?.target as HTMLDivElement)?.className === 'xterm-cursor-layer') { + if ((event?.target as HTMLDivElement)?.offsetParent?.classList.contains('xterm-screen')) { event.stopImmediatePropagation(); } }; @@ -212,6 +228,7 @@ export const useXtermPlayer = ({ return () => { window.removeEventListener('wheel', onScroll, true); + terminal.dispose(); }; }, [terminal, ref]); @@ -241,17 +258,30 @@ export const useXtermPlayer = ({ if (line?.value !== undefined) { terminal.write(line.value); } + + const nextLine = lines[lineNumber + index + 1]; + const maxBytesExceeded = line.event.process?.io?.max_bytes_per_process_exceeded; + + // if next line is start of next event + // and process has exceeded max bytes + // render msg + if (!clear && (!nextLine || nextLine.event !== line.event) && maxBytesExceeded) { + const msg = renderTruncatedMsg(tty, policiesUrl, processName); + if (msg) { + terminal.write(msg); + } + } }); }, - [lines, terminal] + [lines, policiesUrl, processName, terminal, tty] ); useEffect(() => { - const fontChanged = terminal.getOption('fontSize') !== fontSize; + const fontChanged = terminal.options.fontSize !== fontSize; const ttyChanged = tty && (terminal.rows !== tty?.rows || terminal.cols !== tty?.columns); if (fontChanged) { - terminal.setOption('fontSize', fontSize); + terminal.options.fontSize = fontSize; } if (tty?.rows && tty?.columns && ttyChanged) { diff --git a/x-pack/plugins/session_view/public/components/tty_player/index.test.tsx b/x-pack/plugins/session_view/public/components/tty_player/index.test.tsx index f3332ae5bb7f..ba56931b4a99 100644 --- a/x-pack/plugins/session_view/public/components/tty_player/index.test.tsx +++ b/x-pack/plugins/session_view/public/components/tty_player/index.test.tsx @@ -6,10 +6,11 @@ */ import React from 'react'; -import { waitFor } from '@testing-library/react'; +import { waitFor, act } from '@testing-library/react'; import { sessionViewIOEventsMock } from '../../../common/mocks/responses/session_view_io_events.mock'; import { AppContextTestRender, createAppRootMockRenderer } from '../../test'; import { TTYPlayerDeps, TTYPlayer } from '.'; +import userEvent from '@testing-library/user-event'; describe('TTYPlayer component', () => { beforeAll(() => { @@ -81,5 +82,38 @@ describe('TTYPlayer component', () => { await waitForApiCall(); }); + + it('renders a message warning when max_bytes exceeded', async () => { + renderResult = mockedContext.render(); + + await waitForApiCall(); + await new Promise((r) => setTimeout(r, 10)); + + const seekToEndBtn = renderResult.getByTestId('sessionView:TTYPlayerControlsEnd'); + + act(() => { + userEvent.click(seekToEndBtn); + }); + + waitFor(() => expect(renderResult.queryAllByText('Data limit reached')).toHaveLength(1)); + expect(renderResult.queryByText('[ VIEW POLICIES ]')).toBeFalsy(); + }); + + it('renders a message warning when max_bytes exceeded with link to policies page', async () => { + renderResult = mockedContext.render( + + ); + + await waitForApiCall(); + await new Promise((r) => setTimeout(r, 10)); + + const seekToEndBtn = renderResult.getByTestId('sessionView:TTYPlayerControlsEnd'); + + act(() => { + userEvent.click(seekToEndBtn); + }); + + waitFor(() => expect(renderResult.queryAllByText('[ VIEW POLICIES ]')).toHaveLength(1)); + }); }); }); diff --git a/x-pack/plugins/session_view/public/components/tty_player/index.tsx b/x-pack/plugins/session_view/public/components/tty_player/index.tsx index c77efc9d8c15..434805ac689d 100644 --- a/x-pack/plugins/session_view/public/components/tty_player/index.tsx +++ b/x-pack/plugins/session_view/public/components/tty_player/index.tsx @@ -13,6 +13,8 @@ import { EuiButton, EuiBetaBadge, } from '@elastic/eui'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { CoreStart } from '@kbn/core/public'; import useResizeObserver from 'use-resize-observer'; import { throttle } from 'lodash'; import { ProcessEvent } from '../../../common/types/process_tree'; @@ -23,6 +25,8 @@ import { DEFAULT_TTY_ROWS, DEFAULT_TTY_COLS, DEFAULT_TTY_FONT_SIZE, + POLICIES_PAGE_PATH, + SECURITY_APP_ID, } from '../../../common/constants'; import { useFetchIOEvents, useIOLines, useXtermPlayer } from './hooks'; import { TTYPlayerControls } from '../tty_player_controls'; @@ -35,6 +39,7 @@ export interface TTYPlayerDeps { isFullscreen: boolean; onJumpToEvent(event: ProcessEvent): void; autoSeekToEntityId?: string; + canAccessEndpointManagement?: boolean; } export const TTYPlayer = ({ @@ -44,6 +49,7 @@ export const TTYPlayer = ({ isFullscreen, onJumpToEvent, autoSeekToEntityId, + canAccessEndpointManagement, }: TTYPlayerDeps) => { const ref = useRef(null); const { ref: scrollRef, height: containerHeight = 1 } = useResizeObserver({}); @@ -53,7 +59,16 @@ export const TTYPlayer = ({ const { lines, processStartMarkers } = useIOLines(data?.pages); const [fontSize, setFontSize] = useState(DEFAULT_TTY_FONT_SIZE); const [isPlaying, setIsPlaying] = useState(false); + const [searchQuery, setSearchQuery] = useState(''); const [currentAutoSeekEntityId, setCurrentAutoSeekEntityId] = useState(''); + const { getUrlForApp } = useKibana().services.application; + const policiesUrl = useMemo( + () => + canAccessEndpointManagement + ? getUrlForApp(SECURITY_APP_ID, { path: POLICIES_PAGE_PATH }) + : '', + [canAccessEndpointManagement, getUrlForApp] + ); const { search, currentLine, seekToLine } = useXtermPlayer({ ref, @@ -64,6 +79,7 @@ export const TTYPlayer = ({ hasNextPage, fetchNextPage, isFetching, + policiesUrl, }); const currentProcessEvent = lines[Math.min(lines.length - 1, currentLine)]?.event; @@ -113,11 +129,18 @@ export const TTYPlayer = ({ const styles = useStyles(tty, show); + const clearSearch = useCallback(() => { + if (searchQuery) { + setSearchQuery(''); + } + }, [searchQuery]); + const onSeekLine = useMemo(() => { return throttle((line: number) => { + clearSearch(); seekToLine(line); }, 100); - }, [seekToLine]); + }, [clearSearch, seekToLine]); const onTogglePlayback = useCallback(() => { // if at the end, seek to beginning @@ -127,6 +150,12 @@ export const TTYPlayer = ({ setIsPlaying(!isPlaying); }, [currentLine, isPlaying, lines.length, seekToLine]); + useEffect(() => { + if (isPlaying) { + clearSearch(); + } + }, [clearSearch, isPlaying]); + return (
@@ -140,6 +169,8 @@ export const TTYPlayer = ({ seekToLine={seekToLine} xTermSearchFn={search} setIsPlaying={setIsPlaying} + searchQuery={searchQuery} + setSearchQuery={setSearchQuery} /> @@ -157,11 +188,17 @@ export const TTYPlayer = ({ - + - + diff --git a/x-pack/plugins/session_view/public/components/tty_player/translations.ts b/x-pack/plugins/session_view/public/components/tty_player/translations.ts new file mode 100644 index 000000000000..8d1dfd749741 --- /dev/null +++ b/x-pack/plugins/session_view/public/components/tty_player/translations.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { i18n } from '@kbn/i18n'; + +export const PROCESS_DATA_LIMIT_EXCEEDED_START = i18n.translate( + 'xpack.sessionView.processDataLimitExceededStart', + { + defaultMessage: 'Data limit reached for', + } +); + +export const PROCESS_DATA_LIMIT_EXCEEDED_END = i18n.translate( + 'xpack.sessionView.processDataLimitExceededEnd', + { + defaultMessage: 'See "max_kilobytes_per_process" in advanced policy configuration.', + } +); + +export const VIEW_POLICIES = i18n.translate('xpack.sessionView.viewPoliciesLink', { + defaultMessage: 'VIEW POLICIES', +}); diff --git a/x-pack/plugins/session_view/public/components/tty_player/xterm_search.ts b/x-pack/plugins/session_view/public/components/tty_player/xterm_search.ts index 3c430d691e3f..fee2aff4b545 100644 --- a/x-pack/plugins/session_view/public/components/tty_player/xterm_search.ts +++ b/x-pack/plugins/session_view/public/components/tty_player/xterm_search.ts @@ -9,7 +9,7 @@ * Copyright (c) 2017 The xterm.js authors. All rights reserved. * @license MIT */ -import { Terminal, IDisposable, ITerminalAddon, ISelectionPosition } from 'xterm'; +import { Terminal, IDisposable, ITerminalAddon, IBufferRange } from 'xterm'; export interface ISearchOptions { regex?: boolean; @@ -83,14 +83,14 @@ export class SearchAddon implements ITerminalAddon { let startCol = 0; let startRow = 0; - let currentSelection: ISelectionPosition | undefined; + let currentSelection: IBufferRange | undefined; if (this._terminal.hasSelection()) { const incremental = searchOptions ? searchOptions.incremental : false; // Start from the selection end if there is a selection // For incremental search, use existing row currentSelection = this._terminal.getSelectionPosition()!; - startRow = incremental ? currentSelection.startRow : currentSelection.endRow; - startCol = incremental ? currentSelection.startColumn : currentSelection.endColumn; + startRow = incremental ? currentSelection.start.y : currentSelection.end.y; + startCol = incremental ? currentSelection.start.x : currentSelection.end.x; } if (searchOptions?.lastLineOnly) { @@ -139,7 +139,7 @@ export class SearchAddon implements ITerminalAddon { // If there is only one result, wrap back and return selection if it exists. if (!result && currentSelection) { - searchPosition.startRow = currentSelection.startRow; + searchPosition.startRow = currentSelection.start.y; searchPosition.startCol = 0; result = this._findInLine(term, searchPosition, searchOptions); } @@ -170,12 +170,12 @@ export class SearchAddon implements ITerminalAddon { let startCol = this._terminal.cols; let result: ISearchResult | undefined; const incremental = searchOptions ? searchOptions.incremental : false; - let currentSelection: ISelectionPosition | undefined; + let currentSelection: IBufferRange | undefined; if (this._terminal.hasSelection()) { currentSelection = this._terminal.getSelectionPosition()!; // Start from selection start if there is a selection - startRow = currentSelection.startRow; - startCol = currentSelection.startColumn; + startRow = currentSelection.start.y; + startCol = currentSelection.start.x; } else if (searchOptions?.lastLineOnly) { startRow = this._terminal.buffer.active.cursorY - 1; startCol = this._terminal.cols; @@ -194,8 +194,8 @@ export class SearchAddon implements ITerminalAddon { if (!isOldResultHighlighted) { // If selection was not able to be expanded to the right, then try reverse search if (currentSelection) { - searchPosition.startRow = currentSelection.endRow; - searchPosition.startCol = currentSelection.endColumn; + searchPosition.startRow = currentSelection.end.y; + searchPosition.startCol = currentSelection.end.x; } result = this._findInLine(term, searchPosition, searchOptions, true); } diff --git a/x-pack/plugins/session_view/public/components/tty_player_controls/index.test.tsx b/x-pack/plugins/session_view/public/components/tty_player_controls/index.test.tsx index 8a536e1ae022..61a3958c1b88 100644 --- a/x-pack/plugins/session_view/public/components/tty_player_controls/index.test.tsx +++ b/x-pack/plugins/session_view/public/components/tty_player_controls/index.test.tsx @@ -8,6 +8,7 @@ import React from 'react'; import { AppContextTestRender, createAppRootMockRenderer } from '../../test'; import { ProcessEvent } from '../../../common/types/process_tree'; import { TTYPlayerControls, TTYPlayerControlsDeps } from '.'; +import { TTYPlayerLineMarkerType } from './tty_player_controls_markers'; const MOCK_PROCESS_EVENT_START: ProcessEvent = { process: { @@ -100,11 +101,11 @@ describe('TTYPlayerControls component', () => { expect(props.onSeekLine).toHaveBeenCalledWith(9); }); - it('render output markers', async () => { + it('render process_changed markers', async () => { renderResult = mockedContext.render(); expect( renderResult.queryAllByRole('button', { - name: 'output', + name: TTYPlayerLineMarkerType.ProcessChanged, }) ).toHaveLength(props.processStartMarkers.length); }); @@ -115,12 +116,10 @@ describe('TTYPlayerControls component', () => { event: { process: { ...MOCK_PROCESS_EVENT_MIDDLE, - io: { - max_bytes_per_process_exceeded: true, - }, }, }, line: 2, + maxBytesExceeded: true, }, { event: MOCK_PROCESS_EVENT_END, line: 4 }, ]; @@ -129,12 +128,12 @@ describe('TTYPlayerControls component', () => { ); expect( renderResult.queryAllByRole('button', { - name: 'output', + name: TTYPlayerLineMarkerType.ProcessChanged, }) ).toHaveLength(2); expect( renderResult.queryAllByRole('button', { - name: 'data_limited', + name: TTYPlayerLineMarkerType.ProcessDataLimitReached, }) ).toHaveLength(1); }); diff --git a/x-pack/plugins/session_view/public/components/tty_player_controls/tty_player_controls_markers/index.tsx b/x-pack/plugins/session_view/public/components/tty_player_controls/tty_player_controls_markers/index.tsx index b10ba00e1fc2..e84ed7fcf34a 100644 --- a/x-pack/plugins/session_view/public/components/tty_player_controls/tty_player_controls_markers/index.tsx +++ b/x-pack/plugins/session_view/public/components/tty_player_controls/tty_player_controls_markers/index.tsx @@ -19,9 +19,14 @@ type Props = { onSeekLine(line: number): void; }; +export enum TTYPlayerLineMarkerType { + ProcessChanged = 'process_changed', + ProcessDataLimitReached = 'data_limited', +} + type TTYPlayerLineMarker = { line: number; - type: 'output' | 'data_limited'; + type: TTYPlayerLineMarkerType; name: string; }; @@ -44,10 +49,11 @@ export const TTYPlayerControlsMarkers = ({ return []; } return processStartMarkers.map( - ({ event, line }) => + ({ event, line, maxBytesExceeded }) => ({ - type: - event.process?.io?.max_bytes_per_process_exceeded === true ? 'data_limited' : 'output', + type: maxBytesExceeded + ? TTYPlayerLineMarkerType.ProcessDataLimitReached + : TTYPlayerLineMarkerType.ProcessChanged, line, name: event.process?.name, } as TTYPlayerLineMarker) diff --git a/x-pack/plugins/session_view/public/components/tty_player_controls/tty_player_controls_markers/styles.ts b/x-pack/plugins/session_view/public/components/tty_player_controls/tty_player_controls_markers/styles.ts index f48cfd3795eb..48c7c67128c6 100644 --- a/x-pack/plugins/session_view/public/components/tty_player_controls/tty_player_controls_markers/styles.ts +++ b/x-pack/plugins/session_view/public/components/tty_player_controls/tty_player_controls_markers/styles.ts @@ -9,7 +9,7 @@ import { useMemo } from 'react'; import { CSSObject } from '@emotion/react'; import { useEuiTheme } from '../../../hooks'; -type TTYPlayerLineMarkerType = 'output' | 'data_limited'; +import { TTYPlayerLineMarkerType } from '.'; export const useStyles = (progress: number) => { const { euiTheme, euiVars } = useEuiTheme(); @@ -30,7 +30,7 @@ export const useStyles = (progress: number) => { }; const getMarkerBackgroundColor = (type: TTYPlayerLineMarkerType, selected: boolean) => { - if (type === 'data_limited') { + if (type === TTYPlayerLineMarkerType.ProcessDataLimitReached) { return euiVars.terminalOutputMarkerWarning; } if (selected) { @@ -105,7 +105,7 @@ export const useStyles = (progress: number) => { left: progress + '%', top: 16, fill: - type === 'data_limited' + type === TTYPlayerLineMarkerType.ProcessDataLimitReached ? euiVars.terminalOutputMarkerWarning : euiVars.terminalOutputMarkerAccent, }); diff --git a/x-pack/plugins/session_view/public/components/tty_search_bar/index.test.tsx b/x-pack/plugins/session_view/public/components/tty_search_bar/index.test.tsx index 06fa17a6c151..7b8adbc47d52 100644 --- a/x-pack/plugins/session_view/public/components/tty_search_bar/index.test.tsx +++ b/x-pack/plugins/session_view/public/components/tty_search_bar/index.test.tsx @@ -7,7 +7,6 @@ import React from 'react'; import { renderHook } from '@testing-library/react-hooks'; import userEvent from '@testing-library/user-event'; -import { fireEvent } from '@testing-library/dom'; import { AppContextTestRender, createAppRootMockRenderer } from '../../test'; import { sessionViewIOEventsMock } from '../../../common/mocks/responses/session_view_io_events.mock'; import { useIOLines } from '../tty_player/hooks'; @@ -35,6 +34,8 @@ describe('TTYSearchBar component', () => { seekToLine: jest.fn(), xTermSearchFn: jest.fn(), setIsPlaying: jest.fn(), + searchQuery: '', + setSearchQuery: jest.fn(), }; }); @@ -44,33 +45,20 @@ describe('TTYSearchBar component', () => { }); it('does a search when a user enters text and hits enter', async () => { - renderResult = mockedContext.render(); - - const searchInput = renderResult.queryByTestId('sessionView:searchBar')?.querySelector('input'); - if (searchInput) { - userEvent.type(searchInput, '-h'); - fireEvent.keyUp(searchInput, { key: 'Enter', code: 'Enter' }); - } + renderResult = mockedContext.render(); expect(props.seekToLine).toHaveBeenCalledTimes(1); // there is a slight delay in the seek in xtermjs, so we wait 100ms before trying to highlight a result. await new Promise((r) => setTimeout(r, 100)); - expect(props.xTermSearchFn).toHaveBeenCalledTimes(2); - expect(props.xTermSearchFn).toHaveBeenNthCalledWith(1, '', 0); - expect(props.xTermSearchFn).toHaveBeenNthCalledWith(2, '-h', 6); + expect(props.xTermSearchFn).toHaveBeenCalledTimes(1); + expect(props.xTermSearchFn).toHaveBeenNthCalledWith(1, '-h', 6); expect(props.setIsPlaying).toHaveBeenCalledWith(false); }); it('calls seekToline and xTermSearchFn when currentMatch changes', async () => { - renderResult = mockedContext.render(); - - const searchInput = renderResult.queryByTestId('sessionView:searchBar')?.querySelector('input'); - if (searchInput) { - userEvent.type(searchInput, '-h'); - fireEvent.keyUp(searchInput, { key: 'Enter', code: 'Enter' }); - } + renderResult = mockedContext.render(); await new Promise((r) => setTimeout(r, 100)); @@ -83,27 +71,23 @@ describe('TTYSearchBar component', () => { expect(props.seekToLine).toHaveBeenNthCalledWith(1, 26); expect(props.seekToLine).toHaveBeenNthCalledWith(2, 100); - expect(props.xTermSearchFn).toHaveBeenCalledTimes(3); - expect(props.xTermSearchFn).toHaveBeenNthCalledWith(1, '', 0); - expect(props.xTermSearchFn).toHaveBeenNthCalledWith(2, '-h', 6); - expect(props.xTermSearchFn).toHaveBeenNthCalledWith(3, '-h', 13); - expect(props.setIsPlaying).toHaveBeenCalledTimes(3); + expect(props.xTermSearchFn).toHaveBeenCalledTimes(2); + expect(props.xTermSearchFn).toHaveBeenNthCalledWith(1, '-h', 6); + expect(props.xTermSearchFn).toHaveBeenNthCalledWith(2, '-h', 13); + expect(props.setIsPlaying).toHaveBeenCalledTimes(2); }); it('calls xTermSearchFn with empty query when search is cleared', async () => { - renderResult = mockedContext.render(); - - const searchInput = renderResult.queryByTestId('sessionView:searchBar')?.querySelector('input'); - if (searchInput) { - userEvent.type(searchInput, '-h'); - fireEvent.keyUp(searchInput, { key: 'Enter', code: 'Enter' }); - } + renderResult = mockedContext.render(); await new Promise((r) => setTimeout(r, 100)); userEvent.click(renderResult.getByTestId('clearSearchButton')); await new Promise((r) => setTimeout(r, 100)); - expect(props.xTermSearchFn).toHaveBeenNthCalledWith(3, '', 0); + renderResult.rerender(); + + expect(props.setSearchQuery).toHaveBeenNthCalledWith(1, ''); + expect(props.xTermSearchFn).toHaveBeenNthCalledWith(2, '', 0); expect(props.setIsPlaying).toHaveBeenCalledWith(false); }); }); diff --git a/x-pack/plugins/session_view/public/components/tty_search_bar/index.tsx b/x-pack/plugins/session_view/public/components/tty_search_bar/index.tsx index 18b829127ab2..47d166167bb2 100644 --- a/x-pack/plugins/session_view/public/components/tty_search_bar/index.tsx +++ b/x-pack/plugins/session_view/public/components/tty_search_bar/index.tsx @@ -20,6 +20,8 @@ export interface TTYSearchBarDeps { seekToLine(index: number): void; xTermSearchFn(query: string, index: number): void; setIsPlaying(value: boolean): void; + searchQuery: string; + setSearchQuery(value: string): void; } const STRIP_NEWLINES_REGEX = /^(\r\n|\r|\n|\n\r)/; @@ -29,9 +31,10 @@ export const TTYSearchBar = ({ seekToLine, xTermSearchFn, setIsPlaying, + searchQuery, + setSearchQuery, }: TTYSearchBarDeps) => { const [currentMatch, setCurrentMatch] = useState(null); - const [searchQuery, setSearchQuery] = useState(''); const jumpToMatch = useCallback( (match) => { @@ -105,7 +108,7 @@ export const TTYSearchBar = ({ setSearchQuery(query); setCurrentMatch(null); }, - [setIsPlaying] + [setIsPlaying, setSearchQuery] ); const onSetCurrentMatch = useCallback( diff --git a/x-pack/plugins/session_view/public/test/index.tsx b/x-pack/plugins/session_view/public/test/index.tsx index 244560d366ac..6ccc8ca2b199 100644 --- a/x-pack/plugins/session_view/public/test/index.tsx +++ b/x-pack/plugins/session_view/public/test/index.tsx @@ -17,6 +17,7 @@ import { CoreStart } from '@kbn/core/public'; import { coreMock } from '@kbn/core/public/mocks'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; +import { SECURITY_APP_ID, SESSION_VIEW_APP_ID } from '../../common/constants'; type UiRender = (ui: React.ReactElement, options?: RenderOptions) => RenderResult; @@ -46,8 +47,10 @@ const createCoreStartMock = ( // Mock the certain APP Ids returned by `application.getUrlForApp()` coreStart.application.getUrlForApp.mockImplementation((appId) => { switch (appId) { - case 'sessionView': + case SESSION_VIEW_APP_ID: return '/app/sessionView'; + case SECURITY_APP_ID: + return '/app/security'; default: return `${appId} not mocked!`; } diff --git a/x-pack/plugins/session_view/public/types.ts b/x-pack/plugins/session_view/public/types.ts index d276f0e9518a..fa5f9d1ebb04 100644 --- a/x-pack/plugins/session_view/public/types.ts +++ b/x-pack/plugins/session_view/public/types.ts @@ -27,6 +27,7 @@ export interface SessionViewDeps { // Callback used when alert flyout panel is closed handleOnAlertDetailsClosed: () => void ) => void; + canAccessEndpointManagement?: boolean; } export interface EuiTabProps { diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/home.test.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/home.test.ts index 2b13553d4e60..ea71e64f2258 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/home.test.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/home.test.ts @@ -58,7 +58,7 @@ describe('', () => { /** * TODO: investigate why we need to skip this test. * My guess is a change in the useRequest() hook and maybe a setTimout() that hasn't been - * mocked with jest.useFakeTimers(); + * mocked with jest.useFakeTimers('legacy'); * I tested locally and the loading spinner is present in the UI so skipping this test for now. */ test.skip('should display a loading while fetching the repositories', () => { diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/repository_add.test.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/repository_add.test.ts index 3a34926272e0..c9da67b98d62 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/repository_add.test.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/repository_add.test.ts @@ -37,7 +37,7 @@ describe('', () => { /** * TODO: investigate why we need to skip this test. * My guess is a change in the useRequest() hook and maybe a setTimout() that hasn't been - * mocked with jest.useFakeTimers(); + * mocked with jest.useFakeTimers('legacy'); * I tested locally and the loading spinner is present in the UI so skipping this test for now. */ test.skip('should indicate that the repository types are loading', () => { diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/snapshot_list.test.tsx b/x-pack/plugins/snapshot_restore/__jest__/client_integration/snapshot_list.test.tsx index bc6eae3fcd57..a94d0fdcd371 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/snapshot_list.test.tsx +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/snapshot_list.test.tsx @@ -55,7 +55,7 @@ describe('', () => { let getSearchErrorText: SnapshotListTestBed['actions']['getSearchErrorText']; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); const snapshot = fixtures.getSnapshot({ repository: REPOSITORY_NAME, snapshot: getRandomString(), diff --git a/x-pack/plugins/spaces/server/default_space/default_space_service.test.ts b/x-pack/plugins/spaces/server/default_space/default_space_service.test.ts index 5aaeb97ae76c..8250a993192b 100644 --- a/x-pack/plugins/spaces/server/default_space/default_space_service.test.ts +++ b/x-pack/plugins/spaces/server/default_space/default_space_service.test.ts @@ -214,7 +214,7 @@ test('maintains unavailable status if default space cannot be created', async () }); test('retries operation', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); const { repository, serviceStatus$ } = setup({ elasticsearchStatus: ServiceStatusLevels.available, diff --git a/x-pack/plugins/stack_alerts/public/alert_types/threshold/visualization.test.tsx b/x-pack/plugins/stack_alerts/public/alert_types/threshold/visualization.test.tsx index 643d6c79afc7..e6e1b18c5de8 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/threshold/visualization.test.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/threshold/visualization.test.tsx @@ -81,7 +81,7 @@ describe('ThresholdVisualization', () => { test('periodically requests visualization data', async () => { const refreshRate = 10; - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); const wrapper = mountWithIntl( ( - - - - - + + + + + + + + + + ); diff --git a/x-pack/plugins/synthetics/e2e/journeys/data_view_permissions.ts b/x-pack/plugins/synthetics/e2e/journeys/data_view_permissions.ts index 5aa7ee96f25b..648337218979 100644 --- a/x-pack/plugins/synthetics/e2e/journeys/data_view_permissions.ts +++ b/x-pack/plugins/synthetics/e2e/journeys/data_view_permissions.ts @@ -48,10 +48,10 @@ journey('DataViewPermissions', async ({ page, params }) => { step('Click explore data button', async () => { await page.click(byTestId('uptimeExploreDataButton')); await waitForLoadingToFinish({ page }); - await page.waitForSelector(`text=${permissionError}`, TIMEOUT_60_SEC); - expect(await page.$(`text=${permissionError}`)).toBeTruthy(); }); -}); -const permissionError = - "Unable to create Data View. You don't have the required permission, please contact your admin."; + step('it renders for viewer user as well', async () => { + await page.waitForSelector(`text=browser`, TIMEOUT_60_SEC); + expect(await page.$(`text=Monitor duration`)).toBeTruthy(); + }); +}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_container.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_container.test.tsx index 3580df2c317a..6d8b0b455a5f 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_container.test.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_container.test.tsx @@ -135,7 +135,7 @@ const defaultState = { describe('WaterfallChartContainer', () => { beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); it('does not display waterfall chart unavailable when isWaterfallSupported is true', () => { diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_wrapper.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_wrapper.test.tsx index 81ed2d024340..ba26366644db 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_wrapper.test.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_wrapper.test.tsx @@ -26,7 +26,7 @@ const getHighLightedItems = (query: string, filters: string[]) => { describe('WaterfallChartWrapper', () => { beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); it('renders the correct sidebar items', () => { diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/synthetics/step_detail/waterfall/waterfall_filter.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/synthetics/step_detail/waterfall/waterfall_filter.test.tsx index 42cb46427011..c36e46b9d999 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/synthetics/step_detail/waterfall/waterfall_filter.test.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/synthetics/step_detail/waterfall/waterfall_filter.test.tsx @@ -19,7 +19,7 @@ import { } from '../../waterfall/components/translations'; describe('waterfall filter', () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); it('renders correctly', () => { const { getByLabelText, getByTitle } = render( diff --git a/x-pack/plugins/task_manager/server/lib/bulk_remove_if_exist.test.ts b/x-pack/plugins/task_manager/server/lib/bulk_remove_if_exist.test.ts new file mode 100644 index 000000000000..ac71653c31c8 --- /dev/null +++ b/x-pack/plugins/task_manager/server/lib/bulk_remove_if_exist.test.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import uuid from 'uuid'; +import { SavedObjectsErrorHelpers } from '@kbn/core/server'; +import { bulkRemoveIfExist } from './bulk_remove_if_exist'; +import { taskStoreMock } from '../task_store.mock'; + +describe('removeIfExists', () => { + const ids = [uuid.v4(), uuid.v4()]; + + test('removes the tasks by its IDs', async () => { + const ts = taskStoreMock.create({}); + + expect(await bulkRemoveIfExist(ts, ids)).toBe(undefined); + expect(ts.bulkRemove).toHaveBeenCalledWith(ids); + }); + + test('handles 404 errors caused by the task not existing', async () => { + const ts = taskStoreMock.create({}); + + ts.bulkRemove.mockRejectedValue( + SavedObjectsErrorHelpers.createGenericNotFoundError('task', ids[0]) + ); + + expect(await bulkRemoveIfExist(ts, ids)).toBe(undefined); + expect(ts.bulkRemove).toHaveBeenCalledWith(ids); + }); + + test('throws if any other error is caused by task removal', async () => { + const ts = taskStoreMock.create({}); + + const error = SavedObjectsErrorHelpers.createInvalidVersionError(uuid.v4()); + ts.bulkRemove.mockRejectedValue(error); + + expect(bulkRemoveIfExist(ts, ids)).rejects.toBe(error); + expect(ts.bulkRemove).toHaveBeenCalledWith(ids); + }); +}); diff --git a/x-pack/plugins/task_manager/server/lib/bulk_remove_if_exist.ts b/x-pack/plugins/task_manager/server/lib/bulk_remove_if_exist.ts new file mode 100644 index 000000000000..c3c1a61868e6 --- /dev/null +++ b/x-pack/plugins/task_manager/server/lib/bulk_remove_if_exist.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SavedObjectsErrorHelpers } from '@kbn/core/server'; +import { TaskStore } from '../task_store'; + +/** + * Removes a task from the store, ignoring a not found error + * Other errors are re-thrown + * + * @param taskStore + * @param taskIds + */ +export async function bulkRemoveIfExist(taskStore: TaskStore, taskIds: string[]) { + try { + return await taskStore.bulkRemove(taskIds); + } catch (err) { + if (!SavedObjectsErrorHelpers.isNotFoundError(err)) { + throw err; + } + } +} diff --git a/x-pack/plugins/task_manager/server/mocks.ts b/x-pack/plugins/task_manager/server/mocks.ts index 6cf405c4a1b2..97ebb227d17c 100644 --- a/x-pack/plugins/task_manager/server/mocks.ts +++ b/x-pack/plugins/task_manager/server/mocks.ts @@ -27,6 +27,7 @@ const createStartMock = () => { ephemeralRunNow: jest.fn(), ensureScheduled: jest.fn(), removeIfExists: jest.fn(), + bulkRemoveIfExist: jest.fn(), supportsEphemeralTasks: jest.fn(), bulkUpdateSchedules: jest.fn(), bulkSchedule: jest.fn(), diff --git a/x-pack/plugins/task_manager/server/plugin.ts b/x-pack/plugins/task_manager/server/plugin.ts index e4cf8730f6db..d16fab4a48e2 100644 --- a/x-pack/plugins/task_manager/server/plugin.ts +++ b/x-pack/plugins/task_manager/server/plugin.ts @@ -18,10 +18,12 @@ import { ServiceStatusLevels, CoreStatus, } from '@kbn/core/server'; +import type { SavedObjectsBulkDeleteResponse } from '@kbn/core/server'; import { TaskPollingLifecycle } from './polling_lifecycle'; import { TaskManagerConfig } from './config'; import { createInitialMiddleware, addMiddlewareToChain, Middleware } from './lib/middleware'; import { removeIfExists } from './lib/remove_if_exists'; +import { bulkRemoveIfExist } from './lib/bulk_remove_if_exist'; import { setupSavedObjects } from './saved_objects'; import { TaskDefinitionRegistry, TaskTypeDictionary, REMOVED_TYPES } from './task_type_dictionary'; import { AggregationOpts, FetchResult, SearchOpts, TaskStore } from './task_store'; @@ -33,6 +35,7 @@ import { EphemeralTaskLifecycle } from './ephemeral_task_lifecycle'; import { EphemeralTask, ConcreteTaskInstance } from './task'; import { registerTaskManagerUsageCollector } from './usage'; import { TASK_MANAGER_INDEX } from './constants'; + export interface TaskManagerSetupContract { /** * @deprecated @@ -59,7 +62,11 @@ export type TaskManagerStartContract = Pick< > & Pick & { removeIfExists: TaskStore['remove']; - } & { supportsEphemeralTasks: () => boolean }; + } & { + bulkRemoveIfExist: (ids: string[]) => Promise; + } & { + supportsEphemeralTasks: () => boolean; + }; export class TaskManagerPlugin implements Plugin @@ -248,6 +255,7 @@ export class TaskManagerPlugin taskStore.aggregate(opts), get: (id: string) => taskStore.get(id), remove: (id: string) => taskStore.remove(id), + bulkRemoveIfExist: (ids: string[]) => bulkRemoveIfExist(taskStore, ids), removeIfExists: (id: string) => removeIfExists(taskStore, id), schedule: (...args) => taskScheduling.schedule(...args), bulkSchedule: (...args) => taskScheduling.bulkSchedule(...args), diff --git a/x-pack/plugins/task_manager/server/polling/delay_on_claim_conflicts.test.ts b/x-pack/plugins/task_manager/server/polling/delay_on_claim_conflicts.test.ts index cdfebd1156c5..c57c1b2b7bbc 100644 --- a/x-pack/plugins/task_manager/server/polling/delay_on_claim_conflicts.test.ts +++ b/x-pack/plugins/task_manager/server/polling/delay_on_claim_conflicts.test.ts @@ -16,7 +16,7 @@ import { TaskLifecycleEvent } from '../polling_lifecycle'; import { FillPoolResult } from '../lib/fill_pool'; describe('delayOnClaimConflicts', () => { - beforeEach(() => jest.useFakeTimers()); + beforeEach(() => jest.useFakeTimers('legacy')); test( 'initializes with a delay of 0', diff --git a/x-pack/plugins/task_manager/server/polling/task_poller.test.ts b/x-pack/plugins/task_manager/server/polling/task_poller.test.ts index f01ea2f018cb..396cef0cfa7a 100644 --- a/x-pack/plugins/task_manager/server/polling/task_poller.test.ts +++ b/x-pack/plugins/task_manager/server/polling/task_poller.test.ts @@ -14,7 +14,7 @@ import { loggingSystemMock } from '@kbn/core/server/mocks'; import { asOk, asErr } from '../lib/result_type'; describe('TaskPoller', () => { - beforeEach(() => jest.useFakeTimers()); + beforeEach(() => jest.useFakeTimers('legacy')); test( 'intializes the poller with the provided interval', diff --git a/x-pack/plugins/task_manager/server/task_store.mock.ts b/x-pack/plugins/task_manager/server/task_store.mock.ts index 4e9c5dda3952..cc9aac708b2d 100644 --- a/x-pack/plugins/task_manager/server/task_store.mock.ts +++ b/x-pack/plugins/task_manager/server/task_store.mock.ts @@ -20,6 +20,7 @@ export const taskStoreMock = { schedule: jest.fn(), bulkSchedule: jest.fn(), bulkUpdate: jest.fn(), + bulkRemove: jest.fn(), get: jest.fn(), getLifecycle: jest.fn(), fetch: jest.fn(), diff --git a/x-pack/plugins/task_manager/server/task_store.test.ts b/x-pack/plugins/task_manager/server/task_store.test.ts index 561d4ac6a098..7bc731a0d8b6 100644 --- a/x-pack/plugins/task_manager/server/task_store.test.ts +++ b/x-pack/plugins/task_manager/server/task_store.test.ts @@ -25,6 +25,8 @@ import { mockLogger } from './test_utils'; const savedObjectsClient = savedObjectsRepositoryMock.create(); const serializer = savedObjectsServiceMock.createSerializer(); +const randomId = () => `id-${_.random(1, 20)}`; + beforeEach(() => jest.resetAllMocks()); const mockedDate = new Date('2019-02-12T21:01:22.479Z'); @@ -529,6 +531,41 @@ describe('TaskStore', () => { }); }); + describe('bulkRemove', () => { + let store: TaskStore; + + const tasksIdsToDelete = [randomId(), randomId()]; + + beforeAll(() => { + store = new TaskStore({ + index: 'tasky', + taskManagerId: '', + serializer, + esClient: elasticsearchServiceMock.createClusterClient().asInternalUser, + definitions: taskDefinitions, + savedObjectsRepository: savedObjectsClient, + }); + }); + + test('removes the tasks with the specified ids', async () => { + const result = await store.bulkRemove(tasksIdsToDelete); + expect(result).toBeUndefined(); + expect(savedObjectsClient.bulkDelete).toHaveBeenCalledWith([ + { type: 'task', id: tasksIdsToDelete[0] }, + { type: 'task', id: tasksIdsToDelete[1] }, + ]); + }); + + test('pushes error from saved objects client to errors$', async () => { + const firstErrorPromise = store.errors$.pipe(first()).toPromise(); + savedObjectsClient.bulkDelete.mockRejectedValue(new Error('Failure')); + await expect(store.bulkRemove(tasksIdsToDelete)).rejects.toThrowErrorMatchingInlineSnapshot( + `"Failure"` + ); + expect(await firstErrorPromise).toMatchInlineSnapshot(`[Error: Failure]`); + }); + }); + describe('get', () => { let store: TaskStore; @@ -802,5 +839,3 @@ describe('TaskStore', () => { }); }); }); - -const randomId = () => `id-${_.random(1, 20)}`; diff --git a/x-pack/plugins/task_manager/server/task_store.ts b/x-pack/plugins/task_manager/server/task_store.ts index 5d1a7246440f..c2c003ff6f7b 100644 --- a/x-pack/plugins/task_manager/server/task_store.ts +++ b/x-pack/plugins/task_manager/server/task_store.ts @@ -12,6 +12,7 @@ import { Subject } from 'rxjs'; import { omit, defaults } from 'lodash'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import type { SavedObjectsBulkDeleteResponse } from '@kbn/core/server'; import { SavedObject, @@ -294,6 +295,22 @@ export class TaskStore { } } + /** + * Bulk removes the specified tasks from the index. + * + * @param {SavedObjectsBulkDeleteObject[]} savedObjectsToDelete + * @returns {Promise} + */ + public async bulkRemove(taskIds: string[]): Promise { + try { + const savedObjectsToDelete = taskIds.map((taskId) => ({ id: taskId, type: 'task' })); + return await this.savedObjectsRepository.bulkDelete(savedObjectsToDelete); + } catch (e) { + this.errors$.next(e); + throw e; + } + } + /** * Gets a task by id * diff --git a/x-pack/plugins/threat_intelligence/cypress/e2e/indicators.cy.ts b/x-pack/plugins/threat_intelligence/cypress/e2e/indicators.cy.ts index 17c4d79e60dc..4de89a37f883 100644 --- a/x-pack/plugins/threat_intelligence/cypress/e2e/indicators.cy.ts +++ b/x-pack/plugins/threat_intelligence/cypress/e2e/indicators.cy.ts @@ -53,6 +53,15 @@ describe('Indicators', () => { esArchiverUnload('threat_intelligence'); }); + describe('Indicators page loading', () => { + it('verify the fleet plugin integrations endpoint exists', () => { + cy.request({ + method: 'GET', + url: '/api/fleet/epm/packages', + }).should((response) => expect(response.status).to.eq(200)); + }); + }); + describe('Indicators page basics', () => { before(() => { cy.visit(THREAT_INTELLIGENCE); diff --git a/x-pack/plugins/threat_intelligence/cypress/e2e/query_bar.cy.ts b/x-pack/plugins/threat_intelligence/cypress/e2e/query_bar.cy.ts index e2a0459dc3fd..4c55a0505d34 100644 --- a/x-pack/plugins/threat_intelligence/cypress/e2e/query_bar.cy.ts +++ b/x-pack/plugins/threat_intelligence/cypress/e2e/query_bar.cy.ts @@ -6,23 +6,23 @@ */ import { - INDICATOR_TYPE_CELL, - TOGGLE_FLYOUT_BUTTON, - FLYOUT_CLOSE_BUTTON, - KQL_FILTER, - INDICATORS_TABLE_CELL_FILTER_IN_BUTTON, - INDICATORS_TABLE_CELL_FILTER_OUT_BUTTON, - FLYOUT_TABLE_TAB_ROW_FILTER_IN_BUTTON, - FLYOUT_TABLE_TAB_ROW_FILTER_OUT_BUTTON, - BARCHART_POPOVER_BUTTON, BARCHART_FILTER_IN_BUTTON, BARCHART_FILTER_OUT_BUTTON, + BARCHART_POPOVER_BUTTON, + FLYOUT_CLOSE_BUTTON, FLYOUT_OVERVIEW_TAB_BLOCKS_FILTER_IN_BUTTON, FLYOUT_OVERVIEW_TAB_BLOCKS_FILTER_OUT_BUTTON, + FLYOUT_OVERVIEW_TAB_BLOCKS_ITEM, FLYOUT_OVERVIEW_TAB_TABLE_ROW_FILTER_IN_BUTTON, FLYOUT_OVERVIEW_TAB_TABLE_ROW_FILTER_OUT_BUTTON, - FLYOUT_OVERVIEW_TAB_BLOCKS_ITEM, + FLYOUT_TABLE_TAB_ROW_FILTER_IN_BUTTON, + FLYOUT_TABLE_TAB_ROW_FILTER_OUT_BUTTON, FLYOUT_TABS, + INDICATOR_TYPE_CELL, + INDICATORS_TABLE_CELL_FILTER_IN_BUTTON, + INDICATORS_TABLE_CELL_FILTER_OUT_BUTTON, + KQL_FILTER, + TOGGLE_FLYOUT_BUTTON, } from '../screens/indicators'; import { selectRange } from '../tasks/select_range'; import { login } from '../tasks/login'; diff --git a/x-pack/plugins/threat_intelligence/cypress/e2e/timeline.cy.ts b/x-pack/plugins/threat_intelligence/cypress/e2e/timeline.cy.ts index de3d0adf72c8..eb24ecaaa5a5 100644 --- a/x-pack/plugins/threat_intelligence/cypress/e2e/timeline.cy.ts +++ b/x-pack/plugins/threat_intelligence/cypress/e2e/timeline.cy.ts @@ -9,18 +9,18 @@ import { BARCHART_POPOVER_BUTTON, BARCHART_TIMELINE_BUTTON, FLYOUT_CLOSE_BUTTON, + FLYOUT_OVERVIEW_TAB_BLOCKS_ITEM, + FLYOUT_OVERVIEW_TAB_BLOCKS_TIMELINE_BUTTON, FLYOUT_OVERVIEW_TAB_TABLE_ROW_TIMELINE_BUTTON, FLYOUT_TABLE_TAB_ROW_TIMELINE_BUTTON, FLYOUT_TABS, + INDICATOR_FLYOUT_INVESTIGATE_IN_TIMELINE_BUTTON, INDICATOR_TYPE_CELL, INDICATORS_TABLE_CELL_TIMELINE_BUTTON, + INDICATORS_TABLE_INVESTIGATE_IN_TIMELINE_BUTTON_ICON, TIMELINE_DRAGGABLE_ITEM, TOGGLE_FLYOUT_BUTTON, UNTITLED_TIMELINE_BUTTON, - FLYOUT_OVERVIEW_TAB_BLOCKS_TIMELINE_BUTTON, - FLYOUT_OVERVIEW_TAB_BLOCKS_ITEM, - INDICATORS_TABLE_INVESTIGATE_IN_TIMELINE_BUTTON_ICON, - INDICATOR_FLYOUT_INVESTIGATE_IN_TIMELINE_BUTTON, } from '../screens/indicators'; import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; import { login } from '../tasks/login'; diff --git a/x-pack/plugins/threat_intelligence/cypress/tasks/login.ts b/x-pack/plugins/threat_intelligence/cypress/tasks/login.ts index f33034dccb9c..accbed00a47e 100644 --- a/x-pack/plugins/threat_intelligence/cypress/tasks/login.ts +++ b/x-pack/plugins/threat_intelligence/cypress/tasks/login.ts @@ -5,8 +5,8 @@ * 2.0. */ -import Url from 'url'; import type { UrlObject } from 'url'; +import Url from 'url'; import * as yaml from 'js-yaml'; diff --git a/x-pack/plugins/threat_intelligence/public/containers/enterprise_guard/enterprise_guard.tsx b/x-pack/plugins/threat_intelligence/public/containers/enterprise_guard/enterprise_guard.tsx index 370c28e8d50f..e87edb2a3516 100644 --- a/x-pack/plugins/threat_intelligence/public/containers/enterprise_guard/enterprise_guard.tsx +++ b/x-pack/plugins/threat_intelligence/public/containers/enterprise_guard/enterprise_guard.tsx @@ -7,7 +7,8 @@ import React, { FC } from 'react'; import { Paywall } from '../../components/paywall'; -import { useKibana, useSecurityContext } from '../../hooks'; +import { useKibana } from '../../hooks/use_kibana'; +import { useSecurityContext } from '../../hooks/use_security_context'; import { SecuritySolutionPluginTemplateWrapper } from '../security_solution_plugin_template_wrapper'; export const EnterpriseGuard: FC = ({ children }) => { diff --git a/x-pack/plugins/threat_intelligence/public/containers/indicators_page_wrapper.tsx b/x-pack/plugins/threat_intelligence/public/containers/indicators_page_wrapper.tsx new file mode 100644 index 000000000000..f5fe0d496ace --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/containers/indicators_page_wrapper.tsx @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { Suspense, VFC } from 'react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { IndicatorsPage } from '../modules/indicators/pages'; +import { SecuritySolutionPluginTemplateWrapper } from './security_solution_plugin_template_wrapper'; + +export const IndicatorsPageWrapper: VFC = () => { + const queryClient = new QueryClient(); + + return ( + + + }> + + + + + ); +}; + +// Note: This is for lazy loading +// eslint-disable-next-line import/no-default-export +export default IndicatorsPageWrapper; diff --git a/x-pack/plugins/threat_intelligence/public/containers/integrations_guard/__snapshots__/integrations_guard.test.tsx.snap b/x-pack/plugins/threat_intelligence/public/containers/integrations_guard/__snapshots__/integrations_guard.test.tsx.snap index 95785cffd6ad..1fcca470c6f1 100644 --- a/x-pack/plugins/threat_intelligence/public/containers/integrations_guard/__snapshots__/integrations_guard.test.tsx.snap +++ b/x-pack/plugins/threat_intelligence/public/containers/integrations_guard/__snapshots__/integrations_guard.test.tsx.snap @@ -1,24 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`checking if the page should be visible (based on indicator count) when indicator count is being loaded should render nothing at all: loading 1`] = ` - - -`; - -exports[`checking if the page should be visible (based on indicator count) when indicator count is loaded and there are no indicators should render empty page when no indicators are found: no indicators 1`] = ` +exports[`IntegrationsGuard should render empty page when no indicators are found and no ti integrations are installed 1`] = `
`; -exports[`checking if the page should be visible (based on indicator count) when loading is done and we have some indicators should render indicators table: indicators are present 1`] = ` +exports[`IntegrationsGuard should render indicators page when we have some ti integrations installed 1`] = ` should be restricted `; + +exports[`IntegrationsGuard should render indicators table when we have some indicators 1`] = ` + + should be restricted + +`; + +exports[`IntegrationsGuard should render loading when indicator count and integrations are being loaded 1`] = ` + + +`; + +exports[`IntegrationsGuard should render loading when indicator only is loading 1`] = ` + + +`; + +exports[`IntegrationsGuard should render loading when integrations only are loading 1`] = ` + + +`; diff --git a/x-pack/plugins/threat_intelligence/public/containers/integrations_guard/integrations_guard.test.tsx b/x-pack/plugins/threat_intelligence/public/containers/integrations_guard/integrations_guard.test.tsx index ce8b3b9aaaa9..b0976721311f 100644 --- a/x-pack/plugins/threat_intelligence/public/containers/integrations_guard/integrations_guard.test.tsx +++ b/x-pack/plugins/threat_intelligence/public/containers/integrations_guard/integrations_guard.test.tsx @@ -5,76 +5,164 @@ * 2.0. */ +import { UseQueryResult } from '@tanstack/react-query'; import { render } from '@testing-library/react'; import React from 'react'; import { IntegrationsGuard } from '.'; import { TestProvidersComponent } from '../../common/mocks/test_providers'; -import { useIntegrationsPageLink, useTIDocumentationLink } from '../../hooks'; +import { + Integration, + useIntegrations, + useIntegrationsPageLink, + useTIDocumentationLink, +} from '../../hooks'; import { useIndicatorsTotalCount } from '../../modules/indicators'; +import { INSTALLATION_STATUS, THREAT_INTELLIGENCE_CATEGORY } from '../../utils'; jest.mock('../../modules/indicators/hooks/use_total_count'); jest.mock('../../hooks/use_integrations_page_link'); jest.mock('../../hooks/use_documentation_link'); +jest.mock('../../hooks/use_integrations'); -describe('checking if the page should be visible (based on indicator count)', () => { - describe('when indicator count is being loaded', () => { - it('should render nothing at all', () => { - ( - useIndicatorsTotalCount as jest.MockedFunction - ).mockReturnValue({ - count: 0, - isLoading: true, - }); - ( - useIntegrationsPageLink as jest.MockedFunction - ).mockReturnValue(''); - ( - useTIDocumentationLink as jest.MockedFunction - ).mockReturnValue(''); - - const { asFragment } = render(should be restricted, { - wrapper: TestProvidersComponent, - }); - - expect(asFragment()).toMatchSnapshot('loading'); +describe('IntegrationsGuard', () => { + it('should render loading when indicator count and integrations are being loaded', async () => { + ( + useIndicatorsTotalCount as jest.MockedFunction + ).mockReturnValue({ + count: 0, + isLoading: true, }); + ( + useIntegrationsPageLink as jest.MockedFunction + ).mockReturnValue(''); + (useTIDocumentationLink as jest.MockedFunction).mockReturnValue( + '' + ); + (useIntegrations as jest.MockedFunction).mockReturnValue({ + isLoading: true, + data: [], + } as unknown as UseQueryResult); + + const { asFragment } = render(should be restricted, { + wrapper: TestProvidersComponent, + }); + + expect(asFragment()).toMatchSnapshot(); + }); + + it('should render loading when indicator only is loading', async () => { + ( + useIndicatorsTotalCount as jest.MockedFunction + ).mockReturnValue({ + count: 0, + isLoading: true, + }); + ( + useIntegrationsPageLink as jest.MockedFunction + ).mockReturnValue(''); + (useTIDocumentationLink as jest.MockedFunction).mockReturnValue( + '' + ); + (useIntegrations as jest.MockedFunction).mockReturnValue({ + isLoading: false, + data: [], + } as unknown as UseQueryResult); + + const { asFragment } = render(should be restricted, { + wrapper: TestProvidersComponent, + }); + + expect(asFragment()).toMatchSnapshot(); + }); + + it('should render loading when integrations only are loading', async () => { + ( + useIndicatorsTotalCount as jest.MockedFunction + ).mockReturnValue({ + count: 0, + isLoading: true, + }); + ( + useIntegrationsPageLink as jest.MockedFunction + ).mockReturnValue(''); + (useTIDocumentationLink as jest.MockedFunction).mockReturnValue( + '' + ); + (useIntegrations as jest.MockedFunction).mockReturnValue({ + isLoading: true, + data: [], + } as unknown as UseQueryResult); + + const { asFragment } = render(should be restricted, { + wrapper: TestProvidersComponent, + }); + + expect(asFragment()).toMatchSnapshot(); + }); + + it('should render empty page when no indicators are found and no ti integrations are installed', async () => { + ( + useIndicatorsTotalCount as jest.MockedFunction + ).mockReturnValue({ + count: 0, + isLoading: false, + }); + ( + useIntegrationsPageLink as jest.MockedFunction + ).mockReturnValue(''); + (useTIDocumentationLink as jest.MockedFunction).mockReturnValue( + '' + ); + (useIntegrations as jest.MockedFunction).mockReturnValue({ + isLoading: false, + data: [], + } as unknown as UseQueryResult); + + const { asFragment } = render(should be restricted, { + wrapper: TestProvidersComponent, + }); + expect(asFragment()).toMatchSnapshot(); }); - describe('when indicator count is loaded and there are no indicators', () => { - it('should render empty page when no indicators are found', async () => { - ( - useIndicatorsTotalCount as jest.MockedFunction - ).mockReturnValue({ - count: 0, - isLoading: false, - }); - ( - useIntegrationsPageLink as jest.MockedFunction - ).mockReturnValue(''); - ( - useTIDocumentationLink as jest.MockedFunction - ).mockReturnValue(''); - - const { asFragment } = render(should be restricted, { - wrapper: TestProvidersComponent, - }); - expect(asFragment()).toMatchSnapshot('no indicators'); + it('should render indicators table when we have some indicators', async () => { + ( + useIndicatorsTotalCount as jest.MockedFunction + ).mockReturnValue({ + count: 7, + isLoading: false, + }); + (useIntegrations as jest.MockedFunction).mockReturnValue({ + isLoading: false, + data: [], + } as unknown as UseQueryResult); + + const { asFragment } = render(should be restricted, { + wrapper: TestProvidersComponent, }); + expect(asFragment()).toMatchSnapshot(); }); - describe('when loading is done and we have some indicators', () => { - it('should render indicators table', async () => { - ( - useIndicatorsTotalCount as jest.MockedFunction - ).mockReturnValue({ - count: 7, - isLoading: false, - }); - - const { asFragment } = render(should be restricted, { - wrapper: TestProvidersComponent, - }); - expect(asFragment()).toMatchSnapshot('indicators are present'); + it('should render indicators page when we have some ti integrations installed', async () => { + ( + useIndicatorsTotalCount as jest.MockedFunction + ).mockReturnValue({ + count: 0, + isLoading: false, + }); + (useIntegrations as jest.MockedFunction).mockReturnValue({ + isLoading: false, + data: [ + { + categories: [THREAT_INTELLIGENCE_CATEGORY], + id: '123', + status: INSTALLATION_STATUS.Installed, + }, + ], + } as unknown as UseQueryResult); + + const { asFragment } = render(should be restricted, { + wrapper: TestProvidersComponent, }); + expect(asFragment()).toMatchSnapshot(); }); }); diff --git a/x-pack/plugins/threat_intelligence/public/containers/integrations_guard/integrations_guard.tsx b/x-pack/plugins/threat_intelligence/public/containers/integrations_guard/integrations_guard.tsx index eb639d6c50f7..4c225e283b91 100644 --- a/x-pack/plugins/threat_intelligence/public/containers/integrations_guard/integrations_guard.tsx +++ b/x-pack/plugins/threat_intelligence/public/containers/integrations_guard/integrations_guard.tsx @@ -7,18 +7,24 @@ import { EuiLoadingLogo } from '@elastic/eui'; import React, { FC } from 'react'; +import { useIntegrations } from '../../hooks'; import { EmptyPage } from '../../modules/empty_page'; import { useIndicatorsTotalCount } from '../../modules/indicators'; import { SecuritySolutionPluginTemplateWrapper } from '../security_solution_plugin_template_wrapper'; /** - * Renders children only if TI integrations are enabled + * Renders the indicators page if the user has some Threat Intelligence integrations installed or + * the user is receiving indicators. + * If none are received, show the EmptyPage with a link to go install integrations. + * While the indicators call and the integrations call are loading, display a loading screen. */ export const IntegrationsGuard: FC = ({ children }) => { - const { count: indicatorsTotalCount, isLoading: isIndicatorsTotalCountLoading } = + const { isLoading: indicatorsTotalCountLoading, count: indicatorsTotalCount } = useIndicatorsTotalCount(); - if (isIndicatorsTotalCountLoading) { + const { isLoading: integrationLoading, data: installedTIIntegrations } = useIntegrations(); + + if (integrationLoading || indicatorsTotalCountLoading) { return ( @@ -26,7 +32,7 @@ export const IntegrationsGuard: FC = ({ children }) => { ); } - const showEmptyPage = indicatorsTotalCount === 0; - - return showEmptyPage ? : <>{children}; + // show indicators page if there are indicators, or if some ti integrations have been added + const showIndicatorsPage = indicatorsTotalCount > 0 || (installedTIIntegrations || []).length > 0; + return showIndicatorsPage ? <>{children} : ; }; diff --git a/x-pack/plugins/threat_intelligence/public/containers/security_solution_plugin_template_wrapper.tsx b/x-pack/plugins/threat_intelligence/public/containers/security_solution_plugin_template_wrapper.tsx index e2e1a735b0c7..c4b97d1b7e72 100644 --- a/x-pack/plugins/threat_intelligence/public/containers/security_solution_plugin_template_wrapper.tsx +++ b/x-pack/plugins/threat_intelligence/public/containers/security_solution_plugin_template_wrapper.tsx @@ -8,7 +8,7 @@ import type { FC } from 'react'; import React from 'react'; import type { KibanaPageTemplateProps } from '@kbn/shared-ux-page-kibana-template-types'; -import { useKibana } from '../hooks'; +import { useKibana } from '../hooks/use_kibana'; /** * Uses securityLayout service to retrieve shared plugin wrapper component and renders plugin routes / children inside of it. diff --git a/x-pack/plugins/threat_intelligence/public/hooks/index.ts b/x-pack/plugins/threat_intelligence/public/hooks/index.ts index 369a364e99ac..29c019274e94 100644 --- a/x-pack/plugins/threat_intelligence/public/hooks/index.ts +++ b/x-pack/plugins/threat_intelligence/public/hooks/index.ts @@ -8,6 +8,7 @@ export * from './use_documentation_link'; export * from './use_field_types'; export * from './use_inspector'; +export * from './use_integrations'; export * from './use_integrations_page_link'; export * from './use_kibana'; export * from './use_security_context'; diff --git a/x-pack/plugins/threat_intelligence/public/hooks/use_integrations.test.tsx b/x-pack/plugins/threat_intelligence/public/hooks/use_integrations.test.tsx new file mode 100644 index 000000000000..ee868cc9803d --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/hooks/use_integrations.test.tsx @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { renderHook } from '@testing-library/react-hooks'; +import { QueryClient, QueryClientProvider, useQuery } from '@tanstack/react-query'; +import { INSTALLATION_STATUS, THREAT_INTELLIGENCE_CATEGORY } from '../utils'; + +const createWrapper = () => { + const queryClient = new QueryClient(); + return ({ children }: { children: any }) => ( + {children} + ); +}; + +const renderUseQuery = (result: { items: any[] }) => + renderHook(() => useQuery(['integrations'], () => result), { + wrapper: createWrapper(), + }); + +describe('useIntegrations', () => { + it('should have undefined data during loading state', async () => { + const mockIntegrations = { items: [] }; + const { result, waitFor } = renderUseQuery(mockIntegrations); + + await waitFor(() => result.current.isLoading); + + expect(result.current.isLoading).toBeTruthy(); + expect(result.current.data).toBeUndefined(); + }); + + it('should return integrations on success', async () => { + const mockIntegrations = { + items: [ + { + categories: [THREAT_INTELLIGENCE_CATEGORY], + id: '123', + status: INSTALLATION_STATUS.Installed, + }, + ], + }; + const { result, waitFor } = renderUseQuery(mockIntegrations); + + await waitFor(() => result.current.isSuccess); + + expect(result.current.isLoading).toBeFalsy(); + expect(result.current.data).toEqual(mockIntegrations); + }); +}); diff --git a/x-pack/plugins/threat_intelligence/public/hooks/use_integrations.ts b/x-pack/plugins/threat_intelligence/public/hooks/use_integrations.ts new file mode 100644 index 000000000000..a5d1f8f45bdc --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/hooks/use_integrations.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useQuery, useQueryClient } from '@tanstack/react-query'; +import { filterIntegrations } from '../utils'; +import { useKibana } from './use_kibana'; + +type IntegrationInstallStatus = 'installed' | 'installing' | 'install_failed'; + +const INTEGRATIONS_URL = '/api/fleet/epm/packages'; + +const INTEGRATIONS_CALL_TIMEOUT = 2000; + +export interface IntegrationResponse { + items: Integration[]; +} + +export interface Integration { + categories: string[]; + id: string; + status: IntegrationInstallStatus; +} + +/** + * Retrieves integrations from the Fleet plugin endpoint /api/fleet/epm/packages. + * The integrations are then filtered, and we only keep the installed ones, + * with category threat_intel and excluding the ti_utils integration. + * We cancel the query in case it's taking too long to not block the Indicators page for the user. + */ +export const useIntegrations = () => { + const { http } = useKibana().services; + const queryKey = ['integrations']; + + // retrieving the list of integrations from the fleet plugin's endpoint + const fetchIntegrations = () => http.get(INTEGRATIONS_URL); + + const query = useQuery(queryKey, fetchIntegrations, { + select: (data: IntegrationResponse) => (data ? filterIntegrations(data.items) : []), + }); + + const queryClient = useQueryClient(); + + // cancel slow integrations call to unblock the UI + setTimeout(() => queryClient.cancelQueries(queryKey), INTEGRATIONS_CALL_TIMEOUT); + + return query; +}; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/pages/indicators.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/pages/indicators.tsx index 1e1f713957fb..f161b7ac1d85 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/pages/indicators.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/pages/indicators.tsx @@ -6,32 +6,25 @@ */ import React, { FC, VFC } from 'react'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { IndicatorsBarChartWrapper } from '../components/barchart'; import { IndicatorsTable } from '../components/table'; -import { useIndicators } from '../hooks/use_indicators'; +import { useAggregatedIndicators, useIndicators, useSourcererDataView } from '../hooks'; import { DefaultPageLayout } from '../../../components/layout'; import { useFilters } from '../../query_bar'; import { FiltersGlobal } from '../../../containers/filters_global'; -import { useSourcererDataView } from '../hooks/use_sourcerer_data_view'; import { FieldTypesProvider } from '../../../containers/field_types_provider'; import { InspectorProvider } from '../../../containers/inspector'; import { useColumnSettings } from '../components/table/hooks'; -import { useAggregatedIndicators } from '../hooks/use_aggregated_indicators'; import { IndicatorsFilters } from '../containers/filters'; -import { useSecurityContext } from '../../../hooks/use_security_context'; +import { useSecurityContext } from '../../../hooks'; import { UpdateStatus } from '../../../components/update_status'; -const queryClient = new QueryClient(); - const IndicatorsPageProviders: FC = ({ children }) => ( - - - - {children} - - - + + + {children} + + ); const IndicatorsPageContent: VFC = () => { @@ -115,7 +108,3 @@ export const IndicatorsPage: VFC = () => ( ); - -// Note: This is for lazy loading -// eslint-disable-next-line import/no-default-export -export default IndicatorsPage; diff --git a/x-pack/plugins/threat_intelligence/public/plugin.tsx b/x-pack/plugins/threat_intelligence/public/plugin.tsx index 88214b3abe3f..d1e4391edfc0 100755 --- a/x-pack/plugins/threat_intelligence/public/plugin.tsx +++ b/x-pack/plugins/threat_intelligence/public/plugin.tsx @@ -8,9 +8,9 @@ import { CoreStart, Plugin } from '@kbn/core/public'; import { Storage } from '@kbn/kibana-utils-plugin/public'; import { Provider as ReduxStoreProvider } from 'react-redux'; -import React, { Suspense, VFC } from 'react'; +import React from 'react'; import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; -import { KibanaContextProvider } from './hooks'; +import { KibanaContextProvider } from './hooks/use_kibana'; import { SecuritySolutionPluginContext, Services, @@ -20,22 +20,12 @@ import { } from './types'; import { SecuritySolutionContext } from './containers/security_solution_context'; import { EnterpriseGuard } from './containers/enterprise_guard'; -import { SecuritySolutionPluginTemplateWrapper } from './containers/security_solution_plugin_template_wrapper'; -import { IntegrationsGuard } from './containers/integrations_guard'; interface AppProps { securitySolutionContext: SecuritySolutionPluginContext; } -const LazyIndicatorsPage = React.lazy(() => import('./modules/indicators/pages/indicators')); - -const IndicatorsPage: VFC = () => ( - - }> - - - -); +const LazyIndicatorsPageWrapper = React.lazy(() => import('./containers/indicators_page_wrapper')); /** * This is used here: @@ -51,9 +41,7 @@ export const createApp = - - - + diff --git a/x-pack/plugins/threat_intelligence/public/utils/filter_integrations.test.ts b/x-pack/plugins/threat_intelligence/public/utils/filter_integrations.test.ts new file mode 100644 index 000000000000..507bdb5ca4ab --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/utils/filter_integrations.test.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Integration } from '../hooks'; +import { filterIntegrations, THREAT_INTELLIGENCE_CATEGORY, THREAT_INTELLIGENCE_UTILITIES } from '.'; + +describe('filterIntegrations', () => { + it('should empty array', async () => { + const mockIntegrations: Integration[] = []; + const result = filterIntegrations(mockIntegrations); + expect(result).toEqual(mockIntegrations); + }); + + it('should return only installed ti integrations (excluding ti_utils)', async () => { + const tiInstalledIntegration: Integration = { + categories: [THREAT_INTELLIGENCE_CATEGORY], + id: '123', + status: 'installed', + }; + const tiNotInstalledIntegration: Integration = { + categories: [THREAT_INTELLIGENCE_CATEGORY], + id: '456', + status: 'install_failed', + }; + const nonTIInstalledIntegration: Integration = { + categories: ['abc'], + id: '789', + status: 'installed', + }; + const tiUtilsIntegration: Integration = { + categories: [THREAT_INTELLIGENCE_CATEGORY], + id: THREAT_INTELLIGENCE_UTILITIES, + status: 'installed', + }; + const randomIntegration: Integration = { + categories: ['abc'], + id: 'def', + status: 'installing', + }; + const mockIntegrations: Integration[] = [ + tiInstalledIntegration, + tiNotInstalledIntegration, + nonTIInstalledIntegration, + tiUtilsIntegration, + randomIntegration, + ]; + + const result = filterIntegrations(mockIntegrations); + + expect(result).toEqual([tiInstalledIntegration]); + }); +}); diff --git a/x-pack/plugins/threat_intelligence/public/utils/filter_integrations.ts b/x-pack/plugins/threat_intelligence/public/utils/filter_integrations.ts new file mode 100644 index 000000000000..075c34344cbc --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/utils/filter_integrations.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Integration } from '../hooks'; + +export const INSTALLATION_STATUS = { + Installed: 'installed', + Installing: 'installing', + InstallFailed: 'install_failed', + NotInstalled: 'not_installed', +}; + +export const THREAT_INTELLIGENCE_CATEGORY = 'threat_intel'; + +export const THREAT_INTELLIGENCE_UTILITIES = 'ti_util'; + +/** + * Filter an array of integrations: + * - of status `installed` + * - with `threat_intel` category + * - excluding `ti_util` integration + * + * For more details see https://github.com/elastic/security-team/issues/4374 + * + * @param integrations the response from the packages endpoint in the Fleet plugin + */ +export const filterIntegrations = (integrations: Integration[]): Integration[] => + integrations.filter( + (pkg: any) => + pkg.status === INSTALLATION_STATUS.Installed && + pkg.categories.find((category: string) => category === THREAT_INTELLIGENCE_CATEGORY) != + null && + pkg.id !== THREAT_INTELLIGENCE_UTILITIES + ); diff --git a/x-pack/plugins/canvas/public/components/es_index_select/index.tsx b/x-pack/plugins/threat_intelligence/public/utils/index.ts similarity index 64% rename from x-pack/plugins/canvas/public/components/es_index_select/index.tsx rename to x-pack/plugins/threat_intelligence/public/utils/index.ts index 29a19e877060..6459678cb807 100644 --- a/x-pack/plugins/canvas/public/components/es_index_select/index.tsx +++ b/x-pack/plugins/threat_intelligence/public/utils/index.ts @@ -5,5 +5,4 @@ * 2.0. */ -export { ESIndexSelect } from './es_index_select'; -export { ESIndexSelect as ESIndexSelectComponent } from './es_index_select.component'; +export * from './filter_integrations'; diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 0bf0d3908eeb..5d28e32be187 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -1163,7 +1163,6 @@ "data.advancedSettings.timepicker.refreshIntervalDefaultsText": "L'intervalle d'actualisation par défaut du filtre temporel. La valeur doit être spécifiée en millisecondes.", "data.advancedSettings.timepicker.refreshIntervalDefaultsTitle": "Intervalle d'actualisation du filtre temporel", "data.advancedSettings.timepicker.thisWeek": "Cette semaine", - "data.advancedSettings.timepicker.timeDefaultsText": "L’option de filtre temporel à utiliser lorsque Kibana est démarré sans filtre", "data.advancedSettings.timepicker.timeDefaultsTitle": "Filtre temporel par défaut", "data.advancedSettings.timepicker.today": "Aujourd'hui", "data.errors.fetchError": "Vérifiez votre réseau et la configuration de votre proxy. Si le problème persiste, contactez votre administrateur réseau.", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 91d6ae6f2bcb..4c173784e59f 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -1161,7 +1161,6 @@ "data.advancedSettings.timepicker.refreshIntervalDefaultsText": "時間フィルターのデフォルト更新間隔「値」はミリ秒で指定する必要があります。", "data.advancedSettings.timepicker.refreshIntervalDefaultsTitle": "タイムピッカーの更新間隔", "data.advancedSettings.timepicker.thisWeek": "今週", - "data.advancedSettings.timepicker.timeDefaultsText": "時間フィルターが選択されずにKibanaが起動した際に使用される時間フィルターです", "data.advancedSettings.timepicker.timeDefaultsTitle": "デフォルトのタイムピッカー", "data.advancedSettings.timepicker.today": "今日", "data.errors.fetchError": "ネットワークとプロキシ構成を確認してください。問題が解決しない場合は、ネットワーク管理者に問い合わせてください。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index c929f0f0d80b..f985f05cc2dc 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -1163,7 +1163,6 @@ "data.advancedSettings.timepicker.refreshIntervalDefaultsText": "时间筛选的默认刷新时间间隔。需要使用毫秒单位指定“值”。", "data.advancedSettings.timepicker.refreshIntervalDefaultsTitle": "时间筛选刷新时间间隔", "data.advancedSettings.timepicker.thisWeek": "本周", - "data.advancedSettings.timepicker.timeDefaultsText": "在未使用时间筛选的情况下启动 Kibana 时要使用的时间筛选选项", "data.advancedSettings.timepicker.timeDefaultsTitle": "时间筛选默认值", "data.advancedSettings.timepicker.today": "今日", "data.errors.fetchError": "请检查您的网络和代理配置。如果问题持续存在,请联系网络管理员。", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.test.tsx index 0112f2296ee0..f937663cd27a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.test.tsx @@ -181,7 +181,7 @@ describe('action_type_form', () => { // Verify that the tooltip renders // Use fake timers so we don't have to wait for the EuiToolTip timeout - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); wrapper.find('[data-test-subj="action-group-error-icon"]').first().simulate('mouseOver'); // Run the timers so the EuiTooltip will be visible jest.runAllTimers(); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/field_browser/components/field_name/field_name.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/field_browser/components/field_name/field_name.test.tsx index 8a0b52b082ea..1fafae26032c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/field_browser/components/field_name/field_name.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/field_browser/components/field_name/field_name.test.tsx @@ -18,7 +18,7 @@ const defaultProps = { describe('FieldName', () => { beforeEach(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); test('it renders the field name', () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_edit.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_edit.test.tsx index dfebee8e5d6f..58ca1fb0193c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_edit.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_edit.test.tsx @@ -232,7 +232,7 @@ describe('rule_edit', () => { it('should render an alert icon next to save button stating the potential change in permissions', async () => { // Use fake timers so we don't have to wait for the EuiToolTip timeout - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); await setup(); expect(wrapper.find('[data-test-subj="changeInPrivilegesTip"]').exists()).toBeTruthy(); @@ -241,7 +241,7 @@ describe('rule_edit', () => { }); // Run the timers so the EuiTooltip will be visible - jest.runAllTimers(); + jest.runOnlyPendingTimers(); wrapper.update(); expect(wrapper.find('.euiToolTipPopover').text()).toBe( diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.test.tsx index 6bfe953e2869..39613e456157 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.test.tsx @@ -1106,7 +1106,7 @@ describe('rules_list component with items', () => { it('renders table of rules', async () => { // Use fake timers so we don't have to wait for the EuiToolTip timeout - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); await setup(); expect(wrapper.find('EuiBasicTable')).toHaveLength(1); expect(wrapper.find('EuiTableRow')).toHaveLength(mockedRulesData.length); diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecation_logs/es_deprecation_logs.test.tsx b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecation_logs/es_deprecation_logs.test.tsx index f4b07c6f2f5a..6de4d8095d26 100644 --- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecation_logs/es_deprecation_logs.test.tsx +++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecation_logs/es_deprecation_logs.test.tsx @@ -361,7 +361,7 @@ describe('ES deprecation logs', () => { describe('Poll for logs count', () => { beforeEach(async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); // First request should make the step be complete httpRequestsMockHelpers.setLoadDeprecationLogsCountResponse({ diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecations/deprecations_list.test.ts b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecations/deprecations_list.test.ts index 457c0c4ec2be..3af99109510a 100644 --- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecations/deprecations_list.test.ts +++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecations/deprecations_list.test.ts @@ -103,9 +103,9 @@ describe('ES deprecations table', () => { (deprecation) => deprecation.isCritical === false ); - expect(find('criticalDeprecationsCount').text()).toContain(criticalDeprecations.length); + expect(find('criticalDeprecationsCount').text()).toContain(String(criticalDeprecations.length)); - expect(find('warningDeprecationsCount').text()).toContain(warningDeprecations.length); + expect(find('warningDeprecationsCount').text()).toContain(String(warningDeprecations.length)); }); describe('remote clusters callout', () => { diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecations/reindex_deprecation_flyout.test.ts b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecations/reindex_deprecation_flyout.test.ts index 845141fb7784..f181aef4f7c0 100644 --- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecations/reindex_deprecation_flyout.test.ts +++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecations/reindex_deprecation_flyout.test.ts @@ -27,7 +27,7 @@ describe('Reindex deprecation flyout', () => { let testBed: ElasticsearchTestBed; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/time_manipulation.ts b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/time_manipulation.ts index 65cec1954973..9789fa3a8f52 100644 --- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/time_manipulation.ts +++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/time_manipulation.ts @@ -8,7 +8,7 @@ import { act } from 'react-dom/test-utils'; /** - * These helpers are intended to be used in conjunction with jest.useFakeTimers(). + * These helpers are intended to be used in conjunction with jest.useFakeTimers('legacy'). */ const flushPromiseJobQueue = async () => { diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/kibana_deprecations/deprecations_table/deprecations_table.test.ts b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/kibana_deprecations/deprecations_table/deprecations_table.test.ts index 7a62fa33bb87..e4c85d47bc46 100644 --- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/kibana_deprecations/deprecations_table/deprecations_table.test.ts +++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/kibana_deprecations/deprecations_table/deprecations_table.test.ts @@ -69,10 +69,10 @@ describe('Kibana deprecations - Deprecations table', () => { const { find } = testBed; expect(find('criticalDeprecationsCount').text()).toContain( - mockedCriticalKibanaDeprecations.length + String(mockedCriticalKibanaDeprecations.length) ); expect(find('warningDeprecationsCount').text()).toContain( - mockedWarningKibanaDeprecations.length + String(mockedWarningKibanaDeprecations.length) ); }); diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/backup_step/backup_step.test.tsx b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/backup_step/backup_step.test.tsx index 688e060705ee..bc93c05fb7e7 100644 --- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/backup_step/backup_step.test.tsx +++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/backup_step/backup_step.test.tsx @@ -153,7 +153,7 @@ describe('Overview - Backup Step', () => { describe('poll for new status', () => { beforeEach(async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); // First request will succeed. httpRequestsMockHelpers.setLoadCloudBackupStatusResponse({ diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/fix_issues_step/kibana_deprecation_issues.test.tsx b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/fix_issues_step/kibana_deprecation_issues.test.tsx index 77c8a9e998ed..cead46f258c4 100644 --- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/fix_issues_step/kibana_deprecation_issues.test.tsx +++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/fix_issues_step/kibana_deprecation_issues.test.tsx @@ -57,8 +57,8 @@ describe('Overview - Fix deprecation issues step - Kibana deprecations', () => { const { exists, find } = testBed; expect(exists('kibanaStatsPanel')).toBe(true); - expect(find('kibanaStatsPanel.criticalDeprecations').text()).toContain(1); - expect(find('kibanaStatsPanel.warningDeprecations').text()).toContain(2); + expect(find('kibanaStatsPanel.criticalDeprecations').text()).toContain('1'); + expect(find('kibanaStatsPanel.warningDeprecations').text()).toContain('2'); }); test('panel links to Kibana deprecations page', () => { @@ -77,7 +77,7 @@ describe('Overview - Fix deprecation issues step - Kibana deprecations', () => { const { exists, find } = testBed; expect(exists('kibanaStatsPanel')).toBe(true); - expect(find('kibanaStatsPanel.criticalDeprecations').text()).toContain(1); + expect(find('kibanaStatsPanel.criticalDeprecations').text()).toContain('1'); expect(exists('kibanaStatsPanel.noWarningDeprecationIssues')).toBe(true); }); diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/migrate_system_indices/step_completion.test.ts b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/migrate_system_indices/step_completion.test.ts index cbece74355d6..9c69b4bb1847 100644 --- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/migrate_system_indices/step_completion.test.ts +++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/migrate_system_indices/step_completion.test.ts @@ -55,7 +55,7 @@ describe('Overview - Migrate system indices - Step completion', () => { describe('Poll for new status', () => { beforeEach(async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); // First request should make the step be incomplete httpRequestsMockHelpers.setLoadSystemIndicesMigrationStatus({ diff --git a/x-pack/plugins/watcher/__jest__/client_integration/watch_create_json_page.test.ts b/x-pack/plugins/watcher/__jest__/client_integration/watch_create_json_page.test.ts index 54352e278770..b7e80808cfb8 100644 --- a/x-pack/plugins/watcher/__jest__/client_integration/watch_create_json_page.test.ts +++ b/x-pack/plugins/watcher/__jest__/client_integration/watch_create_json_page.test.ts @@ -21,7 +21,7 @@ describe(' create route', () => { let testBed: WatchCreateJsonTestBed; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/watcher/__jest__/client_integration/watch_create_threshold_page.test.tsx b/x-pack/plugins/watcher/__jest__/client_integration/watch_create_threshold_page.test.tsx index 4fe5fb3445da..3395e8d95de7 100644 --- a/x-pack/plugins/watcher/__jest__/client_integration/watch_create_threshold_page.test.tsx +++ b/x-pack/plugins/watcher/__jest__/client_integration/watch_create_threshold_page.test.tsx @@ -84,7 +84,7 @@ describe(' create route', () => { let testBed: WatchCreateThresholdTestBed; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/watcher/__jest__/client_integration/watch_edit_page.test.ts b/x-pack/plugins/watcher/__jest__/client_integration/watch_edit_page.test.ts index f57dcb6788fe..f392806b854f 100644 --- a/x-pack/plugins/watcher/__jest__/client_integration/watch_edit_page.test.ts +++ b/x-pack/plugins/watcher/__jest__/client_integration/watch_edit_page.test.ts @@ -21,7 +21,7 @@ describe('', () => { let testBed: WatchEditTestBed; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/watcher/__jest__/client_integration/watch_list_page.test.ts b/x-pack/plugins/watcher/__jest__/client_integration/watch_list_page.test.ts index b85600277518..995b36350468 100644 --- a/x-pack/plugins/watcher/__jest__/client_integration/watch_list_page.test.ts +++ b/x-pack/plugins/watcher/__jest__/client_integration/watch_list_page.test.ts @@ -18,7 +18,7 @@ describe('', () => { let testBed: WatchListTestBed; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/watcher/__jest__/client_integration/watch_status_page.test.ts b/x-pack/plugins/watcher/__jest__/client_integration/watch_status_page.test.ts index 1d00b6c69972..3ae741563075 100644 --- a/x-pack/plugins/watcher/__jest__/client_integration/watch_status_page.test.ts +++ b/x-pack/plugins/watcher/__jest__/client_integration/watch_status_page.test.ts @@ -45,7 +45,7 @@ describe('', () => { let testBed: WatchStatusTestBed; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/test/alerting_api_integration/common/config.ts b/x-pack/test/alerting_api_integration/common/config.ts index 090e48f7a8a2..d2831b61799f 100644 --- a/x-pack/test/alerting_api_integration/common/config.ts +++ b/x-pack/test/alerting_api_integration/common/config.ts @@ -197,6 +197,18 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) ])}`, `--xpack.actions.preconfiguredAlertHistoryEsIndex=${preconfiguredAlertHistoryEsIndex}`, `--xpack.actions.preconfigured=${JSON.stringify({ + 'my-test-email': { + actionTypeId: '.email', + name: 'TestEmail#xyz', + config: { + from: 'me@test.com', + service: '__json', + }, + secrets: { + user: 'user', + password: 'password', + }, + }, 'my-slack1': { actionTypeId: '.slack', name: 'Slack#xyz', diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server/plugin.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server/plugin.ts index ae874d942e75..316d1916b4af 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server/plugin.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server/plugin.ts @@ -7,10 +7,13 @@ import http from 'http'; import https from 'https'; -import { Plugin, CoreSetup, IRouter } from '@kbn/core/server'; +import { Plugin, CoreSetup } from '@kbn/core/server'; import { EncryptedSavedObjectsPluginStart } from '@kbn/encrypted-saved-objects-plugin/server'; import { PluginSetupContract as FeaturesPluginSetup } from '@kbn/features-plugin/server'; -import { PluginSetupContract as ActionsPluginSetupContract } from '@kbn/actions-plugin/server/plugin'; +import { + PluginSetupContract as ActionsPluginSetupContract, + PluginStartContract as ActionsPluginStartContract, +} from '@kbn/actions-plugin/server/plugin'; import { ActionType } from '@kbn/actions-plugin/server'; import { initPlugin as initPagerduty } from './pagerduty_simulation'; import { initPlugin as initSwimlane } from './swimlane_simulation'; @@ -22,6 +25,7 @@ import { initPlugin as initSlack } from './slack_simulation'; import { initPlugin as initWebhook } from './webhook_simulation'; import { initPlugin as initMSExchange } from './ms_exchage_server_simulation'; import { initPlugin as initXmatters } from './xmatters_simulation'; +import { initPlugin as initUnsecuredAction } from './unsecured_actions_simulation'; export const NAME = 'actions-FTS-external-service-simulators'; @@ -82,8 +86,9 @@ interface FixtureSetupDeps { features: FeaturesPluginSetup; } -interface FixtureStartDeps { +export interface FixtureStartDeps { encryptedSavedObjects: EncryptedSavedObjectsPluginStart; + actions: ActionsPluginStartContract; } export class FixturePlugin implements Plugin { @@ -126,7 +131,7 @@ export class FixturePlugin implements Plugin) { + router.post( + { + path: `/api/sample_unsecured_action`, + validate: { + body: schema.object({ + requesterId: schema.string(), + id: schema.string(), + params: schema.recordOf(schema.string(), schema.any()), + }), + }, + }, + async function ( + context: RequestHandlerContext, + req: KibanaRequest, + res: KibanaResponseFactory + ): Promise> { + const [_, { actions }] = await coreSetup.getStartServices(); + const { body } = req; + + try { + const unsecuredActionsClient = actions.getUnsecuredActionsClient(); + const { requesterId, id, params } = body; + await unsecuredActionsClient.bulkEnqueueExecution(requesterId, [{ id, params }]); + + return res.ok({ body: { status: 'success' } }); + } catch (err) { + return res.ok({ body: { status: 'error', error: `${err}` } }); + } + } + ); +} diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/bulk_delete.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/bulk_delete.ts new file mode 100644 index 000000000000..91de3084993f --- /dev/null +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/bulk_delete.ts @@ -0,0 +1,563 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { UserAtSpaceScenarios, SuperuserAtSpace1 } from '../../../scenarios'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; +import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../../common/lib'; + +const defaultSuccessfulResponse = { errors: [], total: 1, taskIdsFailedToBeDeleted: [] }; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + const es = getService('es'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + + describe('bulkDelete', () => { + const objectRemover = new ObjectRemover(supertest); + + after(() => objectRemover.removeAll()); + + const getScheduledTask = async (id: string) => { + return await es.get({ + id: `task:${id}`, + index: '.kibana_task_manager', + }); + }; + + for (const scenario of UserAtSpaceScenarios) { + const { user, space } = scenario; + + describe(scenario.id, () => { + afterEach(() => objectRemover.removeAll()); + it('should handle bulk delete of one rule appropriately based on id', async () => { + const { body: createdRule1 } = await supertest + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestRuleData()) + .expect(200); + + const response = await supertestWithoutAuth + .patch(`${getUrlPrefix(space.id)}/internal/alerting/rules/_bulk_delete`) + .set('kbn-xsrf', 'foo') + .send({ ids: [createdRule1.id] }) + .auth(user.username, user.password); + + switch (scenario.id) { + case 'no_kibana_privileges at space1': + case 'space_1_all at space2': + expect(response.body).to.eql({ + error: 'Forbidden', + message: 'Unauthorized to find rules for any rule types', + statusCode: 403, + }); + expect(response.statusCode).to.eql(403); + objectRemover.add(space.id, createdRule1.id, 'rule', 'alerting'); + // Ensure task still exists + await getScheduledTask(createdRule1.scheduled_task_id); + break; + case 'global_read at space1': + expect(response.body).to.eql({ + error: 'Forbidden', + message: 'Unauthorized to bulkDelete a "test.noop" rule for "alertsFixture"', + statusCode: 403, + }); + expect(response.statusCode).to.eql(403); + objectRemover.add(space.id, createdRule1.id, 'rule', 'alerting'); + // Ensure task still exists + await getScheduledTask(createdRule1.scheduled_task_id); + break; + case 'space_1_all_alerts_none_actions at space1': + case 'superuser at space1': + case 'space_1_all at space1': + case 'space_1_all_with_restricted_fixture at space1': + expect(response.body).to.eql(defaultSuccessfulResponse); + expect(response.statusCode).to.eql(200); + try { + await getScheduledTask(createdRule1.scheduled_task_id); + throw new Error('Should have removed scheduled task'); + } catch (e) { + expect(e.meta.statusCode).to.eql(404); + } + break; + default: + throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); + } + }); + + it('should handle bulk delete of one rule appropriately based on id when consumer is the same as producer', async () => { + const { body: createdRule1 } = await supertest + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send( + getTestRuleData({ + rule_type_id: 'test.restricted-noop', + consumer: 'alertsRestrictedFixture', + }) + ) + .expect(200); + + const response = await supertestWithoutAuth + .patch(`${getUrlPrefix(space.id)}/internal/alerting/rules/_bulk_delete`) + .set('kbn-xsrf', 'foo') + .send({ ids: [createdRule1.id] }) + .auth(user.username, user.password); + + switch (scenario.id) { + case 'no_kibana_privileges at space1': + case 'space_1_all at space2': + expect(response.body).to.eql({ + error: 'Forbidden', + message: 'Unauthorized to find rules for any rule types', + statusCode: 403, + }); + expect(response.statusCode).to.eql(403); + objectRemover.add(space.id, createdRule1.id, 'rule', 'alerting'); + // Ensure task still exists + await getScheduledTask(createdRule1.scheduled_task_id); + break; + case 'global_read at space1': + expect(response.body).to.eql({ + error: 'Forbidden', + message: + 'Unauthorized to bulkDelete a "test.restricted-noop" rule for "alertsRestrictedFixture"', + statusCode: 403, + }); + expect(response.statusCode).to.eql(403); + objectRemover.add(space.id, createdRule1.id, 'rule', 'alerting'); + // Ensure task still exists + await getScheduledTask(createdRule1.scheduled_task_id); + break; + case 'space_1_all at space1': + case 'space_1_all_alerts_none_actions at space1': + expect(response.body).to.eql({ + statusCode: 400, + error: 'Bad Request', + message: 'No rules found for bulk delete', + }); + expect(response.statusCode).to.eql(400); + objectRemover.add(space.id, createdRule1.id, 'rule', 'alerting'); + // Ensure task still exists + await getScheduledTask(createdRule1.scheduled_task_id); + break; + case 'superuser at space1': + case 'space_1_all_with_restricted_fixture at space1': + expect(response.body).to.eql(defaultSuccessfulResponse); + expect(response.statusCode).to.eql(200); + try { + await getScheduledTask(createdRule1.scheduled_task_id); + throw new Error('Should have removed scheduled task'); + } catch (e) { + expect(e.meta.statusCode).to.eql(404); + } + break; + default: + throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); + } + }); + + it('should handle delete alert request appropriately when consumer is not the producer', async () => { + const { body: createdRule1 } = await supertest + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send( + getTestRuleData({ + rule_type_id: 'test.restricted-noop', + consumer: 'alertsFixture', + }) + ) + .expect(200); + + const response = await supertestWithoutAuth + .patch(`${getUrlPrefix(space.id)}/internal/alerting/rules/_bulk_delete`) + .set('kbn-xsrf', 'foo') + .send({ ids: [createdRule1.id] }) + .auth(user.username, user.password); + + switch (scenario.id) { + case 'no_kibana_privileges at space1': + case 'space_1_all at space2': + expect(response.body).to.eql({ + error: 'Forbidden', + message: 'Unauthorized to find rules for any rule types', + statusCode: 403, + }); + expect(response.statusCode).to.eql(403); + objectRemover.add(space.id, createdRule1.id, 'rule', 'alerting'); + // Ensure task still exists + await getScheduledTask(createdRule1.scheduled_task_id); + break; + case 'space_1_all at space1': + case 'space_1_all_alerts_none_actions at space1': + case 'space_1_all_with_restricted_fixture at space1': + case 'global_read at space1': + expect(response.body).to.eql({ + statusCode: 400, + error: 'Bad Request', + message: 'No rules found for bulk delete', + }); + expect(response.statusCode).to.eql(400); + objectRemover.add(space.id, createdRule1.id, 'rule', 'alerting'); + // Ensure task still exists + await getScheduledTask(createdRule1.scheduled_task_id); + break; + case 'superuser at space1': + expect(response.body).to.eql(defaultSuccessfulResponse); + expect(response.statusCode).to.eql(200); + try { + await getScheduledTask(createdRule1.scheduled_task_id); + throw new Error('Should have removed scheduled task'); + } catch (e) { + expect(e.meta.statusCode).to.eql(404); + } + break; + default: + throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); + } + }); + + it('should handle delete alert request appropriately when consumer is "alerts"', async () => { + const { body: createdRule1 } = await supertest + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send( + getTestRuleData({ + rule_type_id: 'test.noop', + consumer: 'alerts', + }) + ) + .expect(200); + + const response = await supertestWithoutAuth + .patch(`${getUrlPrefix(space.id)}/internal/alerting/rules/_bulk_delete`) + .set('kbn-xsrf', 'foo') + .send({ ids: [createdRule1.id] }) + .auth(user.username, user.password); + + switch (scenario.id) { + case 'no_kibana_privileges at space1': + case 'space_1_all at space2': + expect(response.body).to.eql({ + error: 'Forbidden', + message: 'Unauthorized to find rules for any rule types', + statusCode: 403, + }); + expect(response.statusCode).to.eql(403); + objectRemover.add(space.id, createdRule1.id, 'rule', 'alerting'); + // Ensure task still exists + await getScheduledTask(createdRule1.scheduled_task_id); + break; + case 'global_read at space1': + expect(response.body).to.eql({ + error: 'Forbidden', + message: 'Unauthorized to bulkDelete a "test.noop" rule by "alertsFixture"', + statusCode: 403, + }); + expect(response.statusCode).to.eql(403); + objectRemover.add(space.id, createdRule1.id, 'rule', 'alerting'); + // Ensure task still exists + await getScheduledTask(createdRule1.scheduled_task_id); + break; + case 'superuser at space1': + case 'space_1_all at space1': + case 'space_1_all_alerts_none_actions at space1': + case 'space_1_all_with_restricted_fixture at space1': + expect(response.body).to.eql(defaultSuccessfulResponse); + expect(response.statusCode).to.eql(200); + try { + await getScheduledTask(createdRule1.scheduled_task_id); + throw new Error('Should have removed scheduled task'); + } catch (e) { + expect(e.meta.statusCode).to.eql(404); + } + break; + default: + throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); + } + }); + + it('should handle bulk delete of several rules ids appropriately based on ids', async () => { + const rules = await Promise.all( + Array.from({ length: 3 }).map(() => + supertest + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestRuleData({ tags: ['multiple-rules-edit'] })) + .expect(200) + ) + ); + + const response = await supertestWithoutAuth + .patch(`${getUrlPrefix(space.id)}/internal/alerting/rules/_bulk_delete`) + .set('kbn-xsrf', 'foo') + .send({ ids: rules.map((rule) => rule.body.id) }) + .auth(user.username, user.password); + + switch (scenario.id) { + case 'no_kibana_privileges at space1': + case 'space_1_all at space2': + expect(response.body).to.eql({ + error: 'Forbidden', + message: 'Unauthorized to find rules for any rule types', + statusCode: 403, + }); + expect(response.statusCode).to.eql(403); + await Promise.all( + rules.map((rule) => { + objectRemover.add(space.id, rule.body.id, 'rule', 'alerting'); + return getScheduledTask(rule.body.scheduled_task_id); + }) + ); + break; + case 'global_read at space1': + expect(response.body).to.eql({ + error: 'Forbidden', + message: 'Unauthorized to bulkDelete a "test.noop" rule for "alertsFixture"', + statusCode: 403, + }); + expect(response.statusCode).to.eql(403); + await Promise.all( + rules.map((rule) => { + objectRemover.add(space.id, rule.body.id, 'rule', 'alerting'); + return getScheduledTask(rule.body.scheduled_task_id); + }) + ); + break; + case 'space_1_all_alerts_none_actions at space1': + case 'superuser at space1': + case 'space_1_all at space1': + case 'space_1_all_with_restricted_fixture at space1': + expect(response.body).to.eql({ ...defaultSuccessfulResponse, total: 3 }); + expect(response.statusCode).to.eql(200); + for (const rule of rules) { + try { + await getScheduledTask(rule.body.scheduled_task_id); + throw new Error('Should have removed scheduled task'); + } catch (e) { + expect(e.meta.statusCode).to.eql(404); + } + } + break; + default: + throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); + } + }); + + it('should handle bulk delete of several rules ids appropriately based on filter', async () => { + const rules = await Promise.all( + Array.from({ length: 3 }).map(() => + supertest + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestRuleData({ tags: ['multiple-rules-delete'] })) + .expect(200) + ) + ); + + const response = await supertestWithoutAuth + .patch(`${getUrlPrefix(space.id)}/internal/alerting/rules/_bulk_delete`) + .set('kbn-xsrf', 'foo') + .send({ filter: `alert.attributes.tags: "multiple-rules-delete"` }) + .auth(user.username, user.password); + + switch (scenario.id) { + case 'no_kibana_privileges at space1': + case 'space_1_all at space2': + expect(response.body).to.eql({ + error: 'Forbidden', + message: 'Unauthorized to find rules for any rule types', + statusCode: 403, + }); + expect(response.statusCode).to.eql(403); + await Promise.all( + rules.map((rule) => { + objectRemover.add(space.id, rule.body.id, 'rule', 'alerting'); + return getScheduledTask(rule.body.scheduled_task_id); + }) + ); + break; + case 'global_read at space1': + expect(response.body).to.eql({ + error: 'Forbidden', + message: 'Unauthorized to bulkDelete a "test.noop" rule for "alertsFixture"', + statusCode: 403, + }); + expect(response.statusCode).to.eql(403); + await Promise.all( + rules.map((rule) => { + objectRemover.add(space.id, rule.body.id, 'rule', 'alerting'); + return getScheduledTask(rule.body.scheduled_task_id); + }) + ); + break; + case 'space_1_all_alerts_none_actions at space1': + case 'superuser at space1': + case 'space_1_all at space1': + case 'space_1_all_with_restricted_fixture at space1': + expect(response.body).to.eql({ ...defaultSuccessfulResponse, total: 3 }); + expect(response.statusCode).to.eql(200); + for (const rule of rules) { + try { + await getScheduledTask(rule.body.scheduled_task_id); + throw new Error('Should have removed scheduled task'); + } catch (e) { + expect(e.meta.statusCode).to.eql(404); + } + } + break; + default: + throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); + } + }); + + it('should not delete rule from another space', async () => { + const { body: createdRule } = await supertest + .post(`${getUrlPrefix('other')}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestRuleData()) + .expect(200); + + const response = await supertestWithoutAuth + .patch(`${getUrlPrefix('other')}/internal/alerting/rules/_bulk_delete`) + .set('kbn-xsrf', 'foo') + .auth(user.username, user.password) + .send({ ids: [createdRule.id] }); + + switch (scenario.id) { + // This superuser has more privileges that we think + case 'superuser at space1': + expect(response.body).to.eql(defaultSuccessfulResponse); + expect(response.statusCode).to.eql(200); + try { + await getScheduledTask(createdRule.scheduled_task_id); + throw new Error('Should have removed scheduled task'); + } catch (e) { + expect(e.meta.statusCode).to.eql(404); + } + break; + case 'global_read at space1': + expect(response.body).to.eql({ + error: 'Forbidden', + message: 'Unauthorized to bulkDelete a "test.noop" rule for "alertsFixture"', + statusCode: 403, + }); + expect(response.statusCode).to.eql(403); + objectRemover.add('other', createdRule.id, 'rule', 'alerting'); + await getScheduledTask(createdRule.scheduled_task_id); + break; + case 'no_kibana_privileges at space1': + case 'space_1_all at space2': + case 'space_1_all at space1': + case 'space_1_all_alerts_none_actions at space1': + case 'space_1_all_with_restricted_fixture at space1': + expect(response.body).to.eql({ + error: 'Forbidden', + message: 'Unauthorized to find rules for any rule types', + statusCode: 403, + }); + expect(response.statusCode).to.eql(403); + expect(response.statusCode).to.eql(403); + objectRemover.add('other', createdRule.id, 'rule', 'alerting'); + await getScheduledTask(createdRule.scheduled_task_id); + break; + default: + throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); + } + }); + }); + } + + describe('Validation tests', () => { + const { user, space } = SuperuserAtSpace1; + it('should throw an error when bulk delete of rules when both ids and filter supplied in payload', async () => { + const { body: createdRule1 } = await supertest + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestRuleData({ tags: ['foo'] })) + .expect(200); + + const response = await supertestWithoutAuth + .patch(`${getUrlPrefix(space.id)}/internal/alerting/rules/_bulk_delete`) + .set('kbn-xsrf', 'foo') + .send({ filter: 'fake_filter', ids: [createdRule1.id] }) + .auth(user.username, user.password); + + expect(response.statusCode).to.eql(400); + expect(response.body.message).to.eql( + "Both 'filter' and 'ids' are supplied. Define either 'ids' or 'filter' properties in method's arguments" + ); + objectRemover.add(space.id, createdRule1.id, 'rule', 'alerting'); + await getScheduledTask(createdRule1.scheduled_task_id); + }); + + it('should return an error if we pass more than 1000 ids', async () => { + const ids = [...Array(1001)].map((_, i) => `rule${i}`); + + const response = await supertestWithoutAuth + .patch(`${getUrlPrefix(space.id)}/internal/alerting/rules/_bulk_delete`) + .set('kbn-xsrf', 'foo') + .send({ ids }) + .auth(user.username, user.password); + + expect(response.body).to.eql({ + error: 'Bad Request', + message: '[request body.ids]: array size is [1001], but cannot be greater than [1000]', + statusCode: 400, + }); + }); + + it('should return an error if we do not pass any arguments', async () => { + await supertest + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestRuleData()) + .expect(200); + + const response = await supertestWithoutAuth + .patch(`${getUrlPrefix(space.id)}/internal/alerting/rules/_bulk_delete`) + .set('kbn-xsrf', 'foo') + .send({}) + .auth(user.username, user.password); + + expect(response.body).to.eql({ + error: 'Bad Request', + message: "Either 'ids' or 'filter' property in method's arguments should be provided", + statusCode: 400, + }); + }); + + it('should return an error if we pass empty ids array', async () => { + const response = await supertestWithoutAuth + .patch(`${getUrlPrefix(space.id)}/internal/alerting/rules/_bulk_delete`) + .set('kbn-xsrf', 'foo') + .send({ ids: [] }) + .auth(user.username, user.password); + + expect(response.body).to.eql({ + error: 'Bad Request', + message: '[request body.ids]: array size is [0], but cannot be smaller than [1]', + statusCode: 400, + }); + }); + + it('should return an error if we pass empty string instead of fiter', async () => { + const response = await supertestWithoutAuth + .patch(`${getUrlPrefix(space.id)}/internal/alerting/rules/_bulk_delete`) + .set('kbn-xsrf', 'foo') + .send({ filter: '' }) + .auth(user.username, user.password); + + expect(response.body).to.eql({ + error: 'Bad Request', + message: "Either 'ids' or 'filter' property in method's arguments should be provided", + statusCode: 400, + }); + }); + }); + }); +}; diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/index.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/index.ts index 53b7e8e3fb2c..6265cb7d34ff 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/index.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/index.ts @@ -31,6 +31,7 @@ export default function alertingTests({ loadTestFile, getService }: FtrProviderC loadTestFile(require.resolve('./get_alert_summary')); loadTestFile(require.resolve('./rule_types')); loadTestFile(require.resolve('./bulk_edit')); + loadTestFile(require.resolve('./bulk_delete')); loadTestFile(require.resolve('./retain_api_key')); }); }); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/get_all.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/get_all.ts index 69f618c804eb..d4bfe3cbdd70 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/get_all.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/get_all.ts @@ -127,6 +127,14 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { name: 'Test:_Preconfigured_Index_Record', referenced_by_count: 0, }, + { + id: 'my-test-email', + is_preconfigured: true, + is_deprecated: false, + connector_type_id: '.email', + name: 'TestEmail#xyz', + referenced_by_count: 0, + }, ]); break; default: @@ -262,6 +270,14 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { name: 'Test:_Preconfigured_Index_Record', referenced_by_count: 0, }, + { + id: 'my-test-email', + is_preconfigured: true, + is_deprecated: false, + connector_type_id: '.email', + name: 'TestEmail#xyz', + referenced_by_count: 0, + }, ]); break; default: @@ -361,6 +377,14 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { name: 'Test:_Preconfigured_Index_Record', referenced_by_count: 0, }, + { + id: 'my-test-email', + is_preconfigured: true, + is_deprecated: false, + connector_type_id: '.email', + name: 'TestEmail#xyz', + referenced_by_count: 0, + }, ]); break; default: diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/telemetry/alerting_and_actions_telemetry.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/telemetry/alerting_and_actions_telemetry.ts index b4cb36ab59d8..707f15534e66 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/telemetry/alerting_and_actions_telemetry.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/telemetry/alerting_and_actions_telemetry.ts @@ -571,7 +571,7 @@ export default function createAlertingAndActionsTelemetryTests({ getService }: F expect(taskState).not.to.be(undefined); actionsTelemetry = JSON.parse(taskState!); expect(actionsTelemetry.runs).to.equal(2); - expect(actionsTelemetry.count_total).to.equal(19); + expect(actionsTelemetry.count_total).to.equal(20); }); // request alerting telemetry task to run diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/scenarios.ts b/x-pack/test/alerting_api_integration/security_and_spaces/scenarios.ts index ae8d52ecd531..91d809bff04c 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/scenarios.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/scenarios.ts @@ -196,7 +196,7 @@ const NoKibanaPrivilegesAtSpace1: NoKibanaPrivilegesAtSpace1 = { interface SuperuserAtSpace1 extends Scenario { id: 'superuser at space1'; } -const SuperuserAtSpace1: SuperuserAtSpace1 = { +export const SuperuserAtSpace1: SuperuserAtSpace1 = { id: 'superuser at space1', user: Superuser, space: Space1, diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/get_all.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/get_all.ts index 0632f48ed6e8..7846c9512867 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/get_all.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/get_all.ts @@ -115,6 +115,14 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { name: 'Test:_Preconfigured_Index_Record', referenced_by_count: 0, }, + { + id: 'my-test-email', + is_preconfigured: true, + is_deprecated: false, + connector_type_id: '.email', + name: 'TestEmail#xyz', + referenced_by_count: 0, + }, ]); }); @@ -202,6 +210,14 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { name: 'Test:_Preconfigured_Index_Record', referenced_by_count: 0, }, + { + id: 'my-test-email', + is_preconfigured: true, + is_deprecated: false, + connector_type_id: '.email', + name: 'TestEmail#xyz', + referenced_by_count: 0, + }, ]); }); @@ -302,6 +318,14 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { name: 'Test:_Preconfigured_Index_Record', referencedByCount: 0, }, + { + id: 'my-test-email', + isPreconfigured: true, + isDeprecated: false, + actionTypeId: '.email', + name: 'TestEmail#xyz', + referencedByCount: 0, + }, ]); }); }); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/index.ts index 866f13ed5294..b4dbb42e8f99 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/index.ts @@ -28,6 +28,7 @@ export default function actionsTests({ loadTestFile, getService }: FtrProviderCo loadTestFile(require.resolve('./connector_types/stack/webhook')); loadTestFile(require.resolve('./connector_types/stack/preconfigured_alert_history_connector')); loadTestFile(require.resolve('./type_not_enabled')); + loadTestFile(require.resolve('./schedule_unsecured_action')); // note that this test will destroy existing spaces loadTestFile(require.resolve('./migrations')); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/schedule_unsecured_action.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/schedule_unsecured_action.ts new file mode 100644 index 000000000000..9a5719b7fa70 --- /dev/null +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/schedule_unsecured_action.ts @@ -0,0 +1,191 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import type { SearchTotalHits } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { Spaces } from '../../scenarios'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; +import { getUrlPrefix, ObjectRemover } from '../../../common/lib'; + +// eslint-disable-next-line import/no-default-export +export default function createUnsecuredActionTests({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const kibanaServer = getService('kibanaServer'); + const es = getService('es'); + const retry = getService('retry'); + + describe('schedule unsecured action', () => { + const objectRemover = new ObjectRemover(supertest); + + // need to wait for kibanaServer to settle ... + before(() => { + kibanaServer.resolveUrl(`/api/sample_unsecured_action`); + }); + + after(() => objectRemover.removeAll()); + + it('should successfully schedule email action', async () => { + const testStart = new Date().toISOString(); + const { body: result } = await supertest + .post(`/api/sample_unsecured_action`) + .set('kbn-xsrf', 'xxx') + .send({ + requesterId: 'functional_tester', + id: 'my-test-email', + params: { + to: ['you@test.com'], + subject: 'hello from Kibana!', + message: 'does this work??', + }, + }) + .expect(200); + expect(result.status).to.eql('success'); + + await retry.try(async () => { + const searchResult = await es.search({ + index: '.kibana-event-log*', + body: { + query: { + bool: { + filter: [ + { + term: { + 'event.provider': { + value: 'actions', + }, + }, + }, + { + term: { + 'event.action': 'execute', + }, + }, + { + range: { + '@timestamp': { + gte: testStart, + }, + }, + }, + { + nested: { + path: 'kibana.saved_objects', + query: { + bool: { + filter: [ + { + term: { + 'kibana.saved_objects.id': { + value: 'my-test-email', + }, + }, + }, + { + term: { + 'kibana.saved_objects.type': 'action', + }, + }, + ], + }, + }, + }, + }, + ], + }, + }, + }, + }); + expect((searchResult.hits.total as SearchTotalHits).value).to.eql(1); + + const hit = searchResult.hits.hits[0]; + // @ts-expect-error _source: unknown + expect(hit?._source?.event?.outcome).to.eql('success'); + // @ts-expect-error _source: unknown + expect(hit?._source?.message).to.eql( + `action executed: .email:my-test-email: TestEmail#xyz` + ); + }); + }); + + it('should not allow scheduling email action from unallowed requester', async () => { + const { body: result } = await supertest + .post(`/api/sample_unsecured_action`) + .set('kbn-xsrf', 'xxx') + .send({ + requesterId: 'not_allowed', + id: 'my-test-email', + params: { + to: ['you@test.com'], + subject: 'hello from Kibana!', + message: 'does this work??', + }, + }) + .expect(200); + expect(result.status).to.eql('error'); + expect(result.error).to.eql( + `Error: "not_allowed" feature is not allow-listed for UnsecuredActionsClient access.` + ); + }); + + it('should not allow scheduling action from unallowed connector types', async () => { + const { body: result } = await supertest + .post(`/api/sample_unsecured_action`) + .set('kbn-xsrf', 'xxx') + .send({ + requesterId: 'functional_tester', + id: 'my-slack1', + params: { + message: 'does this work??', + }, + }) + .expect(200); + expect(result.status).to.eql('error'); + expect(result.error).to.eql( + `Error: .slack actions cannot be scheduled for unsecured actions execution` + ); + }); + + it('should not allow scheduling action from non preconfigured connectors', async () => { + const response = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'My email action', + connector_type_id: '.email', + config: { + from: 'me@test.com', + service: '__json', + }, + secrets: { + user: 'user', + password: 'password', + }, + }); + expect(response.status).to.eql(200); + + const connectorId = response.body.id; + objectRemover.add(Spaces.space1.id, connectorId, 'action', 'actions'); + const { body: result } = await supertest + .post(`/api/sample_unsecured_action`) + .set('kbn-xsrf', 'xxx') + .send({ + requesterId: 'functional_tester', + id: connectorId, + params: { + to: ['you@test.com'], + subject: 'hello from Kibana!', + message: 'does this work??', + }, + }) + .expect(200); + expect(result.status).to.eql('error'); + expect(result.error).to.eql( + `Error: ${connectorId} are not preconfigured connectors and can't be scheduled for unsecured actions execution` + ); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/common/config.ts b/x-pack/test/apm_api_integration/common/config.ts index 78fb376fa89b..8b98fc9422c0 100644 --- a/x-pack/test/apm_api_integration/common/config.ts +++ b/x-pack/test/apm_api_integration/common/config.ts @@ -107,9 +107,9 @@ export function createTestConfig(config: ApmFtrConfig) { kibanaServer, username: ApmUsername.apmManageOwnAndCreateAgentKeys, }), - monitorIndicesUser: await getApmApiClient({ + monitorClusterAndIndicesUser: await getApmApiClient({ kibanaServer, - username: ApmUsername.apmMonitorIndices, + username: ApmUsername.apmMonitorClusterAndIndices, }), }; }, diff --git a/x-pack/test/apm_api_integration/tests/metrics_charts/metrics_charts.spec.ts b/x-pack/test/apm_api_integration/tests/metrics/metrics_charts.spec.ts similarity index 100% rename from x-pack/test/apm_api_integration/tests/metrics_charts/metrics_charts.spec.ts rename to x-pack/test/apm_api_integration/tests/metrics/metrics_charts.spec.ts diff --git a/x-pack/test/apm_api_integration/tests/metrics/serverless/generate_data.ts b/x-pack/test/apm_api_integration/tests/metrics/serverless/generate_data.ts new file mode 100644 index 000000000000..07648d4e548a --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/metrics/serverless/generate_data.ts @@ -0,0 +1,120 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { apm, timerange } from '@kbn/apm-synthtrace'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; + +export const config = { + memoryTotal: 536870912, // 0.5gb + memoryFree: 94371840, // ~0.08 gb + billedDurationMs: 4000, + faasTimeoutMs: 10000, + coldStartDurationPython: 4000, + faasDuration: 4000, + transactionDuration: 1000, + pythonServerlessFunctionNames: ['fn-lambda-python', 'fn-lambda-python-2'], + pythonInstanceName: 'instance_A', + serverlessId: 'arn:aws:lambda:us-west-2:001:function:', +}; + +export const expectedValues = { + expectedMemoryUsedRate: (config.memoryTotal - config.memoryFree) / config.memoryTotal, + expectedMemoryUsed: config.memoryTotal - config.memoryFree, +}; + +export async function generateData({ + synthtraceEsClient, + start, + end, +}: { + synthtraceEsClient: ApmSynthtraceEsClient; + start: number; + end: number; +}) { + const { + memoryTotal, + memoryFree, + billedDurationMs, + faasTimeoutMs, + coldStartDurationPython, + faasDuration, + transactionDuration, + pythonServerlessFunctionNames, + pythonInstanceName, + } = config; + + const cloudFields = { + 'cloud.provider': 'aws', + 'cloud.service.name': 'lambda', + 'cloud.region': 'us-west-2', + }; + + const [instanceLambdaPython, instanceLambdaPython2] = pythonServerlessFunctionNames.map( + (functionName) => { + return apm + .serverlessFunction({ + serviceName: 'lambda-python', + environment: 'test', + agentName: 'python', + functionName, + }) + .instance({ instanceName: pythonInstanceName, ...cloudFields }); + } + ); + + const instanceLambdaNode = apm + .serverlessFunction({ + serviceName: 'lambda-node', + environment: 'test', + agentName: 'nodejs', + functionName: 'fn-lambda-node', + }) + .instance({ instanceName: 'instance_B', ...cloudFields }); + + const systemMemory = { + free: memoryFree, + total: memoryTotal, + }; + + const transactionsEvents = timerange(start, end) + .ratePerMinute(1) + .generator((timestamp) => [ + instanceLambdaPython + .invocation() + .billedDuration(billedDurationMs) + .coldStart(true) + .coldStartDuration(coldStartDurationPython) + .faasDuration(faasDuration) + .faasTimeout(faasTimeoutMs) + .memory(systemMemory) + .timestamp(timestamp) + .duration(transactionDuration) + .success(), + instanceLambdaPython2 + .invocation() + .billedDuration(billedDurationMs) + .coldStart(true) + .coldStartDuration(coldStartDurationPython) + .faasDuration(faasDuration) + .faasTimeout(faasTimeoutMs) + .memory(systemMemory) + .timestamp(timestamp) + .duration(transactionDuration) + .success(), + instanceLambdaNode + .invocation() + .billedDuration(billedDurationMs) + .coldStart(false) + .faasDuration(faasDuration) + .faasTimeout(faasTimeoutMs) + .memory(systemMemory) + .timestamp(timestamp) + .duration(transactionDuration) + .success(), + ]); + + await synthtraceEsClient.index(transactionsEvents); +} diff --git a/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_active_instances.spec.ts b/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_active_instances.spec.ts new file mode 100644 index 000000000000..4861313c377f --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_active_instances.spec.ts @@ -0,0 +1,121 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; +import expect from '@kbn/expect'; +import { sumBy } from 'lodash'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { config, expectedValues, generateData } from './generate_data'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const registry = getService('registry'); + const apmApiClient = getService('apmApiClient'); + const synthtraceEsClient = getService('synthtraceEsClient'); + + const start = new Date('2021-01-01T00:00:00.000Z').getTime(); + const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; + const numberOfTransactionsCreated = 15; + + async function callApi(serviceName: string, serverlessId?: string) { + return await apmApiClient.readUser({ + endpoint: `GET /internal/apm/services/{serviceName}/metrics/serverless/active_instances`, + params: { + path: { serviceName }, + query: { + environment: 'test', + kuery: '', + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + ...(serverlessId ? { serverlessId } : {}), + }, + }, + }); + } + + registry.when('Serverless active instances', { config: 'basic', archives: [] }, () => { + const { + memoryTotal, + billedDurationMs, + pythonServerlessFunctionNames, + faasDuration, + serverlessId, + } = config; + + const { expectedMemoryUsed } = expectedValues; + + before(async () => { + await generateData({ start, end, synthtraceEsClient }); + }); + + after(() => synthtraceEsClient.clean()); + + describe('Python service', () => { + let activeInstances: APIReturnType<'GET /internal/apm/services/{serviceName}/metrics/serverless/active_instances'>; + before(async () => { + const response = await callApi('lambda-python'); + activeInstances = response.body; + }); + + it('returns correct values for all serverless functions', () => { + pythonServerlessFunctionNames.forEach((name) => { + const activeInstanceOverview = activeInstances.activeInstances.find( + (item) => item.serverlessFunctionName === name + ); + + expect(activeInstanceOverview?.serverlessId).to.eql(`${serverlessId}${name}`); + expect(activeInstanceOverview?.serverlessDurationAvg).to.eql(faasDuration); + expect(activeInstanceOverview?.billedDurationAvg).to.eql(billedDurationMs); + expect(activeInstanceOverview?.avgMemoryUsed).to.eql(expectedMemoryUsed); + expect(activeInstanceOverview?.memorySize).to.eql(memoryTotal); + }); + }); + describe('timeseries', () => { + it('returns correct sum value', () => { + const sumValue = sumBy( + activeInstances?.timeseries?.filter((item) => item.y !== 0), + 'y' + ); + expect(sumValue).to.equal(numberOfTransactionsCreated); + }); + }); + }); + + describe('detailed metrics', () => { + let activeInstances: APIReturnType<'GET /internal/apm/services/{serviceName}/metrics/serverless/active_instances'>; + before(async () => { + const response = await callApi( + 'lambda-python', + `${serverlessId}${pythonServerlessFunctionNames[0]}` + ); + activeInstances = response.body; + }); + + it('returns correct values for all serverless functions', () => { + const activeInstanceOverview = activeInstances.activeInstances.find( + (item) => item.serverlessFunctionName === pythonServerlessFunctionNames[0] + ); + + expect(activeInstanceOverview?.serverlessId).to.eql( + `${serverlessId}${pythonServerlessFunctionNames[0]}` + ); + expect(activeInstanceOverview?.serverlessDurationAvg).to.eql(faasDuration); + expect(activeInstanceOverview?.billedDurationAvg).to.eql(billedDurationMs); + expect(activeInstanceOverview?.avgMemoryUsed).to.eql(expectedMemoryUsed); + expect(activeInstanceOverview?.memorySize).to.eql(memoryTotal); + }); + describe('timeseries', () => { + it('returns correct sum value', () => { + const sumValue = sumBy( + activeInstances?.timeseries?.filter((item) => item.y !== 0), + 'y' + ); + expect(sumValue).to.equal(numberOfTransactionsCreated); + }); + }); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_functions_overview.spec.ts b/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_functions_overview.spec.ts new file mode 100644 index 000000000000..e58df3291f49 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_functions_overview.spec.ts @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { config, expectedValues, generateData } from './generate_data'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const registry = getService('registry'); + const apmApiClient = getService('apmApiClient'); + const synthtraceEsClient = getService('synthtraceEsClient'); + + const start = new Date('2021-01-01T00:00:00.000Z').getTime(); + const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; + const numberOfTransactionsCreated = 15; + + async function callApi(serviceName: string) { + return await apmApiClient.readUser({ + endpoint: `GET /internal/apm/services/{serviceName}/metrics/serverless/functions_overview`, + params: { + path: { serviceName }, + query: { + environment: 'test', + kuery: '', + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + }, + }, + }); + } + + registry.when('Serverless functions overview', { config: 'basic', archives: [] }, () => { + const { + memoryTotal, + billedDurationMs, + pythonServerlessFunctionNames, + faasDuration, + serverlessId, + } = config; + const { expectedMemoryUsed } = expectedValues; + + before(async () => { + await generateData({ start, end, synthtraceEsClient }); + }); + + after(() => synthtraceEsClient.clean()); + + describe('Python service', () => { + let functionsOverview: APIReturnType<'GET /internal/apm/services/{serviceName}/metrics/serverless/functions_overview'>; + before(async () => { + const response = await callApi('lambda-python'); + functionsOverview = response.body; + }); + it('returns correct number of serverless functions', () => { + expect( + functionsOverview.serverlessFunctionsOverview.map((item) => { + return item.serverlessFunctionName; + }) + ).to.eql(pythonServerlessFunctionNames); + }); + it('returns correct values for all serverless functions', () => { + pythonServerlessFunctionNames.forEach((name) => { + const functionOverview = functionsOverview.serverlessFunctionsOverview.find( + (item) => item.serverlessFunctionName === name + ); + + expect(functionOverview?.serverlessId).to.eql(`${serverlessId}${name}`); + expect(functionOverview?.serverlessDurationAvg).to.eql(faasDuration); + expect(functionOverview?.billedDurationAvg).to.eql(billedDurationMs); + expect(functionOverview?.coldStartCount).to.eql(numberOfTransactionsCreated); + expect(functionOverview?.avgMemoryUsed).to.eql(expectedMemoryUsed); + expect(functionOverview?.memorySize).to.eql(memoryTotal); + }); + }); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_metrics_charts.spec.ts b/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_metrics_charts.spec.ts new file mode 100644 index 000000000000..f660e218eba1 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_metrics_charts.spec.ts @@ -0,0 +1,322 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { meanBy, sumBy } from 'lodash'; +import { Coordinate } from '@kbn/apm-plugin/typings/timeseries'; +import { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { generateData, config } from './generate_data'; + +function isNotNullOrZeroCoordinate(coordinate: Coordinate) { + return coordinate.y !== null && coordinate.y !== 0; +} + +export default function ApiTest({ getService }: FtrProviderContext) { + const registry = getService('registry'); + const apmApiClient = getService('apmApiClient'); + const synthtraceEsClient = getService('synthtraceEsClient'); + + const start = new Date('2021-01-01T00:00:00.000Z').getTime(); + const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; + const numberOfTransactionsCreated = 15; + + async function callApi(serviceName: string, serverlessId?: string) { + return await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/services/{serviceName}/metrics/serverless/charts', + params: { + path: { serviceName }, + query: { + environment: 'test', + kuery: '', + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + ...(serverlessId ? { serverlessId } : {}), + }, + }, + }); + } + + registry.when( + 'Serverless metrics charts when data is not loaded', + { config: 'basic', archives: [] }, + () => { + let serverlessMetrics: APIReturnType<'GET /internal/apm/services/{serviceName}/metrics/serverless/charts'>; + before(async () => { + const response = await callApi('lambda-python'); + serverlessMetrics = response.body; + }); + + it('returns empty', () => { + serverlessMetrics.charts.forEach((chart) => { + expect(chart.series).to.be.empty(); + }); + }); + } + ); + + registry.when('Serverless metrics charts', { config: 'basic', archives: [] }, () => { + const { + memoryTotal, + memoryFree, + billedDurationMs, + coldStartDurationPython, + transactionDuration, + pythonServerlessFunctionNames, + serverlessId, + } = config; + + before(async () => { + await generateData({ start, end, synthtraceEsClient }); + }); + + after(() => synthtraceEsClient.clean()); + + describe('Python service', () => { + let serverlessMetrics: APIReturnType<'GET /internal/apm/services/{serviceName}/metrics/serverless/charts'>; + before(async () => { + const response = await callApi('lambda-python'); + serverlessMetrics = response.body; + }); + + it('returns all metrics chart', () => { + expect(serverlessMetrics.charts.length).to.be.greaterThan(0); + expect(serverlessMetrics.charts.map(({ title }) => title).sort()).to.eql([ + 'Cold start duration', + 'Cold starts', + 'Compute usage', + 'Lambda Duration', + 'System memory usage', + ]); + }); + + describe('Avg. Duration', () => { + const transactionDurationInMicroSeconds = transactionDuration * 1000; + [ + { title: 'Billed Duration', expectedValue: billedDurationMs * 1000 }, + { title: 'Transaction Duration', expectedValue: transactionDurationInMicroSeconds }, + ].map(({ title, expectedValue }) => + it(`returns correct ${title} value`, () => { + const avgDurationMetric = serverlessMetrics.charts.find((chart) => { + return chart.key === 'avg_duration'; + }); + const series = avgDurationMetric?.series.find((item) => item.title === title); + expect(series?.overallValue).to.eql(expectedValue); + const meanValue = meanBy(series?.data.filter(isNotNullOrZeroCoordinate), 'y'); + expect(meanValue).to.eql(expectedValue); + }) + ); + }); + + let metricsChart: typeof serverlessMetrics.charts[0] | undefined; + + describe('Cold start duration', () => { + before(() => { + metricsChart = serverlessMetrics.charts.find((chart) => { + return chart.key === 'cold_start_duration'; + }); + }); + it('returns correct overall value', () => { + expect(metricsChart?.series[0].overallValue).to.equal(coldStartDurationPython * 1000); + }); + + it('returns correct mean value', () => { + const meanValue = meanBy( + metricsChart?.series[0]?.data.filter(isNotNullOrZeroCoordinate), + 'y' + ); + expect(meanValue).to.equal(coldStartDurationPython * 1000); + }); + }); + + describe('Cold start count', () => { + before(() => { + metricsChart = serverlessMetrics.charts.find((chart) => { + return chart.key === 'cold_start_count'; + }); + }); + + it('returns correct overall value', () => { + expect(metricsChart?.series[0].overallValue).to.equal( + numberOfTransactionsCreated * pythonServerlessFunctionNames.length + ); + }); + + it('returns correct sum value', () => { + const sumValue = sumBy( + metricsChart?.series[0]?.data.filter(isNotNullOrZeroCoordinate), + 'y' + ); + expect(sumValue).to.equal( + numberOfTransactionsCreated * pythonServerlessFunctionNames.length + ); + }); + }); + + describe('memory usage', () => { + const expectedFreeMemory = 1 - memoryFree / memoryTotal; + [ + { title: 'Max', expectedValue: expectedFreeMemory }, + { title: 'Average', expectedValue: expectedFreeMemory }, + ].map(({ title, expectedValue }) => + it(`returns correct ${title} value`, () => { + const memoryUsageMetric = serverlessMetrics.charts.find((chart) => { + return chart.key === 'memory_usage_chart'; + }); + const series = memoryUsageMetric?.series.find((item) => item.title === title); + expect(series?.overallValue).to.eql(expectedValue); + const meanValue = meanBy(series?.data.filter(isNotNullOrZeroCoordinate), 'y'); + expect(meanValue).to.eql(expectedValue); + }) + ); + }); + + describe('Compute usage', () => { + const GBSeconds = 1024 * 1024 * 1024 * 1000; + const expectedValue = (memoryTotal * billedDurationMs) / GBSeconds; + let computeUsageMetric: typeof serverlessMetrics.charts[0] | undefined; + before(() => { + computeUsageMetric = serverlessMetrics.charts.find((chart) => { + return chart.key === 'compute_usage'; + }); + }); + it('returns correct overall value', () => { + expect(computeUsageMetric?.series[0].overallValue).to.equal(expectedValue); + }); + + it('returns correct mean value', () => { + const meanValue = meanBy( + computeUsageMetric?.series[0]?.data.filter((item) => item.y !== 0), + 'y' + ); + expect(meanValue).to.equal(expectedValue); + }); + }); + }); + + describe('detailed metrics', () => { + let serverlessMetrics: APIReturnType<'GET /internal/apm/services/{serviceName}/metrics/serverless/charts'>; + before(async () => { + const response = await callApi( + 'lambda-python', + `${serverlessId}${pythonServerlessFunctionNames[0]}` + ); + serverlessMetrics = response.body; + }); + + it('returns all metrics chart', () => { + expect(serverlessMetrics.charts.length).to.be.greaterThan(0); + expect(serverlessMetrics.charts.map(({ title }) => title).sort()).to.eql([ + 'Cold start duration', + 'Cold starts', + 'Compute usage', + 'Lambda Duration', + 'System memory usage', + ]); + }); + + describe('Avg. Duration', () => { + const transactionDurationInMicroSeconds = transactionDuration * 1000; + [ + { title: 'Billed Duration', expectedValue: billedDurationMs * 1000 }, + { title: 'Transaction Duration', expectedValue: transactionDurationInMicroSeconds }, + ].map(({ title, expectedValue }) => + it(`returns correct ${title} value`, () => { + const avgDurationMetric = serverlessMetrics.charts.find((chart) => { + return chart.key === 'avg_duration'; + }); + const series = avgDurationMetric?.series.find((item) => item.title === title); + expect(series?.overallValue).to.eql(expectedValue); + const meanValue = meanBy(series?.data.filter(isNotNullOrZeroCoordinate), 'y'); + expect(meanValue).to.eql(expectedValue); + }) + ); + }); + + let metricsChart: typeof serverlessMetrics.charts[0] | undefined; + + describe('Cold start duration', () => { + before(() => { + metricsChart = serverlessMetrics.charts.find((chart) => { + return chart.key === 'cold_start_duration'; + }); + }); + it('returns correct overall value', () => { + expect(metricsChart?.series[0].overallValue).to.equal(coldStartDurationPython * 1000); + }); + + it('returns correct mean value', () => { + const meanValue = meanBy( + metricsChart?.series[0]?.data.filter(isNotNullOrZeroCoordinate), + 'y' + ); + expect(meanValue).to.equal(coldStartDurationPython * 1000); + }); + }); + + describe('Cold start count', () => { + before(() => { + metricsChart = serverlessMetrics.charts.find((chart) => { + return chart.key === 'cold_start_count'; + }); + }); + + it('returns correct overall value', () => { + expect(metricsChart?.series[0].overallValue).to.equal(numberOfTransactionsCreated); + }); + + it('returns correct sum value', () => { + const sumValue = sumBy( + metricsChart?.series[0]?.data.filter(isNotNullOrZeroCoordinate), + 'y' + ); + expect(sumValue).to.equal(numberOfTransactionsCreated); + }); + }); + + describe('memory usage', () => { + const expectedFreeMemory = 1 - memoryFree / memoryTotal; + [ + { title: 'Max', expectedValue: expectedFreeMemory }, + { title: 'Average', expectedValue: expectedFreeMemory }, + ].map(({ title, expectedValue }) => + it(`returns correct ${title} value`, () => { + const memoryUsageMetric = serverlessMetrics.charts.find((chart) => { + return chart.key === 'memory_usage_chart'; + }); + const series = memoryUsageMetric?.series.find((item) => item.title === title); + expect(series?.overallValue).to.eql(expectedValue); + const meanValue = meanBy(series?.data.filter(isNotNullOrZeroCoordinate), 'y'); + expect(meanValue).to.eql(expectedValue); + }) + ); + }); + + describe('Compute usage', () => { + const GBSeconds = 1024 * 1024 * 1024 * 1000; + const expectedValue = (memoryTotal * billedDurationMs) / GBSeconds; + let computeUsageMetric: typeof serverlessMetrics.charts[0] | undefined; + before(() => { + computeUsageMetric = serverlessMetrics.charts.find((chart) => { + return chart.key === 'compute_usage'; + }); + }); + it('returns correct overall value', () => { + expect(computeUsageMetric?.series[0].overallValue).to.equal(expectedValue); + }); + + it('returns correct mean value', () => { + const meanValue = meanBy( + computeUsageMetric?.series[0]?.data.filter((item) => item.y !== 0), + 'y' + ); + expect(meanValue).to.equal(expectedValue); + }); + }); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_summary.spec.ts b/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_summary.spec.ts new file mode 100644 index 000000000000..eff5e1127801 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_summary.spec.ts @@ -0,0 +1,109 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { config, expectedValues, generateData } from './generate_data'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const registry = getService('registry'); + const apmApiClient = getService('apmApiClient'); + const synthtraceEsClient = getService('synthtraceEsClient'); + + const start = new Date('2021-01-01T00:00:00.000Z').getTime(); + const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; + async function callApi(serviceName: string, serverlessId?: string) { + return await apmApiClient.readUser({ + endpoint: `GET /internal/apm/services/{serviceName}/metrics/serverless/summary`, + params: { + path: { serviceName }, + query: { + environment: 'test', + kuery: '', + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + ...(serverlessId ? { serverlessId } : {}), + }, + }, + }); + } + + registry.when( + 'Serverless overview when data is not loaded', + { config: 'basic', archives: [] }, + () => { + let serverlessSummary: APIReturnType<'GET /internal/apm/services/{serviceName}/metrics/serverless/summary'>; + before(async () => { + const response = await callApi('lambda-python'); + serverlessSummary = response.body; + }); + + it('returns empty', () => { + expect(serverlessSummary).to.be.empty(); + }); + } + ); + + registry.when('Serverless overview', { config: 'basic', archives: [] }, () => { + const { billedDurationMs, pythonServerlessFunctionNames, faasDuration, serverlessId } = config; + const { expectedMemoryUsedRate } = expectedValues; + + before(async () => { + await generateData({ start, end, synthtraceEsClient }); + }); + + // after(() => synthtraceEsClient.clean()); + + describe('Python service', () => { + let serverlessSummary: APIReturnType<'GET /internal/apm/services/{serviceName}/metrics/serverless/summary'>; + before(async () => { + const response = await callApi('lambda-python'); + serverlessSummary = response.body; + }); + + it('returns correct memory avg', () => { + expect(serverlessSummary.memoryUsageAvgRate).to.eql(expectedMemoryUsedRate); + }); + it('returns correct serverless function total', () => { + expect(serverlessSummary.serverlessFunctionsTotal).to.eql( + pythonServerlessFunctionNames.length + ); + }); + it('returns correct serverless duration avg', () => { + expect(serverlessSummary.serverlessDurationAvg).to.eql(faasDuration); + }); + it('returns correct billed duration avg', () => { + expect(serverlessSummary.billedDurationAvg).to.eql(billedDurationMs); + }); + }); + + describe('detailed metrics', () => { + let serverlessSummary: APIReturnType<'GET /internal/apm/services/{serviceName}/metrics/serverless/summary'>; + before(async () => { + const response = await callApi( + 'lambda-python', + `${serverlessId}${pythonServerlessFunctionNames[0]}` + ); + serverlessSummary = response.body; + }); + + it('returns correct memory avg', () => { + expect(serverlessSummary.memoryUsageAvgRate).to.eql(expectedMemoryUsedRate); + }); + it('returns correct serverless function total', () => { + expect(serverlessSummary.serverlessFunctionsTotal).to.eql(1); + }); + it('returns correct serverless duration avg', () => { + expect(serverlessSummary.serverlessDurationAvg).to.eql(faasDuration); + }); + it('returns correct billed duration avg', () => { + expect(serverlessSummary.billedDurationAvg).to.eql(billedDurationMs); + }); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/metrics_charts/serverless.spec.ts b/x-pack/test/apm_api_integration/tests/metrics_charts/serverless.spec.ts deleted file mode 100644 index be56277b5fad..000000000000 --- a/x-pack/test/apm_api_integration/tests/metrics_charts/serverless.spec.ts +++ /dev/null @@ -1,431 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; -import { apm, timerange } from '@kbn/apm-synthtrace'; -import expect from '@kbn/expect'; -import { meanBy, sumBy } from 'lodash'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; - -export default function ApiTest({ getService }: FtrProviderContext) { - const registry = getService('registry'); - const apmApiClient = getService('apmApiClient'); - const synthtraceEsClient = getService('synthtraceEsClient'); - - const start = new Date('2021-01-01T00:00:00.000Z').getTime(); - const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; - - async function callApi(serviceName: string, agentName: string) { - return await apmApiClient.readUser({ - endpoint: `GET /internal/apm/services/{serviceName}/metrics/charts`, - params: { - path: { serviceName }, - query: { - environment: 'test', - agentName, - kuery: '', - start: new Date(start).toISOString(), - end: new Date(end).toISOString(), - serviceRuntimeName: 'aws_lambda', - }, - }, - }); - } - - registry.when( - 'Serverless metrics charts when data is loaded', - { config: 'basic', archives: [] }, - () => { - const MEMORY_TOTAL = 536870912; // 0.5gb; - const MEMORY_FREE = 94371840; // ~0.08 gb; - const BILLED_DURATION_MS = 4000; - const FAAS_TIMEOUT_MS = 10000; - const COLD_START_DURATION_PYTHON = 4000; - const COLD_START_DURATION_NODE = 0; - const FAAS_DURATION = 4000; - const TRANSACTION_DURATION = 1000; - - const numberOfTransactionsCreated = 15; - const numberOfPythonInstances = 2; - - before(async () => { - const cloudFields = { - 'cloud.provider': 'aws', - 'cloud.service.name': 'lambda', - 'cloud.region': 'us-west-2', - }; - - const instanceLambdaPython = apm - .serverlessFunction({ - serviceName: 'lambda-python', - environment: 'test', - agentName: 'python', - functionName: 'fn-lambda-python', - }) - .instance({ instanceName: 'instance python', ...cloudFields }); - - const instanceLambdaPython2 = apm - .serverlessFunction({ - serviceName: 'lambda-python', - environment: 'test', - agentName: 'python', - functionName: 'fn-lambda-python-2', - }) - .instance({ instanceName: 'instance python 2', ...cloudFields }); - - const instanceLambdaNode = apm - .serverlessFunction({ - serviceName: 'lambda-node', - environment: 'test', - agentName: 'nodejs', - functionName: 'fn-lambda-node', - }) - .instance({ instanceName: 'instance node', ...cloudFields }); - - const systemMemory = { - free: MEMORY_FREE, - total: MEMORY_TOTAL, - }; - - const transactionsEvents = timerange(start, end) - .interval('1m') - .rate(1) - .generator((timestamp) => [ - instanceLambdaPython - .invocation() - .billedDuration(BILLED_DURATION_MS) - .coldStart(true) - .coldStartDuration(COLD_START_DURATION_PYTHON) - .faasDuration(FAAS_DURATION) - .faasTimeout(FAAS_TIMEOUT_MS) - .memory(systemMemory) - .timestamp(timestamp) - .duration(TRANSACTION_DURATION) - .success(), - instanceLambdaPython2 - .invocation() - .billedDuration(BILLED_DURATION_MS) - .coldStart(true) - .coldStartDuration(COLD_START_DURATION_PYTHON) - .faasDuration(FAAS_DURATION) - .faasTimeout(FAAS_TIMEOUT_MS) - .memory(systemMemory) - .timestamp(timestamp) - .duration(TRANSACTION_DURATION) - .success(), - instanceLambdaNode - .invocation() - .billedDuration(BILLED_DURATION_MS) - .coldStart(false) - .coldStartDuration(COLD_START_DURATION_NODE) - .faasDuration(FAAS_DURATION) - .faasTimeout(FAAS_TIMEOUT_MS) - .memory(systemMemory) - .timestamp(timestamp) - .duration(TRANSACTION_DURATION) - .success(), - ]); - - await synthtraceEsClient.index(transactionsEvents); - }); - - after(() => synthtraceEsClient.clean()); - - describe('python', () => { - let metrics: APIReturnType<'GET /internal/apm/services/{serviceName}/metrics/charts'>; - before(async () => { - const { status, body } = await callApi('lambda-python', 'python'); - - expect(status).to.be(200); - metrics = body; - }); - - it('returns all metrics chart', () => { - expect(metrics.charts.length).to.be.greaterThan(0); - expect(metrics.charts.map(({ title }) => title).sort()).to.eql([ - 'Active instances', - 'Avg. Duration', - 'Cold start', - 'Cold start duration', - 'Compute usage', - 'System memory usage', - ]); - }); - - describe('Avg. Duration', () => { - const transactionDurationInMicroSeconds = TRANSACTION_DURATION * 1000; - [ - { title: 'Billed Duration', expectedValue: BILLED_DURATION_MS * 1000 }, - { title: 'Transaction Duration', expectedValue: transactionDurationInMicroSeconds }, - ].map(({ title, expectedValue }) => - it(`returns correct ${title} value`, () => { - const avgDurationMetric = metrics.charts.find((chart) => { - return chart.key === 'avg_duration'; - }); - const series = avgDurationMetric?.series.find((item) => item.title === title); - expect(series?.overallValue).to.eql(expectedValue); - const meanValue = meanBy( - series?.data.filter((item) => item.y !== null), - 'y' - ); - expect(meanValue).to.eql(expectedValue); - }) - ); - }); - - describe('Cold start duration', () => { - let coldStartDurationMetric: typeof metrics['charts'][0] | undefined; - before(() => { - coldStartDurationMetric = metrics.charts.find((chart) => { - return chart.key === 'cold_start_duration'; - }); - }); - it('returns correct overall value', () => { - expect(coldStartDurationMetric?.series[0].overallValue).to.equal( - COLD_START_DURATION_PYTHON * 1000 - ); - }); - - it('returns correct mean value', () => { - const meanValue = meanBy( - coldStartDurationMetric?.series[0]?.data.filter((item) => item.y !== null), - 'y' - ); - expect(meanValue).to.equal(COLD_START_DURATION_PYTHON * 1000); - }); - }); - - describe('Cold start count', () => { - let coldStartCountMetric: typeof metrics['charts'][0] | undefined; - before(() => { - coldStartCountMetric = metrics.charts.find((chart) => { - return chart.key === 'cold_start_count'; - }); - }); - - it('returns correct overall value', () => { - expect(coldStartCountMetric?.series[0].overallValue).to.equal( - numberOfTransactionsCreated * numberOfPythonInstances - ); - }); - - it('returns correct sum value', () => { - const sumValue = sumBy( - coldStartCountMetric?.series[0]?.data.filter((item) => item.y !== null), - 'y' - ); - expect(sumValue).to.equal(numberOfTransactionsCreated * numberOfPythonInstances); - }); - }); - - describe('memory usage', () => { - const expectedFreeMemory = 1 - MEMORY_FREE / MEMORY_TOTAL; - [ - { title: 'Max', expectedValue: expectedFreeMemory }, - { title: 'Average', expectedValue: expectedFreeMemory }, - ].map(({ title, expectedValue }) => - it(`returns correct ${title} value`, () => { - const memoryUsageMetric = metrics.charts.find((chart) => { - return chart.key === 'memory_usage_chart'; - }); - const series = memoryUsageMetric?.series.find((item) => item.title === title); - expect(series?.overallValue).to.eql(expectedValue); - const meanValue = meanBy( - series?.data.filter((item) => item.y !== null), - 'y' - ); - expect(meanValue).to.eql(expectedValue); - }) - ); - }); - - describe('Compute usage', () => { - const GBSeconds = 1024 * 1024 * 1024 * 1000; - const expectedValue = (MEMORY_TOTAL * BILLED_DURATION_MS) / GBSeconds; - let computeUsageMetric: typeof metrics['charts'][0] | undefined; - before(() => { - computeUsageMetric = metrics.charts.find((chart) => { - return chart.key === 'compute_usage'; - }); - }); - it('returns correct overall value', () => { - expect(computeUsageMetric?.series[0].overallValue).to.equal(expectedValue); - }); - - it('returns correct mean value', () => { - const meanValue = meanBy( - computeUsageMetric?.series[0]?.data.filter((item) => item.y !== 0), - 'y' - ); - expect(meanValue).to.equal(expectedValue); - }); - }); - - describe('Active instances', () => { - let activeInstancesMetric: typeof metrics['charts'][0] | undefined; - before(() => { - activeInstancesMetric = metrics.charts.find((chart) => { - return chart.key === 'active_instances'; - }); - }); - it('returns correct overall value', () => { - expect(activeInstancesMetric?.series[0].overallValue).to.equal(numberOfPythonInstances); - }); - - it('returns correct sum value', () => { - const sumValue = sumBy( - activeInstancesMetric?.series[0]?.data.filter((item) => item.y !== 0), - 'y' - ); - expect(sumValue).to.equal(numberOfTransactionsCreated * numberOfPythonInstances); - }); - }); - }); - - describe('nodejs', () => { - let metrics: APIReturnType<'GET /internal/apm/services/{serviceName}/metrics/charts'>; - before(async () => { - const { status, body } = await callApi('lambda-node', 'nodejs'); - expect(status).to.be(200); - metrics = body; - }); - - it('returns all metrics chart', () => { - expect(metrics.charts.length).to.be.greaterThan(0); - expect(metrics.charts.map(({ title }) => title).sort()).to.eql([ - 'Active instances', - 'Avg. Duration', - 'Cold start', - 'Cold start duration', - 'Compute usage', - 'System memory usage', - ]); - }); - describe('Avg. Duration', () => { - const transactionDurationInMicroSeconds = TRANSACTION_DURATION * 1000; - [ - { title: 'Billed Duration', expectedValue: BILLED_DURATION_MS * 1000 }, - { title: 'Transaction Duration', expectedValue: transactionDurationInMicroSeconds }, - ].map(({ title, expectedValue }) => - it(`returns correct ${title} value`, () => { - const avgDurationMetric = metrics.charts.find((chart) => { - return chart.key === 'avg_duration'; - }); - const series = avgDurationMetric?.series.find((item) => item.title === title); - expect(series?.overallValue).to.eql(expectedValue); - const meanValue = meanBy( - series?.data.filter((item) => item.y !== null), - 'y' - ); - expect(meanValue).to.eql(expectedValue); - }) - ); - }); - - describe('Cold start duration', () => { - let coldStartDurationMetric: typeof metrics['charts'][0] | undefined; - before(() => { - coldStartDurationMetric = metrics.charts.find((chart) => { - return chart.key === 'cold_start_duration'; - }); - }); - - it('returns 0 overall value', () => { - expect(coldStartDurationMetric?.series[0].overallValue).to.equal( - COLD_START_DURATION_NODE * 1000 - ); - }); - - it('returns 0 mean value', () => { - const meanValue = meanBy( - coldStartDurationMetric?.series[0]?.data.filter((item) => item.y !== null), - 'y' - ); - expect(meanValue).to.equal(COLD_START_DURATION_NODE * 1000); - }); - }); - - describe('Cold start count', () => { - let coldStartCountMetric: typeof metrics['charts'][0] | undefined; - before(() => { - coldStartCountMetric = metrics.charts.find((chart) => { - return chart.key === 'cold_start_count'; - }); - }); - - it('does not return cold start count', () => { - expect(coldStartCountMetric?.series).to.be.empty(); - }); - }); - - describe('memory usage', () => { - const expectedFreeMemory = 1 - MEMORY_FREE / MEMORY_TOTAL; - [ - { title: 'Max', expectedValue: expectedFreeMemory }, - { title: 'Average', expectedValue: expectedFreeMemory }, - ].map(({ title, expectedValue }) => - it(`returns correct ${title} value`, () => { - const memoryUsageMetric = metrics.charts.find((chart) => { - return chart.key === 'memory_usage_chart'; - }); - const series = memoryUsageMetric?.series.find((item) => item.title === title); - expect(series?.overallValue).to.eql(expectedValue); - const meanValue = meanBy( - series?.data.filter((item) => item.y !== null), - 'y' - ); - expect(meanValue).to.eql(expectedValue); - }) - ); - }); - - describe('Compute usage', () => { - const GBSeconds = 1024 * 1024 * 1024 * 1000; - const expectedValue = (MEMORY_TOTAL * BILLED_DURATION_MS) / GBSeconds; - let computeUsageMetric: typeof metrics['charts'][0] | undefined; - before(() => { - computeUsageMetric = metrics.charts.find((chart) => { - return chart.key === 'compute_usage'; - }); - }); - it('returns correct overall value', () => { - expect(computeUsageMetric?.series[0].overallValue).to.equal(expectedValue); - }); - - it('returns correct mean value', () => { - const meanValue = meanBy( - computeUsageMetric?.series[0]?.data.filter((item) => item.y !== 0), - 'y' - ); - expect(meanValue).to.equal(expectedValue); - }); - }); - - describe('Active instances', () => { - let activeInstancesMetric: typeof metrics['charts'][0] | undefined; - before(() => { - activeInstancesMetric = metrics.charts.find((chart) => { - return chart.key === 'active_instances'; - }); - }); - it('returns correct overall value', () => { - // there's only one node instance - expect(activeInstancesMetric?.series[0].overallValue).to.equal(1); - }); - - it('returns correct sum value', () => { - const sumValue = sumBy( - activeInstancesMetric?.series[0]?.data.filter((item) => item.y !== 0), - 'y' - ); - expect(sumValue).to.equal(numberOfTransactionsCreated); - }); - }); - }); - } - ); -} diff --git a/x-pack/test/apm_api_integration/tests/storage_explorer/get_services.spec.ts b/x-pack/test/apm_api_integration/tests/storage_explorer/get_services.spec.ts new file mode 100644 index 000000000000..7d036a7018de --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/storage_explorer/get_services.spec.ts @@ -0,0 +1,116 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { apm, timerange } from '@kbn/apm-synthtrace'; +import expect from '@kbn/expect'; +import { IndexLifecyclePhaseSelectOption } from '@kbn/apm-plugin/common/storage_explorer_types'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const registry = getService('registry'); + const synthtraceEsClient = getService('synthtraceEsClient'); + const apmApiClient = getService('apmApiClient'); + + const start = '2021-01-01T12:00:00.000Z'; + const end = '2021-08-01T12:00:00.000Z'; + + // The terms enum API may return terms from deleted documents + // so we add a prefix to make sure we don't get data from other tests + const SERVICE_NAME_PREFIX = 'storage_explorer_services_'; + + async function getServices({ + environment = 'ENVIRONMENT_ALL', + kuery = '', + indexLifecyclePhase = IndexLifecyclePhaseSelectOption.All, + }: { + environment?: string; + kuery?: string; + indexLifecyclePhase?: IndexLifecyclePhaseSelectOption; + } = {}) { + const response = await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/storage_explorer/get_services', + params: { + query: { + environment, + kuery, + indexLifecyclePhase, + }, + }, + }); + + return response.body.services + .filter((service) => service.serviceName.startsWith(SERVICE_NAME_PREFIX)) + .map((service) => ({ + ...service, + serviceName: service.serviceName.replace(SERVICE_NAME_PREFIX, ''), + })); + } + + registry.when('Get services', { config: 'basic', archives: [] }, () => { + before(async () => { + const serviceA = apm + .service({ name: `${SERVICE_NAME_PREFIX}a`, environment: 'production', agentName: 'java' }) + .instance('a'); + + const serviceB = apm + .service({ name: `${SERVICE_NAME_PREFIX}b`, environment: 'development', agentName: 'go' }) + .instance('b'); + + const serviceC = apm + .service({ name: `${SERVICE_NAME_PREFIX}c`, environment: 'development', agentName: 'go' }) + .instance('c'); + + const eventsWithinTimerange = timerange(new Date(start).getTime(), new Date(end).getTime()) + .interval('15m') + .rate(1) + .generator((timestamp) => [ + serviceA.transaction({ transactionName: 'GET /api' }).duration(1000).timestamp(timestamp), + serviceB.transaction({ transactionName: 'GET /api' }).duration(1000).timestamp(timestamp), + ]); + + const eventsOutsideOfTimerange = timerange( + new Date('2021-01-01T00:00:00.000Z').getTime(), + new Date(start).getTime() - 1 + ) + .interval('15m') + .rate(1) + .generator((timestamp) => + serviceC.transaction({ transactionName: 'GET /api' }).duration(1000).timestamp(timestamp) + ); + + await synthtraceEsClient.index(eventsWithinTimerange.merge(eventsOutsideOfTimerange)); + }); + + after(() => synthtraceEsClient.clean()); + + it('with no kuery, environment or index lifecycle phase set it returns services based on the terms enum API', async () => { + const items = await getServices(); + const serviceNames = items.map((item) => item.serviceName); + expect(serviceNames.sort()).to.eql(['a', 'b', 'c']); + }); + + it('with kuery set does it does not return any services', async () => { + const services = await getServices({ + kuery: 'service.name:*', + }); + expect(services).to.be.empty(); + }); + + it('with environment set to production it does not return any services', async () => { + const services = await getServices({ + environment: 'production', + }); + expect(services).to.be.empty(); + }); + + it('with index lifecycle phase set to hot it does not return any services', async () => { + const services = await getServices({ + indexLifecyclePhase: IndexLifecyclePhaseSelectOption.Hot, + }); + expect(services).to.be.empty(); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/storage_explorer/storage_details.spec.ts b/x-pack/test/apm_api_integration/tests/storage_explorer/storage_details.spec.ts index a9ec2199c8b2..35bfca8b8c82 100644 --- a/x-pack/test/apm_api_integration/tests/storage_explorer/storage_details.spec.ts +++ b/x-pack/test/apm_api_integration/tests/storage_explorer/storage_details.spec.ts @@ -36,7 +36,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { APIClientRequestParamsOf<'GET /internal/apm/services/{serviceName}/storage_details'>['params'] > ) { - return await apmApiClient.monitorIndicesUser({ + return await apmApiClient.monitorClusterAndIndicesUser({ endpoint: 'GET /internal/apm/services/{serviceName}/storage_details', params: { path: { diff --git a/x-pack/test/apm_api_integration/tests/storage_explorer/storage_explorer.spec.ts b/x-pack/test/apm_api_integration/tests/storage_explorer/storage_explorer.spec.ts index dc2429a5b8b3..f54149665174 100644 --- a/x-pack/test/apm_api_integration/tests/storage_explorer/storage_explorer.spec.ts +++ b/x-pack/test/apm_api_integration/tests/storage_explorer/storage_explorer.spec.ts @@ -27,7 +27,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { APIClientRequestParamsOf<'GET /internal/apm/storage_explorer'>['params'] > ) { - return await apmApiClient.monitorIndicesUser({ + return await apmApiClient.monitorClusterAndIndicesUser({ endpoint: 'GET /internal/apm/storage_explorer', params: { query: { diff --git a/x-pack/test/apm_api_integration/tests/storage_explorer/storage_explorer_privileges.spec.ts b/x-pack/test/apm_api_integration/tests/storage_explorer/storage_explorer_privileges.spec.ts index 4d7cc8655840..087bacdc898b 100644 --- a/x-pack/test/apm_api_integration/tests/storage_explorer/storage_explorer_privileges.spec.ts +++ b/x-pack/test/apm_api_integration/tests/storage_explorer/storage_explorer_privileges.spec.ts @@ -20,7 +20,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { registry.when('Storage explorer privileges', { config: 'basic', archives: [] }, () => { it('returns true when the user has the required indices privileges', async () => { - const { status, body } = await callApi(apmApiClient.monitorIndicesUser); + const { status, body } = await callApi(apmApiClient.monitorClusterAndIndicesUser); expect(status).to.be(200); expect(body.hasPrivileges).to.be(true); }); diff --git a/x-pack/test/apm_api_integration/tests/storage_explorer/storage_explorer_summary_stats.spec.ts b/x-pack/test/apm_api_integration/tests/storage_explorer/storage_explorer_summary_stats.spec.ts index afde7d243c22..37a747a3c3e7 100644 --- a/x-pack/test/apm_api_integration/tests/storage_explorer/storage_explorer_summary_stats.spec.ts +++ b/x-pack/test/apm_api_integration/tests/storage_explorer/storage_explorer_summary_stats.spec.ts @@ -27,7 +27,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { APIClientRequestParamsOf<'GET /internal/apm/storage_explorer_summary_stats'>['params'] > ) { - return await apmApiClient.monitorIndicesUser({ + return await apmApiClient.monitorClusterAndIndicesUser({ endpoint: 'GET /internal/apm/storage_explorer_summary_stats', params: { query: { @@ -53,7 +53,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(status).to.be(200); expect(body.tracesPerMinute).to.be(0); expect(body.numberOfServices).to.be(0); - expect(body.estimatedSize).to.be(0); + expect(body.totalSize).to.be(0); + expect(body.estimatedIncrementalSize).to.be(0); + expect(body.diskSpaceUsedPct).to.be(0); expect(body.dailyDataGeneration).to.be(0); }); } @@ -100,7 +102,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(status).to.be(200); expect(body.numberOfServices).to.be(2); expect(roundNumber(body.tracesPerMinute)).to.be(2); - expect(body.estimatedSize).to.be.greaterThan(0); + expect(body.totalSize).to.be.greaterThan(0); + expect(body.estimatedIncrementalSize).to.be.greaterThan(0); + expect(body.diskSpaceUsedPct).to.be.greaterThan(0); expect(body.dailyDataGeneration).to.be.greaterThan(0); }); @@ -114,7 +118,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(status).to.be(200); expect(body.numberOfServices).to.be(1); expect(roundNumber(body.tracesPerMinute)).to.be(1); - expect(body.estimatedSize).to.be.greaterThan(0); + expect(body.totalSize).to.be.greaterThan(0); + expect(body.estimatedIncrementalSize).to.be.greaterThan(0); + expect(body.diskSpaceUsedPct).to.be.greaterThan(0); expect(body.dailyDataGeneration).to.be.greaterThan(0); }); @@ -128,7 +134,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(status).to.be(200); expect(body.tracesPerMinute).to.be(0); expect(body.numberOfServices).to.be(0); - expect(body.estimatedSize).to.be(0); + expect(body.totalSize).to.be.greaterThan(0); + expect(body.estimatedIncrementalSize).to.be(0); + expect(body.diskSpaceUsedPct).to.be.greaterThan(0); expect(body.dailyDataGeneration).to.be(0); }); @@ -142,7 +150,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(status).to.be(200); expect(body.numberOfServices).to.be(1); expect(roundNumber(body.tracesPerMinute)).to.be(1); - expect(body.estimatedSize).to.be.greaterThan(0); + expect(body.totalSize).to.be.greaterThan(0); + expect(body.estimatedIncrementalSize).to.be.greaterThan(0); + expect(body.diskSpaceUsedPct).to.be.greaterThan(0); expect(body.dailyDataGeneration).to.be.greaterThan(0); }); }); diff --git a/x-pack/test/apm_api_integration/tests/storage_explorer/storage_explorer_timeseries_chart.spec.ts b/x-pack/test/apm_api_integration/tests/storage_explorer/storage_explorer_timeseries_chart.spec.ts index 632e17c37509..f668b0ba71a5 100644 --- a/x-pack/test/apm_api_integration/tests/storage_explorer/storage_explorer_timeseries_chart.spec.ts +++ b/x-pack/test/apm_api_integration/tests/storage_explorer/storage_explorer_timeseries_chart.spec.ts @@ -22,7 +22,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; async function callApi() { - return await apmApiClient.monitorIndicesUser({ + return await apmApiClient.monitorClusterAndIndicesUser({ endpoint: 'GET /internal/apm/storage_chart', params: { query: { diff --git a/x-pack/test/detection_engine_api_integration/common/config.ts b/x-pack/test/detection_engine_api_integration/common/config.ts index a95cb937d4cd..fbbe7fd62a7a 100644 --- a/x-pack/test/detection_engine_api_integration/common/config.ts +++ b/x-pack/test/detection_engine_api_integration/common/config.ts @@ -77,6 +77,7 @@ export function createTestConfig(options: CreateTestConfigOptions, testFiles?: s `--xpack.securitySolution.enableExperimental=${JSON.stringify([ 'previewTelemetryUrlEnabled', ])}`, + '--xpack.task_manager.poll_interval=1000', ...(ssl ? [ `--elasticsearch.hosts=${servers.elasticsearch.protocol}://${servers.elasticsearch.hostname}:${servers.elasticsearch.port}`, diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_new_terms.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_new_terms.ts index bfb369e0091b..095ce3766918 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_new_terms.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_new_terms.ts @@ -5,78 +5,26 @@ * 2.0. */ -import { orderBy } from 'lodash'; import expect from '@kbn/expect'; import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; -import { RuleExecutionStatus } from '@kbn/security-solution-plugin/common/detection_engine/rule_monitoring'; -import { NewTermsRuleCreateProps } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; import { getCreateNewTermsRulesSchemaMock } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema/mocks'; -import { DetectionAlert } from '@kbn/security-solution-plugin/common/detection_engine/schemas/alerts'; import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { - createRule, - createRuleWithExceptionEntries, - createSignalsIndex, - deleteAllAlerts, - deleteSignalsIndex, - getOpenSignals, - getSignalsByIds, - waitForRuleSuccessOrStatus, - waitForSignalsToBePresent, -} from '../../utils'; +import { deleteAllAlerts } from '../../utils'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { - const esArchiver = getService('esArchiver'); const supertest = getService('supertest'); const log = getService('log'); - const es = getService('es'); /** * Specific api integration tests for threat matching rule type */ describe('create_new_terms', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); - }); - - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); - }); - - beforeEach(async () => { - await createSignalsIndex(supertest, log); - }); - afterEach(async () => { - await deleteSignalsIndex(supertest, log); await deleteAllAlerts(supertest, log); }); - it('should create a single rule with a rule_id and validate it ran successfully', async () => { - const ruleResponse = await createRule( - supertest, - log, - getCreateNewTermsRulesSchemaMock('rule-1', true) - ); - - await waitForRuleSuccessOrStatus( - supertest, - log, - ruleResponse.id, - RuleExecutionStatus.succeeded - ); - - const { body: rule } = await supertest - .get(DETECTION_ENGINE_RULES_URL) - .set('kbn-xsrf', 'true') - .query({ id: ruleResponse.id }) - .expect(200); - - expect(rule?.execution_summary?.last_execution.status).to.eql('succeeded'); - }); - it('should not be able to create a new terms rule with too small history window', async () => { const rule = { ...getCreateNewTermsRulesSchemaMock('rule-1'), @@ -92,379 +40,5 @@ export default ({ getService }: FtrProviderContext) => { "params invalid: History window size is smaller than rule interval + additional lookback, 'historyWindowStart' must be earlier than 'from'" ); }); - - const removeRandomValuedProperties = (alert: DetectionAlert | undefined) => { - if (!alert) { - return undefined; - } - const { - 'kibana.version': version, - 'kibana.alert.rule.execution.uuid': execUuid, - 'kibana.alert.rule.uuid': uuid, - '@timestamp': timestamp, - 'kibana.alert.rule.created_at': createdAt, - 'kibana.alert.rule.updated_at': updatedAt, - 'kibana.alert.uuid': alertUuid, - ...restOfAlert - } = alert; - return restOfAlert; - }; - - // This test also tests that alerts are NOT created for terms that are not new: the host name - // suricata-sensor-san-francisco appears in a document at 2019-02-19T20:42:08.230Z, but also appears - // in earlier documents so is not new. An alert should not be generated for that term. - it('should generate 1 alert with 1 selected field', async () => { - const rule: NewTermsRuleCreateProps = { - ...getCreateNewTermsRulesSchemaMock('rule-1', true), - new_terms_fields: ['host.name'], - from: '2019-02-19T20:42:00.000Z', - history_window_start: '2019-01-19T20:42:00.000Z', - }; - - const createdRule = await createRule(supertest, log, rule); - - await waitForRuleSuccessOrStatus( - supertest, - log, - createdRule.id, - RuleExecutionStatus.succeeded - ); - - const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); - expect(signalsOpen.hits.hits.length).eql(1); - expect(removeRandomValuedProperties(signalsOpen.hits.hits[0]._source)).eql({ - 'kibana.alert.new_terms': ['zeek-newyork-sha-aa8df15'], - 'kibana.alert.rule.category': 'New Terms Rule', - 'kibana.alert.rule.consumer': 'siem', - 'kibana.alert.rule.name': 'Query with a rule id', - 'kibana.alert.rule.producer': 'siem', - 'kibana.alert.rule.rule_type_id': 'siem.newTermsRule', - 'kibana.space_ids': ['default'], - 'kibana.alert.rule.tags': [], - agent: { - ephemeral_id: '7cc2091a-72f1-4c63-843b-fdeb622f9c69', - hostname: 'zeek-newyork-sha-aa8df15', - id: '4b4462ef-93d2-409c-87a6-299d942e5047', - type: 'auditbeat', - version: '8.0.0', - }, - cloud: { instance: { id: '139865230' }, provider: 'digitalocean', region: 'nyc1' }, - ecs: { version: '1.0.0-beta2' }, - host: { - architecture: 'x86_64', - hostname: 'zeek-newyork-sha-aa8df15', - id: '3729d06ce9964aa98549f41cbd99334d', - ip: ['157.230.208.30', '10.10.0.6', 'fe80::24ce:f7ff:fede:a571'], - mac: ['26:ce:f7:de:a5:71'], - name: 'zeek-newyork-sha-aa8df15', - os: { - codename: 'cosmic', - family: 'debian', - kernel: '4.18.0-10-generic', - name: 'Ubuntu', - platform: 'ubuntu', - version: '18.10 (Cosmic Cuttlefish)', - }, - }, - message: - 'Login by user root (UID: 0) on pts/0 (PID: 20638) from 8.42.77.171 (IP: 8.42.77.171)', - process: { pid: 20638 }, - service: { type: 'system' }, - source: { ip: '8.42.77.171' }, - user: { id: 0, name: 'root', terminal: 'pts/0' }, - 'event.action': 'user_login', - 'event.category': 'authentication', - 'event.dataset': 'login', - 'event.kind': 'signal', - 'event.module': 'system', - 'event.origin': '/var/log/wtmp', - 'event.outcome': 'success', - 'event.type': 'authentication_success', - 'kibana.alert.original_time': '2019-02-19T20:42:08.230Z', - 'kibana.alert.ancestors': [ - { - id: 'x07wJ2oB9v5HJNSHhyxi', - type: 'event', - index: 'auditbeat-8.0.0-2019.02.19-000001', - depth: 0, - }, - ], - 'kibana.alert.status': 'active', - 'kibana.alert.workflow_status': 'open', - 'kibana.alert.depth': 1, - 'kibana.alert.reason': - 'authentication event with source 8.42.77.171 by root on zeek-newyork-sha-aa8df15 created high alert Query with a rule id.', - 'kibana.alert.severity': 'high', - 'kibana.alert.risk_score': 55, - 'kibana.alert.rule.parameters': { - description: 'Detecting root and admin users', - risk_score: 55, - severity: 'high', - author: [], - false_positives: [], - from: '2019-02-19T20:42:00.000Z', - rule_id: 'rule-1', - max_signals: 100, - risk_score_mapping: [], - severity_mapping: [], - threat: [], - to: 'now', - references: [], - version: 1, - exceptions_list: [], - immutable: false, - related_integrations: [], - required_fields: [], - setup: '', - type: 'new_terms', - query: '*', - new_terms_fields: ['host.name'], - history_window_start: '2019-01-19T20:42:00.000Z', - index: ['auditbeat-*'], - language: 'kuery', - }, - 'kibana.alert.rule.actions': [], - 'kibana.alert.rule.author': [], - 'kibana.alert.rule.created_by': 'elastic', - 'kibana.alert.rule.description': 'Detecting root and admin users', - 'kibana.alert.rule.enabled': true, - 'kibana.alert.rule.exceptions_list': [], - 'kibana.alert.rule.false_positives': [], - 'kibana.alert.rule.from': '2019-02-19T20:42:00.000Z', - 'kibana.alert.rule.immutable': false, - 'kibana.alert.rule.indices': ['auditbeat-*'], - 'kibana.alert.rule.interval': '5m', - 'kibana.alert.rule.max_signals': 100, - 'kibana.alert.rule.references': [], - 'kibana.alert.rule.risk_score_mapping': [], - 'kibana.alert.rule.rule_id': 'rule-1', - 'kibana.alert.rule.severity_mapping': [], - 'kibana.alert.rule.threat': [], - 'kibana.alert.rule.to': 'now', - 'kibana.alert.rule.type': 'new_terms', - 'kibana.alert.rule.updated_by': 'elastic', - 'kibana.alert.rule.version': 1, - 'kibana.alert.rule.risk_score': 55, - 'kibana.alert.rule.severity': 'high', - 'kibana.alert.original_event.action': 'user_login', - 'kibana.alert.original_event.category': 'authentication', - 'kibana.alert.original_event.dataset': 'login', - 'kibana.alert.original_event.kind': 'event', - 'kibana.alert.original_event.module': 'system', - 'kibana.alert.original_event.origin': '/var/log/wtmp', - 'kibana.alert.original_event.outcome': 'success', - 'kibana.alert.original_event.type': 'authentication_success', - }); - }); - - it('should generate 3 alerts when 1 document has 3 new values', async () => { - const rule: NewTermsRuleCreateProps = { - ...getCreateNewTermsRulesSchemaMock('rule-1', true), - new_terms_fields: ['host.ip'], - from: '2019-02-19T20:42:00.000Z', - history_window_start: '2019-01-19T20:42:00.000Z', - }; - - const createdRule = await createRule(supertest, log, rule); - - await waitForRuleSuccessOrStatus( - supertest, - log, - createdRule.id, - RuleExecutionStatus.succeeded - ); - - const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); - expect(signalsOpen.hits.hits.length).eql(3); - const signalsOrderedByHostIp = orderBy( - signalsOpen.hits.hits, - '_source.kibana.alert.new_terms', - 'asc' - ); - expect(signalsOrderedByHostIp[0]._source?.['kibana.alert.new_terms']).eql(['10.10.0.6']); - expect(signalsOrderedByHostIp[1]._source?.['kibana.alert.new_terms']).eql(['157.230.208.30']); - expect(signalsOrderedByHostIp[2]._source?.['kibana.alert.new_terms']).eql([ - 'fe80::24ce:f7ff:fede:a571', - ]); - }); - - it('should generate alerts for every term when history window is small', async () => { - const rule: NewTermsRuleCreateProps = { - ...getCreateNewTermsRulesSchemaMock('rule-1', true), - new_terms_fields: ['host.name'], - from: '2019-02-19T20:42:00.000Z', - // Set the history_window_start close to 'from' so we should alert on all terms in the time range - history_window_start: '2019-02-19T20:41:59.000Z', - }; - - const createdRule = await createRule(supertest, log, rule); - - await waitForRuleSuccessOrStatus( - supertest, - log, - createdRule.id, - RuleExecutionStatus.succeeded - ); - - const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); - expect(signalsOpen.hits.hits.length).eql(5); - const hostNames = signalsOpen.hits.hits - .map((signal) => signal._source?.['kibana.alert.new_terms']) - .sort(); - expect(hostNames[0]).eql(['suricata-sensor-amsterdam']); - expect(hostNames[1]).eql(['suricata-sensor-san-francisco']); - expect(hostNames[2]).eql(['zeek-newyork-sha-aa8df15']); - expect(hostNames[3]).eql(['zeek-sensor-amsterdam']); - expect(hostNames[4]).eql(['zeek-sensor-san-francisco']); - }); - - describe('timestamp override and fallback', () => { - before(async () => { - await esArchiver.load( - 'x-pack/test/functional/es_archives/security_solution/timestamp_fallback' - ); - await esArchiver.load( - 'x-pack/test/functional/es_archives/security_solution/timestamp_override_3' - ); - }); - after(async () => { - await esArchiver.unload( - 'x-pack/test/functional/es_archives/security_solution/timestamp_fallback' - ); - await esArchiver.unload( - 'x-pack/test/functional/es_archives/security_solution/timestamp_override_3' - ); - }); - - it('should generate the correct alerts', async () => { - const rule: NewTermsRuleCreateProps = { - ...getCreateNewTermsRulesSchemaMock('rule-1', true), - // myfakeindex-3 does not have event.ingested mapped so we can test if the runtime field - // 'kibana.combined_timestamp' handles unmapped fields properly - index: ['timestamp-fallback-test', 'myfakeindex-3'], - new_terms_fields: ['host.name'], - from: '2020-12-16T16:00:00.000Z', - // Set the history_window_start close to 'from' so we should alert on all terms in the time range - history_window_start: '2020-12-16T15:59:00.000Z', - timestamp_override: 'event.ingested', - }; - - const createdRule = await createRule(supertest, log, rule); - - await waitForSignalsToBePresent(supertest, log, 2, [createdRule.id]); - - const signalsOpen = await getSignalsByIds(supertest, log, [createdRule.id]); - expect(signalsOpen.hits.hits.length).eql(2); - const hostNames = signalsOpen.hits.hits - .map((signal) => signal._source?.['kibana.alert.new_terms']) - .sort(); - expect(hostNames[0]).eql(['host-3']); - expect(hostNames[1]).eql(['host-4']); - }); - }); - - it('should apply exceptions', async () => { - const rule: NewTermsRuleCreateProps = { - ...getCreateNewTermsRulesSchemaMock('rule-1', true), - new_terms_fields: ['host.name'], - from: '2019-02-19T20:42:00.000Z', - // Set the history_window_start close to 'from' so we should alert on all terms in the time range - history_window_start: '2019-02-19T20:41:59.000Z', - }; - const createdRule = await createRuleWithExceptionEntries(supertest, log, rule, [ - [ - { - field: 'host.name', - operator: 'included', - type: 'match', - value: 'zeek-sensor-san-francisco', - }, - ], - ]); - - await waitForRuleSuccessOrStatus( - supertest, - log, - createdRule.id, - RuleExecutionStatus.succeeded - ); - - const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); - expect(signalsOpen.hits.hits.length).eql(4); - const hostNames = signalsOpen.hits.hits - .map((signal) => signal._source?.['kibana.alert.new_terms']) - .sort(); - expect(hostNames[0]).eql(['suricata-sensor-amsterdam']); - expect(hostNames[1]).eql(['suricata-sensor-san-francisco']); - expect(hostNames[2]).eql(['zeek-newyork-sha-aa8df15']); - expect(hostNames[3]).eql(['zeek-sensor-amsterdam']); - }); - - it('should work for max signals > 100', async () => { - const maxSignals = 200; - const rule: NewTermsRuleCreateProps = { - ...getCreateNewTermsRulesSchemaMock('rule-1', true), - new_terms_fields: ['process.pid'], - from: '2018-02-19T20:42:00.000Z', - // Set the history_window_start close to 'from' so we should alert on all terms in the time range - history_window_start: '2018-02-19T20:41:59.000Z', - max_signals: maxSignals, - }; - - const createdRule = await createRule(supertest, log, rule); - - await waitForRuleSuccessOrStatus( - supertest, - log, - createdRule.id, - RuleExecutionStatus.succeeded - ); - - const signalsOpen = await getOpenSignals( - supertest, - log, - es, - createdRule, - RuleExecutionStatus.succeeded, - maxSignals - ); - expect(signalsOpen.hits.hits.length).eql(maxSignals); - const processPids = signalsOpen.hits.hits - .map((signal) => signal._source?.['kibana.alert.new_terms']) - .sort(); - expect(processPids[0]).eql([1]); - }); - - describe('alerts should be be enriched', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/entity/host_risk'); - }); - - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/entity/host_risk'); - }); - - it('should be enriched with host risk score', async () => { - const rule: NewTermsRuleCreateProps = { - ...getCreateNewTermsRulesSchemaMock('rule-1', true), - new_terms_fields: ['host.name'], - from: '2019-02-19T20:42:00.000Z', - history_window_start: '2019-01-19T20:42:00.000Z', - }; - - const createdRule = await createRule(supertest, log, rule); - - await waitForRuleSuccessOrStatus( - supertest, - log, - createdRule.id, - RuleExecutionStatus.succeeded - ); - - const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); - expect(signalsOpen.hits.hits[0]?._source?.host?.risk?.calculated_level).to.eql('Low'); - expect(signalsOpen.hits.hits[0]?._source?.host?.risk?.calculated_score_norm).to.eql(23); - }); - }); }); }; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_threat_matching.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_threat_matching.ts deleted file mode 100644 index b18f716d17d4..000000000000 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_threat_matching.ts +++ /dev/null @@ -1,1421 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { get, isEqual } from 'lodash'; -import expect from '@kbn/expect'; -import { - ALERT_REASON, - ALERT_RULE_UUID, - ALERT_STATUS, - ALERT_RULE_NAMESPACE, - ALERT_RULE_UPDATED_AT, - ALERT_UUID, - ALERT_WORKFLOW_STATUS, - SPACE_IDS, - VERSION, -} from '@kbn/rule-data-utils'; -import { flattenWithPrefix } from '@kbn/securitysolution-rules'; - -import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; -import { RuleExecutionStatus } from '@kbn/security-solution-plugin/common/detection_engine/rule_monitoring'; -import { RuleCreateProps } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; -import { - getCreateThreatMatchRulesSchemaMock, - getThreatMatchingSchemaPartialMock, -} from '@kbn/security-solution-plugin/common/detection_engine/rule_schema/mocks'; -import { ENRICHMENT_TYPES } from '@kbn/security-solution-plugin/common/cti/constants'; -import { Ancestor } from '@kbn/security-solution-plugin/server/lib/detection_engine/signals/types'; -import { - ALERT_ANCESTORS, - ALERT_DEPTH, - ALERT_ORIGINAL_EVENT_ACTION, - ALERT_ORIGINAL_EVENT_CATEGORY, - ALERT_ORIGINAL_EVENT_MODULE, - ALERT_ORIGINAL_TIME, -} from '@kbn/security-solution-plugin/common/field_maps/field_names'; -import { - createRule, - createSignalsIndex, - deleteAllAlerts, - deleteSignalsIndex, - getSignalsByIds, - removeServerGeneratedProperties, - waitForRuleSuccessOrStatus, - waitForSignalsToBePresent, -} from '../../utils'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; - -const format = (value: unknown): string => JSON.stringify(value, null, 2); - -// Asserts that each expected value is included in the subject, independent of -// ordering. Uses _.isEqual for value comparison. -const assertContains = (subject: unknown[], expected: unknown[]) => - expected.forEach((expectedValue) => - expect(subject.some((value) => isEqual(value, expectedValue))).to.eql( - true, - `expected ${format(subject)} to contain ${format(expectedValue)}` - ) - ); - -// eslint-disable-next-line import/no-default-export -export default ({ getService }: FtrProviderContext) => { - const esArchiver = getService('esArchiver'); - const supertest = getService('supertest'); - const log = getService('log'); - - /** - * Specific api integration tests for threat matching rule type - */ - describe('create_threat_matching', () => { - describe('creating threat match rule', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); - }); - - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); - }); - - beforeEach(async () => { - await createSignalsIndex(supertest, log); - }); - - afterEach(async () => { - await deleteSignalsIndex(supertest, log); - await deleteAllAlerts(supertest, log); - }); - - it('should create a single rule with a rule_id', async () => { - const ruleResponse = await createRule( - supertest, - log, - getCreateThreatMatchRulesSchemaMock() - ); - const bodyToCompare = removeServerGeneratedProperties(ruleResponse); - expect(bodyToCompare).to.eql(getThreatMatchingSchemaPartialMock()); - }); - - it('should create a single rule with a rule_id and validate it ran successfully', async () => { - const ruleResponse = await createRule( - supertest, - log, - getCreateThreatMatchRulesSchemaMock('rule-1', true) - ); - - await waitForRuleSuccessOrStatus( - supertest, - log, - ruleResponse.id, - RuleExecutionStatus.succeeded - ); - - const { body: rule } = await supertest - .get(DETECTION_ENGINE_RULES_URL) - .set('kbn-xsrf', 'true') - .query({ id: ruleResponse.id }) - .expect(200); - - const bodyToCompare = removeServerGeneratedProperties(ruleResponse); - expect(bodyToCompare).to.eql(getThreatMatchingSchemaPartialMock(true)); - - // TODO: https://github.com/elastic/kibana/pull/121644 clean up, make type-safe - expect(rule?.execution_summary?.last_execution.status).to.eql('succeeded'); - }); - }); - - describe('tests with auditbeat data', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); - }); - - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); - }); - - beforeEach(async () => { - await deleteAllAlerts(supertest, log); - await createSignalsIndex(supertest, log); - }); - - afterEach(async () => { - await deleteSignalsIndex(supertest, log); - await deleteAllAlerts(supertest, log); - }); - - it('should be able to execute and get 10 signals when doing a specific query', async () => { - const rule: RuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: '*:*', - threat_query: 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip - threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity - threat_mapping: [ - // We match host.name against host.name - { - entries: [ - { - field: 'host.name', - value: 'host.name', - type: 'mapping', - }, - ], - }, - ], - threat_filters: [], - }; - - const createdRule = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, createdRule.id); - await waitForSignalsToBePresent(supertest, log, 10, [createdRule.id]); - const signalsOpen = await getSignalsByIds(supertest, log, [createdRule.id]); - expect(signalsOpen.hits.hits.length).equal(10); - const fullSource = signalsOpen.hits.hits.find( - (signal) => - (signal._source?.[ALERT_ANCESTORS] as Ancestor[])[0].id === '7yJ-B2kBR346wHgnhlMn' - ); - const fullSignal = fullSource?._source; - if (!fullSignal) { - return expect(fullSignal).to.be.ok(); - } - expect(fullSignal).eql({ - ...fullSignal, - '@timestamp': fullSignal['@timestamp'], - agent: { - ephemeral_id: '1b4978a0-48be-49b1-ac96-323425b389ab', - hostname: 'zeek-sensor-amsterdam', - id: 'e52588e6-7aa3-4c89-a2c4-d6bc5c286db1', - type: 'auditbeat', - version: '8.0.0', - }, - auditd: { - data: { - hostname: '46.101.47.213', - op: 'PAM:bad_ident', - terminal: 'ssh', - }, - message_type: 'user_err', - result: 'fail', - sequence: 2267, - session: 'unset', - summary: { - actor: { - primary: 'unset', - secondary: 'root', - }, - how: '/usr/sbin/sshd', - object: { - primary: 'ssh', - secondary: '46.101.47.213', - type: 'user-session', - }, - }, - }, - cloud: { - instance: { - id: '133551048', - }, - provider: 'digitalocean', - region: 'ams3', - }, - ecs: { - version: '1.0.0-beta2', - }, - ...flattenWithPrefix('event', { - action: 'error', - category: 'user-login', - module: 'auditd', - kind: 'signal', - }), - host: { - architecture: 'x86_64', - containerized: false, - hostname: 'zeek-sensor-amsterdam', - id: '2ce8b1e7d69e4a1d9c6bcddc473da9d9', - name: 'zeek-sensor-amsterdam', - os: { - codename: 'bionic', - family: 'debian', - kernel: '4.15.0-45-generic', - name: 'Ubuntu', - platform: 'ubuntu', - version: '18.04.2 LTS (Bionic Beaver)', - }, - }, - network: { - direction: 'incoming', - }, - process: { - executable: '/usr/sbin/sshd', - pid: 32739, - }, - service: { - type: 'auditd', - }, - source: { - ip: '46.101.47.213', - }, - user: { - audit: { - id: 'unset', - }, - id: '0', - name: 'root', - }, - [ALERT_ANCESTORS]: [ - { - id: '7yJ-B2kBR346wHgnhlMn', - type: 'event', - index: 'auditbeat-8.0.0-2019.02.19-000001', - depth: 0, - }, - ], - [ALERT_DEPTH]: 1, - [ALERT_ORIGINAL_EVENT_ACTION]: 'error', - [ALERT_ORIGINAL_EVENT_CATEGORY]: 'user-login', - [ALERT_ORIGINAL_EVENT_MODULE]: 'auditd', - [ALERT_ORIGINAL_TIME]: fullSignal[ALERT_ORIGINAL_TIME], - [ALERT_REASON]: - 'user-login event with source 46.101.47.213 by root on zeek-sensor-amsterdam created high alert Query with a rule id.', - [ALERT_RULE_UUID]: fullSignal[ALERT_RULE_UUID], - [ALERT_STATUS]: 'active', - [ALERT_UUID]: fullSignal[ALERT_UUID], - [ALERT_WORKFLOW_STATUS]: 'open', - [SPACE_IDS]: ['default'], - [VERSION]: fullSignal[VERSION], - threat: { - enrichments: get(fullSignal, 'threat.enrichments'), - }, - ...flattenWithPrefix(ALERT_RULE_NAMESPACE, { - actions: [], - author: [], - category: 'Indicator Match Rule', - consumer: 'siem', - created_at: createdRule.created_at, - created_by: 'elastic', - description: 'Detecting root and admin users', - enabled: true, - exceptions_list: [], - false_positives: [], - from: '1900-01-01T00:00:00.000Z', - immutable: false, - interval: '5m', - max_signals: 100, - name: 'Query with a rule id', - producer: 'siem', - references: [], - risk_score: 55, - risk_score_mapping: [], - rule_id: createdRule.rule_id, - rule_type_id: 'siem.indicatorRule', - severity: 'high', - severity_mapping: [], - tags: [], - threat: [], - to: 'now', - type: 'threat_match', - updated_at: fullSignal[ALERT_RULE_UPDATED_AT], - updated_by: 'elastic', - uuid: createdRule.id, - version: 1, - }), - }); - }); - - it('should return 0 matches if the mapping does not match against anything in the mapping', async () => { - const rule: RuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: '*:*', - threat_query: 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip - threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity - threat_mapping: [ - // We match host.name against host.name - { - entries: [ - { - field: 'host.name', - value: 'invalid.mapping.value', // invalid mapping value - type: 'mapping', - }, - ], - }, - ], - threat_filters: [], - }; - - const ruleResponse = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, ruleResponse.id); - const signalsOpen = await getSignalsByIds(supertest, log, [ruleResponse.id]); - expect(signalsOpen.hits.hits.length).equal(0); - }); - - it('should return 0 signals when using an AND and one of the clauses does not have data', async () => { - const rule: RuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: '*:*', - threat_query: 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip - threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity - threat_mapping: [ - { - entries: [ - { - field: 'source.ip', - value: 'source.ip', - type: 'mapping', - }, - { - field: 'source.ip', - value: 'destination.ip', // All records from the threat query do NOT have destination.ip, so those records that do not should drop this entire AND clause. - type: 'mapping', - }, - ], - }, - ], - threat_filters: [], - }; - - const ruleResponse = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, ruleResponse.id); - const signalsOpen = await getSignalsByIds(supertest, log, [ruleResponse.id]); - expect(signalsOpen.hits.hits.length).equal(0); - }); - - it('should return 0 signals when using an AND and one of the clauses has a made up value that does not exist', async () => { - const rule: RuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - type: 'threat_match', - index: ['auditbeat-*'], - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: '*:*', - threat_query: 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip - threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity - threat_mapping: [ - { - entries: [ - { - field: 'source.ip', - value: 'source.ip', - type: 'mapping', - }, - { - field: 'source.ip', - value: 'made.up.non.existent.field', // made up field should not match - type: 'mapping', - }, - ], - }, - ], - threat_filters: [], - }; - - const ruleResponse = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, ruleResponse.id); - const signalsOpen = await getSignalsByIds(supertest, log, [ruleResponse.id]); - expect(signalsOpen.hits.hits.length).equal(0); - }); - - describe('timeout behavior', () => { - // Flaky - it.skip('will return an error if a rule execution exceeds the rule interval', async () => { - const rule: RuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a short interval', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: '*:*', - threat_query: '*:*', // broad query to take more time - threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity - threat_mapping: [ - { - entries: [ - { - field: 'host.name', - value: 'host.name', - type: 'mapping', - }, - ], - }, - ], - threat_filters: [], - concurrent_searches: 1, - interval: '1s', // short interval - items_per_search: 1, // iterate only 1 threat item per loop to ensure we're slow - }; - - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id, RuleExecutionStatus.failed); - - const { body } = await supertest - .post(DETECTION_ENGINE_RULES_URL) - .set('kbn-xsrf', 'true') - .query({ id }) - .expect(200); - - // TODO: https://github.com/elastic/kibana/pull/121644 clean up, make type-safe - expect(body?.execution_summary?.last_execution.message).to.contain( - 'execution has exceeded its allotted interval' - ); - }); - }); - - describe('indicator enrichment: threat-first search', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/filebeat/threat_intel'); - }); - - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/filebeat/threat_intel'); - }); - - it('enriches signals with the single indicator that matched', async () => { - const rule: RuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: '*:*', // narrow events down to 2 with a destination.ip - threat_indicator_path: 'threat.indicator', - threat_query: 'threat.indicator.domain: 159.89.119.67', // narrow things down to indicators with a domain - threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module - threat_mapping: [ - { - entries: [ - { - value: 'threat.indicator.domain', - field: 'destination.ip', - type: 'mapping', - }, - ], - }, - ], - threat_filters: [], - }; - - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsByIds(supertest, log, [id]); - expect(signalsOpen.hits.hits.length).equal(2); - - const { hits } = signalsOpen.hits; - const threats = hits.map((hit) => hit._source?.threat); - expect(threats).to.eql([ - { - enrichments: [ - { - feed: {}, - indicator: { - description: "domain should match the auditbeat hosts' data's source.ip", - domain: '159.89.119.67', - first_seen: '2021-01-26T11:09:04.000Z', - provider: 'geenensp', - url: { - full: 'http://159.89.119.67:59600/bin.sh', - scheme: 'http', - }, - type: 'url', - }, - matched: { - atomic: '159.89.119.67', - id: '978783', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'destination.ip', - type: ENRICHMENT_TYPES.IndicatorMatchRule, - }, - }, - ], - }, - { - enrichments: [ - { - feed: {}, - indicator: { - description: "domain should match the auditbeat hosts' data's source.ip", - domain: '159.89.119.67', - first_seen: '2021-01-26T11:09:04.000Z', - provider: 'geenensp', - url: { - full: 'http://159.89.119.67:59600/bin.sh', - scheme: 'http', - }, - type: 'url', - }, - matched: { - atomic: '159.89.119.67', - id: '978783', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'destination.ip', - type: ENRICHMENT_TYPES.IndicatorMatchRule, - }, - }, - ], - }, - ]); - }); - - it('enriches signals with multiple indicators if several matched', async () => { - const rule: RuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: 'NOT source.port:35326', // specify query to have signals more than treat indicators, but only 1 will match - threat_indicator_path: 'threat.indicator', - threat_query: 'threat.indicator.ip: *', - threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module - threat_mapping: [ - { - entries: [ - { - value: 'threat.indicator.ip', - field: 'source.ip', - type: 'mapping', - }, - ], - }, - ], - threat_filters: [], - }; - - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsByIds(supertest, log, [id]); - expect(signalsOpen.hits.hits.length).equal(1); - - const { hits } = signalsOpen.hits; - const [threat] = hits.map((hit) => hit._source?.threat) as Array<{ - enrichments: unknown[]; - }>; - - assertContains(threat.enrichments, [ - { - feed: {}, - indicator: { - description: 'this should match auditbeat/hosts on both port and ip', - first_seen: '2021-01-26T11:06:03.000Z', - ip: '45.115.45.3', - port: 57324, - provider: 'geenensp', - type: 'url', - }, - matched: { - atomic: '45.115.45.3', - id: '978785', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'source.ip', - type: ENRICHMENT_TYPES.IndicatorMatchRule, - }, - }, - { - feed: {}, - indicator: { - description: 'this should match auditbeat/hosts on ip', - first_seen: '2021-01-26T11:06:03.000Z', - ip: '45.115.45.3', - provider: 'other_provider', - type: 'ip', - }, - - matched: { - atomic: '45.115.45.3', - id: '978787', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'source.ip', - type: ENRICHMENT_TYPES.IndicatorMatchRule, - }, - }, - ]); - }); - - it('adds a single indicator that matched multiple fields', async () => { - const rule: RuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: 'NOT source.port:35326', // specify query to have signals more than treat indicators, but only 1 will match - threat_indicator_path: 'threat.indicator', - threat_query: 'threat.indicator.port: 57324 or threat.indicator.ip:45.115.45.3', // narrow our query to a single indicator - threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module - threat_mapping: [ - { - entries: [ - { - value: 'threat.indicator.port', - field: 'source.port', - type: 'mapping', - }, - ], - }, - { - entries: [ - { - value: 'threat.indicator.ip', - field: 'source.ip', - type: 'mapping', - }, - ], - }, - ], - threat_filters: [], - }; - - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsByIds(supertest, log, [id]); - expect(signalsOpen.hits.hits.length).equal(1); - - const { hits } = signalsOpen.hits; - const [threat] = hits.map((hit) => hit._source?.threat) as Array<{ - enrichments: unknown[]; - }>; - - assertContains(threat.enrichments, [ - { - feed: {}, - indicator: { - description: 'this should match auditbeat/hosts on both port and ip', - first_seen: '2021-01-26T11:06:03.000Z', - ip: '45.115.45.3', - port: 57324, - provider: 'geenensp', - type: 'url', - }, - matched: { - atomic: '45.115.45.3', - id: '978785', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'source.ip', - type: ENRICHMENT_TYPES.IndicatorMatchRule, - }, - }, - // We do not merge matched indicators during enrichment, so in - // certain circumstances a given indicator document could appear - // multiple times in an enriched alert (albeit with different - // threat.indicator.matched data). That's the case with the - // first and third indicators matched, here. - { - feed: {}, - indicator: { - description: 'this should match auditbeat/hosts on both port and ip', - first_seen: '2021-01-26T11:06:03.000Z', - ip: '45.115.45.3', - port: 57324, - provider: 'geenensp', - type: 'url', - }, - - matched: { - atomic: 57324, - id: '978785', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'source.port', - type: ENRICHMENT_TYPES.IndicatorMatchRule, - }, - }, - { - feed: {}, - indicator: { - description: 'this should match auditbeat/hosts on ip', - first_seen: '2021-01-26T11:06:03.000Z', - ip: '45.115.45.3', - provider: 'other_provider', - type: 'ip', - }, - matched: { - atomic: '45.115.45.3', - id: '978787', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'source.ip', - type: ENRICHMENT_TYPES.IndicatorMatchRule, - }, - }, - ]); - }); - - it('generates multiple signals with multiple matches', async () => { - const rule: RuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - threat_language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: '*:*', // narrow our query to a single record that matches two indicators - threat_indicator_path: 'threat.indicator', - threat_query: '*:*', - threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module - threat_mapping: [ - { - entries: [ - { - value: 'threat.indicator.port', - field: 'source.port', - type: 'mapping', - }, - { - value: 'threat.indicator.ip', - field: 'source.ip', - type: 'mapping', - }, - ], - }, - { - entries: [ - { - value: 'threat.indicator.domain', - field: 'destination.ip', - type: 'mapping', - }, - ], - }, - ], - threat_filters: [], - }; - - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsByIds(supertest, log, [id]); - expect(signalsOpen.hits.hits.length).equal(2); - - const { hits } = signalsOpen.hits; - const threats = hits.map((hit) => hit._source?.threat) as Array<{ - enrichments: unknown[]; - }>; - - assertContains(threats[0].enrichments, [ - { - feed: {}, - indicator: { - description: "domain should match the auditbeat hosts' data's source.ip", - domain: '159.89.119.67', - first_seen: '2021-01-26T11:09:04.000Z', - provider: 'geenensp', - type: 'url', - url: { - full: 'http://159.89.119.67:59600/bin.sh', - scheme: 'http', - }, - }, - matched: { - atomic: '159.89.119.67', - id: '978783', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'destination.ip', - type: ENRICHMENT_TYPES.IndicatorMatchRule, - }, - }, - { - feed: {}, - indicator: { - description: 'this should match auditbeat/hosts on both port and ip', - first_seen: '2021-01-26T11:06:03.000Z', - ip: '45.115.45.3', - port: 57324, - provider: 'geenensp', - type: 'url', - }, - matched: { - atomic: '45.115.45.3', - id: '978785', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'source.ip', - type: ENRICHMENT_TYPES.IndicatorMatchRule, - }, - }, - { - feed: {}, - indicator: { - description: 'this should match auditbeat/hosts on both port and ip', - first_seen: '2021-01-26T11:06:03.000Z', - ip: '45.115.45.3', - port: 57324, - provider: 'geenensp', - type: 'url', - }, - matched: { - atomic: 57324, - id: '978785', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'source.port', - type: ENRICHMENT_TYPES.IndicatorMatchRule, - }, - }, - ]); - - assertContains(threats[1].enrichments, [ - { - feed: {}, - indicator: { - description: "domain should match the auditbeat hosts' data's source.ip", - domain: '159.89.119.67', - first_seen: '2021-01-26T11:09:04.000Z', - provider: 'geenensp', - type: 'url', - url: { - full: 'http://159.89.119.67:59600/bin.sh', - scheme: 'http', - }, - }, - matched: { - atomic: '159.89.119.67', - id: '978783', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'destination.ip', - type: ENRICHMENT_TYPES.IndicatorMatchRule, - }, - }, - ]); - }); - }); - - describe('indicator enrichment: event-first search', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/filebeat/threat_intel'); - }); - - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/filebeat/threat_intel'); - }); - - it('enriches signals with the single indicator that matched', async () => { - const rule: RuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: 'destination.ip:159.89.119.67', - threat_indicator_path: 'threat.indicator', - threat_query: 'threat.indicator.domain: *', // narrow things down to indicators with a domain - threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module - threat_mapping: [ - { - entries: [ - { - value: 'threat.indicator.domain', - field: 'destination.ip', - type: 'mapping', - }, - ], - }, - ], - threat_filters: [], - }; - - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsByIds(supertest, log, [id]); - expect(signalsOpen.hits.hits.length).equal(2); - - const { hits } = signalsOpen.hits; - const threats = hits.map((hit) => hit._source?.threat); - expect(threats).to.eql([ - { - enrichments: [ - { - feed: {}, - indicator: { - description: "domain should match the auditbeat hosts' data's source.ip", - domain: '159.89.119.67', - first_seen: '2021-01-26T11:09:04.000Z', - provider: 'geenensp', - url: { - full: 'http://159.89.119.67:59600/bin.sh', - scheme: 'http', - }, - type: 'url', - }, - matched: { - atomic: '159.89.119.67', - id: '978783', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'destination.ip', - type: ENRICHMENT_TYPES.IndicatorMatchRule, - }, - }, - ], - }, - { - enrichments: [ - { - feed: {}, - indicator: { - description: "domain should match the auditbeat hosts' data's source.ip", - domain: '159.89.119.67', - first_seen: '2021-01-26T11:09:04.000Z', - provider: 'geenensp', - url: { - full: 'http://159.89.119.67:59600/bin.sh', - scheme: 'http', - }, - type: 'url', - }, - matched: { - atomic: '159.89.119.67', - id: '978783', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'destination.ip', - type: ENRICHMENT_TYPES.IndicatorMatchRule, - }, - }, - ], - }, - ]); - }); - - it('enriches signals with multiple indicators if several matched', async () => { - const rule: RuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: 'source.port: 57324', // narrow our query to a single record that matches two indicators - threat_indicator_path: 'threat.indicator', - threat_query: 'threat.indicator.ip: *', - threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module - threat_mapping: [ - { - entries: [ - { - value: 'threat.indicator.ip', - field: 'source.ip', - type: 'mapping', - }, - ], - }, - ], - threat_filters: [], - }; - - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsByIds(supertest, log, [id]); - expect(signalsOpen.hits.hits.length).equal(1); - - const { hits } = signalsOpen.hits; - const [threat] = hits.map((hit) => hit._source?.threat) as Array<{ - enrichments: unknown[]; - }>; - - assertContains(threat.enrichments, [ - { - feed: {}, - indicator: { - description: 'this should match auditbeat/hosts on both port and ip', - first_seen: '2021-01-26T11:06:03.000Z', - ip: '45.115.45.3', - port: 57324, - provider: 'geenensp', - type: 'url', - }, - matched: { - atomic: '45.115.45.3', - id: '978785', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'source.ip', - type: ENRICHMENT_TYPES.IndicatorMatchRule, - }, - }, - { - feed: {}, - indicator: { - description: 'this should match auditbeat/hosts on ip', - first_seen: '2021-01-26T11:06:03.000Z', - ip: '45.115.45.3', - provider: 'other_provider', - type: 'ip', - }, - - matched: { - atomic: '45.115.45.3', - id: '978787', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'source.ip', - type: ENRICHMENT_TYPES.IndicatorMatchRule, - }, - }, - ]); - }); - - it('adds a single indicator that matched multiple fields', async () => { - const rule: RuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: 'source.port: 57324', // narrow our query to a single record that matches two indicators - threat_indicator_path: 'threat.indicator', - threat_query: 'threat.indicator.ip: *', - threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module - threat_mapping: [ - { - entries: [ - { - value: 'threat.indicator.port', - field: 'source.port', - type: 'mapping', - }, - ], - }, - { - entries: [ - { - value: 'threat.indicator.ip', - field: 'source.ip', - type: 'mapping', - }, - ], - }, - ], - threat_filters: [], - }; - - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsByIds(supertest, log, [id]); - expect(signalsOpen.hits.hits.length).equal(1); - - const { hits } = signalsOpen.hits; - const [threat] = hits.map((hit) => hit._source?.threat) as Array<{ - enrichments: unknown[]; - }>; - - assertContains(threat.enrichments, [ - { - feed: {}, - indicator: { - description: 'this should match auditbeat/hosts on both port and ip', - first_seen: '2021-01-26T11:06:03.000Z', - ip: '45.115.45.3', - port: 57324, - provider: 'geenensp', - type: 'url', - }, - matched: { - atomic: '45.115.45.3', - id: '978785', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'source.ip', - type: ENRICHMENT_TYPES.IndicatorMatchRule, - }, - }, - // We do not merge matched indicators during enrichment, so in - // certain circumstances a given indicator document could appear - // multiple times in an enriched alert (albeit with different - // threat.indicator.matched data). That's the case with the - // first and third indicators matched, here. - { - feed: {}, - indicator: { - description: 'this should match auditbeat/hosts on both port and ip', - first_seen: '2021-01-26T11:06:03.000Z', - ip: '45.115.45.3', - port: 57324, - provider: 'geenensp', - type: 'url', - }, - - matched: { - atomic: 57324, - id: '978785', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'source.port', - type: ENRICHMENT_TYPES.IndicatorMatchRule, - }, - }, - { - feed: {}, - indicator: { - description: 'this should match auditbeat/hosts on ip', - first_seen: '2021-01-26T11:06:03.000Z', - ip: '45.115.45.3', - provider: 'other_provider', - type: 'ip', - }, - matched: { - atomic: '45.115.45.3', - id: '978787', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'source.ip', - type: ENRICHMENT_TYPES.IndicatorMatchRule, - }, - }, - ]); - }); - - it('generates multiple signals with multiple matches', async () => { - const rule: RuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - threat_language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: '(source.port:57324 and source.ip:45.115.45.3) or destination.ip:159.89.119.67', // narrow our query to a single record that matches two indicators - threat_indicator_path: 'threat.indicator', - threat_query: '*:*', - threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module - threat_mapping: [ - { - entries: [ - { - value: 'threat.indicator.port', - field: 'source.port', - type: 'mapping', - }, - { - value: 'threat.indicator.ip', - field: 'source.ip', - type: 'mapping', - }, - ], - }, - { - entries: [ - { - value: 'threat.indicator.domain', - field: 'destination.ip', - type: 'mapping', - }, - ], - }, - ], - threat_filters: [], - }; - - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsByIds(supertest, log, [id]); - expect(signalsOpen.hits.hits.length).equal(2); - - const { hits } = signalsOpen.hits; - const threats = hits.map((hit) => hit._source?.threat) as Array<{ - enrichments: unknown[]; - }>; - - assertContains(threats[0].enrichments, [ - { - feed: {}, - indicator: { - description: "domain should match the auditbeat hosts' data's source.ip", - domain: '159.89.119.67', - first_seen: '2021-01-26T11:09:04.000Z', - provider: 'geenensp', - type: 'url', - url: { - full: 'http://159.89.119.67:59600/bin.sh', - scheme: 'http', - }, - }, - matched: { - atomic: '159.89.119.67', - id: '978783', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'destination.ip', - type: ENRICHMENT_TYPES.IndicatorMatchRule, - }, - }, - { - feed: {}, - indicator: { - description: 'this should match auditbeat/hosts on both port and ip', - first_seen: '2021-01-26T11:06:03.000Z', - ip: '45.115.45.3', - port: 57324, - provider: 'geenensp', - type: 'url', - }, - matched: { - atomic: '45.115.45.3', - id: '978785', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'source.ip', - type: ENRICHMENT_TYPES.IndicatorMatchRule, - }, - }, - { - feed: {}, - indicator: { - description: 'this should match auditbeat/hosts on both port and ip', - first_seen: '2021-01-26T11:06:03.000Z', - ip: '45.115.45.3', - port: 57324, - provider: 'geenensp', - type: 'url', - }, - matched: { - atomic: 57324, - id: '978785', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'source.port', - type: ENRICHMENT_TYPES.IndicatorMatchRule, - }, - }, - ]); - - assertContains(threats[1].enrichments, [ - { - feed: {}, - indicator: { - description: "domain should match the auditbeat hosts' data's source.ip", - domain: '159.89.119.67', - first_seen: '2021-01-26T11:09:04.000Z', - provider: 'geenensp', - type: 'url', - url: { - full: 'http://159.89.119.67:59600/bin.sh', - scheme: 'http', - }, - }, - matched: { - atomic: '159.89.119.67', - id: '978783', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'destination.ip', - type: ENRICHMENT_TYPES.IndicatorMatchRule, - }, - }, - ]); - }); - }); - - describe('alerts should be be enriched', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/entity/host_risk'); - }); - - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/entity/host_risk'); - }); - - it('should be enriched with host risk score', async () => { - const rule: RuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: '*:*', - threat_query: 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip - threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity - threat_mapping: [ - // We match host.name against host.name - { - entries: [ - { - field: 'host.name', - value: 'host.name', - type: 'mapping', - }, - ], - }, - ], - threat_filters: [], - }; - - const createdRule = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, createdRule.id); - await waitForSignalsToBePresent(supertest, log, 10, [createdRule.id]); - const signalsOpen = await getSignalsByIds(supertest, log, [createdRule.id]); - expect(signalsOpen.hits.hits.length).equal(10); - const fullSource = signalsOpen.hits.hits.find( - (signal) => - (signal._source?.[ALERT_ANCESTORS] as Ancestor[])[0].id === '7yJ-B2kBR346wHgnhlMn' - ); - const fullSignal = fullSource?._source; - if (!fullSignal) { - return expect(fullSignal).to.be.ok(); - } - - expect(fullSignal?.host?.risk?.calculated_level).to.eql('Critical'); - expect(fullSignal?.host?.risk?.calculated_score_norm).to.eql(70); - }); - }); - }); - }); -}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/generating_signals.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/generating_signals.ts deleted file mode 100644 index 60e4cef77c89..000000000000 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/generating_signals.ts +++ /dev/null @@ -1,1554 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import expect from '@kbn/expect'; -import { - ALERT_REASON, - ALERT_RISK_SCORE, - ALERT_RULE_NAME, - ALERT_RULE_PARAMETERS, - ALERT_RULE_RULE_ID, - ALERT_RULE_RULE_NAME_OVERRIDE, - ALERT_RULE_UUID, - ALERT_SEVERITY, - ALERT_WORKFLOW_STATUS, - EVENT_ACTION, - EVENT_KIND, -} from '@kbn/rule-data-utils'; -import { flattenWithPrefix } from '@kbn/securitysolution-rules'; - -import { orderBy, get } from 'lodash'; - -import { RuleExecutionStatus } from '@kbn/security-solution-plugin/common/detection_engine/rule_monitoring'; -import { - EqlRuleCreateProps, - QueryRuleCreateProps, - SavedQueryRuleCreateProps, - ThresholdRuleCreateProps, -} from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; -import { Ancestor } from '@kbn/security-solution-plugin/server/lib/detection_engine/signals/types'; -import { - ALERT_ANCESTORS, - ALERT_DEPTH, - ALERT_ORIGINAL_TIME, - ALERT_ORIGINAL_EVENT, - ALERT_ORIGINAL_EVENT_CATEGORY, - ALERT_GROUP_ID, - ALERT_THRESHOLD_RESULT, -} from '@kbn/security-solution-plugin/common/field_maps/field_names'; -import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; -import { - createRule, - createSignalsIndex, - deleteAllAlerts, - deleteSignalsIndex, - getEqlRuleForSignalTesting, - getOpenSignals, - getRuleForSignalTesting, - getSignalsByIds, - getSignalsByRuleIds, - getSimpleRule, - getThresholdRuleForSignalTesting, - waitForRuleSuccessOrStatus, - waitForSignalsToBePresent, -} from '../../utils'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; - -/** - * Specific _id to use for some of the tests. If the archiver changes and you see errors - * here, update this to a new value of a chosen auditbeat record and update the tests values. - */ -export const ID = 'BhbXBmkBR346wHgn4PeZ'; - -// eslint-disable-next-line import/no-default-export -export default ({ getService }: FtrProviderContext) => { - const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - const es = getService('es'); - const log = getService('log'); - - describe('Generating signals from source indexes', () => { - beforeEach(async () => { - await deleteSignalsIndex(supertest, log); - await createSignalsIndex(supertest, log); - }); - - afterEach(async () => { - await deleteSignalsIndex(supertest, log); - await deleteAllAlerts(supertest, log); - }); - - describe('Signals from audit beat are of the expected structure', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); - }); - - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); - }); - - it('should have the specific audit record for _id or none of these tests below will pass', async () => { - const rule: QueryRuleCreateProps = { - ...getRuleForSignalTesting(['auditbeat-*']), - query: `_id:${ID}`, - }; - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsByIds(supertest, log, [id]); - expect(signalsOpen.hits.hits.length).greaterThan(0); - }); - - it('should abide by max_signals > 100', async () => { - const maxSignals = 500; - const rule: QueryRuleCreateProps = { - ...getRuleForSignalTesting(['auditbeat-*']), - max_signals: maxSignals, - }; - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, maxSignals, [id]); - const signalsOpen = await getSignalsByIds(supertest, log, [id], maxSignals); - expect(signalsOpen.hits.hits.length).equal(maxSignals); - }); - - it('should have recorded the rule_id within the signal', async () => { - const rule: QueryRuleCreateProps = { - ...getRuleForSignalTesting(['auditbeat-*']), - query: `_id:${ID}`, - }; - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsByIds(supertest, log, [id]); - expect(signalsOpen.hits.hits[0]._source![ALERT_RULE_RULE_ID]).eql(getSimpleRule().rule_id); - }); - - it('should query and get back expected signal structure using a basic KQL query', async () => { - const rule: QueryRuleCreateProps = { - ...getRuleForSignalTesting(['auditbeat-*']), - query: `_id:${ID}`, - }; - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsByIds(supertest, log, [id]); - const signal = signalsOpen.hits.hits[0]._source!; - - expect(signal).eql({ - ...signal, - [ALERT_ANCESTORS]: [ - { - id: 'BhbXBmkBR346wHgn4PeZ', - type: 'event', - index: 'auditbeat-8.0.0-2019.02.19-000001', - depth: 0, - }, - ], - [ALERT_WORKFLOW_STATUS]: 'open', - [ALERT_DEPTH]: 1, - [ALERT_ORIGINAL_TIME]: '2019-02-19T17:40:03.790Z', - ...flattenWithPrefix(ALERT_ORIGINAL_EVENT, { - action: 'socket_closed', - dataset: 'socket', - kind: 'event', - module: 'system', - }), - }); - }); - - it('should query and get back expected signal structure using a saved query rule', async () => { - const rule: SavedQueryRuleCreateProps = { - ...getRuleForSignalTesting(['auditbeat-*']), - type: 'saved_query', - query: `_id:${ID}`, - saved_id: 'doesnt-exist', - }; - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsByIds(supertest, log, [id]); - const signal = signalsOpen.hits.hits[0]._source!; - expect(signal).eql({ - ...signal, - [ALERT_ANCESTORS]: [ - { - id: 'BhbXBmkBR346wHgn4PeZ', - type: 'event', - index: 'auditbeat-8.0.0-2019.02.19-000001', - depth: 0, - }, - ], - [ALERT_WORKFLOW_STATUS]: 'open', - [ALERT_DEPTH]: 1, - [ALERT_ORIGINAL_TIME]: '2019-02-19T17:40:03.790Z', - ...flattenWithPrefix(ALERT_ORIGINAL_EVENT, { - action: 'socket_closed', - dataset: 'socket', - kind: 'event', - module: 'system', - }), - }); - }); - - it('should query and get back expected signal structure when it is a signal on a signal', async () => { - const rule: QueryRuleCreateProps = { - ...getRuleForSignalTesting(['auditbeat-*']), - query: `_id:${ID}`, - }; - const { id: createdId } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, createdId); - await waitForSignalsToBePresent(supertest, log, 1, [createdId]); - - // Run signals on top of that 1 signal which should create a single signal (on top of) a signal - const ruleForSignals: QueryRuleCreateProps = { - ...getRuleForSignalTesting([`.alerts-security.alerts-default*`]), - rule_id: 'signal-on-signal', - }; - - const { id } = await createRule(supertest, log, ruleForSignals); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - - // Get our single signal on top of a signal - const signalsOpen = await getSignalsByRuleIds(supertest, log, ['signal-on-signal']); - - const signal = signalsOpen.hits.hits[0]._source!; - expect(signal).eql({ - ...signal, - [ALERT_ANCESTORS]: [ - { - id: 'BhbXBmkBR346wHgn4PeZ', - type: 'event', - index: 'auditbeat-8.0.0-2019.02.19-000001', - depth: 0, - }, - { - ...(signal[ALERT_ANCESTORS] as Ancestor[])[1], - type: 'signal', - index: '.internal.alerts-security.alerts-default-000001', - depth: 1, - }, - ], - [ALERT_WORKFLOW_STATUS]: 'open', - [ALERT_DEPTH]: 2, - [ALERT_ORIGINAL_TIME]: signal[ALERT_ORIGINAL_TIME], // original_time will always be changing sine it's based on a signal created here, so skip testing it - ...flattenWithPrefix(ALERT_ORIGINAL_EVENT, { - action: 'socket_closed', - dataset: 'socket', - kind: 'signal', - module: 'system', - }), - }); - }); - - describe('EQL Rules', () => { - before(async () => { - await esArchiver.load( - 'x-pack/test/functional/es_archives/security_solution/timestamp_override_6' - ); - }); - - after(async () => { - await esArchiver.unload( - 'x-pack/test/functional/es_archives/security_solution/timestamp_override_6' - ); - }); - - it('generates a correctly formatted signal from EQL non-sequence queries', async () => { - const rule: EqlRuleCreateProps = { - ...getEqlRuleForSignalTesting(['auditbeat-*']), - query: 'configuration where agent.id=="a1d7b39c-f898-4dbe-a761-efb61939302d"', - }; - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signals = await getSignalsByIds(supertest, log, [id]); - expect(signals.hits.hits.length).eql(1); - const fullSignal = signals.hits.hits[0]._source; - if (!fullSignal) { - return expect(fullSignal).to.be.ok(); - } - - expect(fullSignal).eql({ - ...fullSignal, - agent: { - ephemeral_id: '0010d67a-14f7-41da-be30-489fea735967', - hostname: 'suricata-zeek-sensor-toronto', - id: 'a1d7b39c-f898-4dbe-a761-efb61939302d', - type: 'auditbeat', - version: '8.0.0', - }, - auditd: { - data: { - audit_enabled: '1', - old: '1', - }, - message_type: 'config_change', - result: 'success', - sequence: 1496, - session: 'unset', - summary: { - actor: { - primary: 'unset', - }, - object: { - primary: '1', - type: 'audit-config', - }, - }, - }, - cloud: { - instance: { - id: '133555295', - }, - provider: 'digitalocean', - region: 'tor1', - }, - ecs: { - version: '1.0.0-beta2', - }, - ...flattenWithPrefix('event', { - action: 'changed-audit-configuration', - category: 'configuration', - module: 'auditd', - kind: 'signal', - }), - host: { - architecture: 'x86_64', - containerized: false, - hostname: 'suricata-zeek-sensor-toronto', - id: '8cc95778cce5407c809480e8e32ad76b', - name: 'suricata-zeek-sensor-toronto', - os: { - codename: 'bionic', - family: 'debian', - kernel: '4.15.0-45-generic', - name: 'Ubuntu', - platform: 'ubuntu', - version: '18.04.2 LTS (Bionic Beaver)', - }, - }, - service: { - type: 'auditd', - }, - user: { - audit: { - id: 'unset', - }, - }, - [ALERT_REASON]: - 'configuration event on suricata-zeek-sensor-toronto created high alert Signal Testing Query.', - [ALERT_RULE_UUID]: fullSignal[ALERT_RULE_UUID], - [ALERT_ORIGINAL_TIME]: fullSignal[ALERT_ORIGINAL_TIME], - [ALERT_WORKFLOW_STATUS]: 'open', - [ALERT_DEPTH]: 1, - [ALERT_ANCESTORS]: [ - { - depth: 0, - id: '9xbRBmkBR346wHgngz2D', - index: 'auditbeat-8.0.0-2019.02.19-000001', - type: 'event', - }, - ], - ...flattenWithPrefix(ALERT_ORIGINAL_EVENT, { - action: 'changed-audit-configuration', - category: 'configuration', - module: 'auditd', - }), - }); - }); - - it('generates up to max_signals for non-sequence EQL queries', async () => { - const rule: EqlRuleCreateProps = getEqlRuleForSignalTesting(['auditbeat-*']); - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 100, [id]); - const signals = await getSignalsByIds(supertest, log, [id], 1000); - const filteredSignals = signals.hits.hits.filter( - (signal) => signal._source?.[ALERT_DEPTH] === 1 - ); - expect(filteredSignals.length).eql(100); - }); - - it('uses the provided event_category_override', async () => { - const rule: EqlRuleCreateProps = { - ...getEqlRuleForSignalTesting(['auditbeat-*']), - query: 'config_change where agent.id=="a1d7b39c-f898-4dbe-a761-efb61939302d"', - event_category_override: 'auditd.message_type', - }; - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signals = await getSignalsByIds(supertest, log, [id]); - expect(signals.hits.hits.length).eql(1); - const fullSignal = signals.hits.hits[0]._source; - if (!fullSignal) { - return expect(fullSignal).to.be.ok(); - } - - expect(fullSignal).eql({ - ...fullSignal, - auditd: { - data: { - audit_enabled: '1', - old: '1', - }, - message_type: 'config_change', - result: 'success', - sequence: 1496, - session: 'unset', - summary: { - actor: { - primary: 'unset', - }, - object: { - primary: '1', - type: 'audit-config', - }, - }, - }, - ...flattenWithPrefix('event', { - action: 'changed-audit-configuration', - category: 'configuration', - module: 'auditd', - kind: 'signal', - }), - service: { - type: 'auditd', - }, - user: { - audit: { - id: 'unset', - }, - }, - [ALERT_REASON]: - 'configuration event on suricata-zeek-sensor-toronto created high alert Signal Testing Query.', - [ALERT_RULE_UUID]: fullSignal[ALERT_RULE_UUID], - [ALERT_ORIGINAL_TIME]: fullSignal[ALERT_ORIGINAL_TIME], - [ALERT_WORKFLOW_STATUS]: 'open', - [ALERT_DEPTH]: 1, - [ALERT_ANCESTORS]: [ - { - depth: 0, - id: '9xbRBmkBR346wHgngz2D', - index: 'auditbeat-8.0.0-2019.02.19-000001', - type: 'event', - }, - ], - ...flattenWithPrefix(ALERT_ORIGINAL_EVENT, { - action: 'changed-audit-configuration', - category: 'configuration', - module: 'auditd', - }), - }); - }); - - it('uses the provided timestamp_field', async () => { - const rule: EqlRuleCreateProps = { - ...getEqlRuleForSignalTesting(['fake.index.1']), - query: 'any where true', - timestamp_field: 'created_at', - }; - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signals = await getSignalsByIds(supertest, log, [id]); - expect(signals.hits.hits.length).eql(3); - - const createdAtHits = signals.hits.hits.map((hit) => hit._source?.created_at); - expect(createdAtHits).to.eql([1622676785, 1622676790, 1622676795]); - }); - - it('uses the provided tiebreaker_field', async () => { - const rule: EqlRuleCreateProps = { - ...getEqlRuleForSignalTesting(['fake.index.1']), - query: 'any where true', - tiebreaker_field: 'locale', - }; - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signals = await getSignalsByIds(supertest, log, [id]); - expect(signals.hits.hits.length).eql(3); - - const createdAtHits = signals.hits.hits.map((hit) => hit._source?.locale); - expect(createdAtHits).to.eql(['es', 'pt', 'ua']); - }); - - it('generates building block signals from EQL sequences in the expected form', async () => { - const rule: EqlRuleCreateProps = { - ...getEqlRuleForSignalTesting(['auditbeat-*']), - query: 'sequence by host.name [anomoly where true] [any where true]', // TODO: spelling - }; - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signals = await getSignalsByIds(supertest, log, [id]); - const buildingBlock = signals.hits.hits.find( - (signal) => - signal._source?.[ALERT_DEPTH] === 1 && - get(signal._source, ALERT_ORIGINAL_EVENT_CATEGORY) === 'anomoly' - ); - expect(buildingBlock).not.eql(undefined); - const fullSignal = buildingBlock?._source; - if (!fullSignal) { - return expect(fullSignal).to.be.ok(); - } - - expect(fullSignal).eql({ - ...fullSignal, - agent: { - ephemeral_id: '1b4978a0-48be-49b1-ac96-323425b389ab', - hostname: 'zeek-sensor-amsterdam', - id: 'e52588e6-7aa3-4c89-a2c4-d6bc5c286db1', - type: 'auditbeat', - version: '8.0.0', - }, - auditd: { - data: { - a0: '3', - a1: '107', - a2: '1', - a3: '7ffc186b58e0', - arch: 'x86_64', - auid: 'unset', - dev: 'eth0', - exit: '0', - gid: '0', - old_prom: '0', - prom: '256', - ses: 'unset', - syscall: 'setsockopt', - tty: '(none)', - uid: '0', - }, - message_type: 'anom_promiscuous', - result: 'success', - sequence: 1392, - session: 'unset', - summary: { - actor: { - primary: 'unset', - secondary: 'root', - }, - how: '/usr/bin/bro', - object: { - primary: 'eth0', - type: 'network-device', - }, - }, - }, - cloud: { instance: { id: '133551048' }, provider: 'digitalocean', region: 'ams3' }, - ecs: { version: '1.0.0-beta2' }, - ...flattenWithPrefix('event', { - action: 'changed-promiscuous-mode-on-device', - category: 'anomoly', - module: 'auditd', - kind: 'signal', - }), - host: { - architecture: 'x86_64', - containerized: false, - hostname: 'zeek-sensor-amsterdam', - id: '2ce8b1e7d69e4a1d9c6bcddc473da9d9', - name: 'zeek-sensor-amsterdam', - os: { - codename: 'bionic', - family: 'debian', - kernel: '4.15.0-45-generic', - name: 'Ubuntu', - platform: 'ubuntu', - version: '18.04.2 LTS (Bionic Beaver)', - }, - }, - process: { - executable: '/usr/bin/bro', - name: 'bro', - pid: 30157, - ppid: 30151, - title: - '/usr/bin/bro -i eth0 -U .status -p broctl -p broctl-live -p standalone -p local -p bro local.bro broctl broctl/standalone broctl', - }, - service: { type: 'auditd' }, - user: { - audit: { id: 'unset' }, - effective: { - group: { - id: '0', - name: 'root', - }, - id: '0', - name: 'root', - }, - filesystem: { - group: { - id: '0', - name: 'root', - }, - id: '0', - name: 'root', - }, - group: { id: '0', name: 'root' }, - id: '0', - name: 'root', - saved: { - group: { - id: '0', - name: 'root', - }, - id: '0', - name: 'root', - }, - }, - [ALERT_REASON]: - 'anomoly event with process bro, by root on zeek-sensor-amsterdam created high alert Signal Testing Query.', - [ALERT_RULE_UUID]: fullSignal[ALERT_RULE_UUID], - [ALERT_GROUP_ID]: fullSignal[ALERT_GROUP_ID], - [ALERT_ORIGINAL_TIME]: fullSignal[ALERT_ORIGINAL_TIME], - [ALERT_WORKFLOW_STATUS]: 'open', - [ALERT_DEPTH]: 1, - [ALERT_ANCESTORS]: [ - { - depth: 0, - id: 'VhXOBmkBR346wHgnLP8T', - index: 'auditbeat-8.0.0-2019.02.19-000001', - type: 'event', - }, - ], - ...flattenWithPrefix(ALERT_ORIGINAL_EVENT, { - action: 'changed-promiscuous-mode-on-device', - category: 'anomoly', - module: 'auditd', - }), - }); - }); - - it('generates shell signals from EQL sequences in the expected form', async () => { - const rule: EqlRuleCreateProps = { - ...getEqlRuleForSignalTesting(['auditbeat-*']), - query: 'sequence by host.name [anomoly where true] [any where true]', - }; - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsByIds(supertest, log, [id]); - const sequenceSignal = signalsOpen.hits.hits.find( - (signal) => signal._source?.[ALERT_DEPTH] === 2 - ); - const source = sequenceSignal?._source; - if (!source) { - return expect(source).to.be.ok(); - } - const eventIds = (source?.[ALERT_ANCESTORS] as Ancestor[]) - .filter((event) => event.depth === 1) - .map((event) => event.id); - expect(source).eql({ - ...source, - agent: { - ephemeral_id: '1b4978a0-48be-49b1-ac96-323425b389ab', - hostname: 'zeek-sensor-amsterdam', - id: 'e52588e6-7aa3-4c89-a2c4-d6bc5c286db1', - type: 'auditbeat', - version: '8.0.0', - }, - auditd: { session: 'unset', summary: { actor: { primary: 'unset' } } }, - cloud: { instance: { id: '133551048' }, provider: 'digitalocean', region: 'ams3' }, - ecs: { version: '1.0.0-beta2' }, - [EVENT_KIND]: 'signal', - host: { - architecture: 'x86_64', - containerized: false, - hostname: 'zeek-sensor-amsterdam', - id: '2ce8b1e7d69e4a1d9c6bcddc473da9d9', - name: 'zeek-sensor-amsterdam', - os: { - codename: 'bionic', - family: 'debian', - kernel: '4.15.0-45-generic', - name: 'Ubuntu', - platform: 'ubuntu', - version: '18.04.2 LTS (Bionic Beaver)', - }, - }, - service: { type: 'auditd' }, - user: { audit: { id: 'unset' }, id: '0', name: 'root' }, - [ALERT_WORKFLOW_STATUS]: 'open', - [ALERT_DEPTH]: 2, - [ALERT_GROUP_ID]: source[ALERT_GROUP_ID], - [ALERT_REASON]: - 'event by root on zeek-sensor-amsterdam created high alert Signal Testing Query.', - [ALERT_RULE_UUID]: source[ALERT_RULE_UUID], - [ALERT_ANCESTORS]: [ - { - depth: 0, - id: 'VhXOBmkBR346wHgnLP8T', - index: 'auditbeat-8.0.0-2019.02.19-000001', - type: 'event', - }, - { - depth: 1, - id: eventIds[0], - index: '', - rule: source[ALERT_RULE_UUID], - type: 'signal', - }, - { - depth: 0, - id: '4hbXBmkBR346wHgn6fdp', - index: 'auditbeat-8.0.0-2019.02.19-000001', - type: 'event', - }, - { - depth: 1, - id: eventIds[1], - index: '', - rule: source[ALERT_RULE_UUID], - type: 'signal', - }, - ], - }); - }); - - it('generates up to max_signals with an EQL rule', async () => { - const rule: EqlRuleCreateProps = { - ...getEqlRuleForSignalTesting(['auditbeat-*']), - query: 'sequence by host.name [any where true] [any where true]', - max_signals: 200, - }; - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - // For EQL rules, max_signals is the maximum number of detected sequences: each sequence has a building block - // alert for each event in the sequence, so max_signals=200 results in 400 building blocks in addition to - // 200 regular alerts - await waitForSignalsToBePresent(supertest, log, 600, [id]); - const signalsOpen = await getSignalsByIds(supertest, log, [id], 1000); - expect(signalsOpen.hits.hits.length).eql(600); - const shellSignals = signalsOpen.hits.hits.filter( - (signal) => signal._source?.[ALERT_DEPTH] === 2 - ); - const buildingBlocks = signalsOpen.hits.hits.filter( - (signal) => signal._source?.[ALERT_DEPTH] === 1 - ); - expect(shellSignals.length).eql(200); - expect(buildingBlocks.length).eql(400); - }); - - it('generates signals when an index name contains special characters to encode', async () => { - const rule: EqlRuleCreateProps = { - ...getEqlRuleForSignalTesting(['auditbeat-*', '']), - query: 'configuration where agent.id=="a1d7b39c-f898-4dbe-a761-efb61939302d"', - }; - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signals = await getSignalsByIds(supertest, log, [id]); - expect(signals.hits.hits.length).eql(1); - }); - - it('uses the provided filters', async () => { - const rule: EqlRuleCreateProps = { - ...getEqlRuleForSignalTesting(['auditbeat-*']), - query: 'any where true', - filters: [ - { - meta: { - alias: null, - negate: false, - disabled: false, - type: 'phrase', - key: 'source.ip', - params: { - query: '46.148.18.163', - }, - }, - query: { - match_phrase: { - 'source.ip': '46.148.18.163', - }, - }, - }, - { - meta: { - alias: null, - negate: false, - disabled: false, - type: 'phrase', - key: 'event.action', - params: { - query: 'error', - }, - }, - query: { - match_phrase: { - 'event.action': 'error', - }, - }, - }, - ], - }; - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signals = await getSignalsByIds(supertest, log, [id]); - expect(signals.hits.hits.length).eql(2); - }); - - describe('EQL alerts should be be enriched', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/entity/host_risk'); - }); - - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/entity/host_risk'); - }); - - it('should be enriched with host risk score', async () => { - const rule: EqlRuleCreateProps = { - ...getEqlRuleForSignalTesting(['auditbeat-*']), - query: 'configuration where agent.id=="a1d7b39c-f898-4dbe-a761-efb61939302d"', - }; - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signals = await getSignalsByIds(supertest, log, [id]); - expect(signals.hits.hits.length).eql(1); - const fullSignal = signals.hits.hits[0]._source; - if (!fullSignal) { - return expect(fullSignal).to.be.ok(); - } - expect(fullSignal?.host?.risk?.calculated_level).to.eql('Critical'); - expect(fullSignal?.host?.risk?.calculated_score_norm).to.eql(96); - }); - }); - }); - - describe('Threshold Rules', () => { - it('generates 1 signal from Threshold rules when threshold is met', async () => { - const rule: ThresholdRuleCreateProps = { - ...getThresholdRuleForSignalTesting(['auditbeat-*']), - threshold: { - field: ['host.id'], - value: 700, - }, - }; - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsByIds(supertest, log, [id]); - expect(signalsOpen.hits.hits.length).eql(1); - const fullSignal = signalsOpen.hits.hits[0]._source; - if (!fullSignal) { - return expect(fullSignal).to.be.ok(); - } - const eventIds = (fullSignal?.[ALERT_ANCESTORS] as Ancestor[]).map((event) => event.id); - expect(fullSignal).eql({ - ...fullSignal, - 'host.id': '8cc95778cce5407c809480e8e32ad76b', - [EVENT_KIND]: 'signal', - [ALERT_ANCESTORS]: [ - { - depth: 0, - id: eventIds[0], - index: 'auditbeat-*', - type: 'event', - }, - ], - [ALERT_WORKFLOW_STATUS]: 'open', - [ALERT_REASON]: 'event created high alert Signal Testing Query.', - [ALERT_RULE_UUID]: fullSignal[ALERT_RULE_UUID], - [ALERT_ORIGINAL_TIME]: fullSignal[ALERT_ORIGINAL_TIME], - [ALERT_DEPTH]: 1, - [ALERT_THRESHOLD_RESULT]: { - terms: [ - { - field: 'host.id', - value: '8cc95778cce5407c809480e8e32ad76b', - }, - ], - count: 788, - from: '2019-02-19T07:12:05.332Z', - }, - }); - }); - - it('generates 2 signals from Threshold rules when threshold is met', async () => { - const rule: ThresholdRuleCreateProps = { - ...getThresholdRuleForSignalTesting(['auditbeat-*']), - threshold: { - field: 'host.id', - value: 100, - }, - }; - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsByIds(supertest, log, [id]); - expect(signalsOpen.hits.hits.length).eql(2); - }); - - it('applies the provided query before bucketing ', async () => { - const rule: ThresholdRuleCreateProps = { - ...getThresholdRuleForSignalTesting(['auditbeat-*']), - query: 'host.id:"2ab45fc1c41e4c84bbd02202a7e5761f"', - threshold: { - field: 'process.name', - value: 21, - }, - }; - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsByIds(supertest, log, [id]); - expect(signalsOpen.hits.hits.length).eql(1); - }); - - it('generates no signals from Threshold rules when threshold is met and cardinality is not met', async () => { - const rule: ThresholdRuleCreateProps = { - ...getThresholdRuleForSignalTesting(['auditbeat-*']), - threshold: { - field: 'host.id', - value: 100, - cardinality: [ - { - field: 'destination.ip', - value: 100, - }, - ], - }, - }; - const createdRule = await createRule(supertest, log, rule); - const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); - expect(signalsOpen.hits.hits.length).eql(0); - }); - - it('generates no signals from Threshold rules when cardinality is met and threshold is not met', async () => { - const rule: ThresholdRuleCreateProps = { - ...getThresholdRuleForSignalTesting(['auditbeat-*']), - threshold: { - field: 'host.id', - value: 1000, - cardinality: [ - { - field: 'destination.ip', - value: 5, - }, - ], - }, - }; - const createdRule = await createRule(supertest, log, rule); - const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); - expect(signalsOpen.hits.hits.length).eql(0); - }); - - it('generates signals from Threshold rules when threshold and cardinality are both met', async () => { - const rule: ThresholdRuleCreateProps = { - ...getThresholdRuleForSignalTesting(['auditbeat-*']), - threshold: { - field: 'host.id', - value: 100, - cardinality: [ - { - field: 'destination.ip', - value: 5, - }, - ], - }, - }; - const createdRule = await createRule(supertest, log, rule); - const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); - expect(signalsOpen.hits.hits.length).eql(1); - const fullSignal = signalsOpen.hits.hits[0]._source; - if (!fullSignal) { - return expect(fullSignal).to.be.ok(); - } - const eventIds = (fullSignal?.[ALERT_ANCESTORS] as Ancestor[]).map((event) => event.id); - expect(fullSignal).eql({ - ...fullSignal, - 'host.id': '8cc95778cce5407c809480e8e32ad76b', - [EVENT_KIND]: 'signal', - [ALERT_ANCESTORS]: [ - { - depth: 0, - id: eventIds[0], - index: 'auditbeat-*', - type: 'event', - }, - ], - [ALERT_WORKFLOW_STATUS]: 'open', - [ALERT_REASON]: `event created high alert Signal Testing Query.`, - [ALERT_RULE_UUID]: fullSignal[ALERT_RULE_UUID], - [ALERT_ORIGINAL_TIME]: fullSignal[ALERT_ORIGINAL_TIME], - [ALERT_DEPTH]: 1, - [ALERT_THRESHOLD_RESULT]: { - terms: [ - { - field: 'host.id', - value: '8cc95778cce5407c809480e8e32ad76b', - }, - ], - cardinality: [ - { - field: 'destination.ip', - value: 7, - }, - ], - count: 788, - from: '2019-02-19T07:12:05.332Z', - }, - }); - }); - - it('should not generate signals if only one field meets the threshold requirement', async () => { - const rule: ThresholdRuleCreateProps = { - ...getThresholdRuleForSignalTesting(['auditbeat-*']), - threshold: { - field: ['host.id', 'process.name'], - value: 22, - }, - }; - const createdRule = await createRule(supertest, log, rule); - const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); - expect(signalsOpen.hits.hits.length).eql(0); - }); - - it('generates signals from Threshold rules when bucketing by multiple fields', async () => { - const rule: ThresholdRuleCreateProps = { - ...getThresholdRuleForSignalTesting(['auditbeat-*']), - threshold: { - field: ['host.id', 'process.name', 'event.module'], - value: 21, - }, - }; - const createdRule = await createRule(supertest, log, rule); - const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); - expect(signalsOpen.hits.hits.length).eql(1); - const fullSignal = signalsOpen.hits.hits[0]._source; - if (!fullSignal) { - return expect(fullSignal).to.be.ok(); - } - const eventIds = (fullSignal[ALERT_ANCESTORS] as Ancestor[]).map((event) => event.id); - expect(fullSignal).eql({ - ...fullSignal, - 'event.module': 'system', - 'host.id': '2ab45fc1c41e4c84bbd02202a7e5761f', - 'process.name': 'sshd', - [EVENT_KIND]: 'signal', - [ALERT_ANCESTORS]: [ - { - depth: 0, - id: eventIds[0], - index: 'auditbeat-*', - type: 'event', - }, - ], - [ALERT_WORKFLOW_STATUS]: 'open', - [ALERT_REASON]: `event with process sshd, created high alert Signal Testing Query.`, - [ALERT_RULE_UUID]: fullSignal[ALERT_RULE_UUID], - [ALERT_ORIGINAL_TIME]: fullSignal[ALERT_ORIGINAL_TIME], - [ALERT_DEPTH]: 1, - [ALERT_THRESHOLD_RESULT]: { - terms: [ - { - field: 'host.id', - value: '2ab45fc1c41e4c84bbd02202a7e5761f', - }, - { - field: 'process.name', - value: 'sshd', - }, - { - field: 'event.module', - value: 'system', - }, - ], - count: 21, - from: '2019-02-19T20:22:03.561Z', - }, - }); - }); - - describe('Timestamp override and fallback', async () => { - before(async () => { - await esArchiver.load( - 'x-pack/test/functional/es_archives/security_solution/timestamp_fallback' - ); - }); - - after(async () => { - await esArchiver.unload( - 'x-pack/test/functional/es_archives/security_solution/timestamp_fallback' - ); - }); - - it('applies timestamp override when using single field', async () => { - const rule: ThresholdRuleCreateProps = { - ...getThresholdRuleForSignalTesting(['timestamp-fallback-test']), - threshold: { - field: 'host.name', - value: 1, - }, - timestamp_override: 'event.ingested', - }; - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsByIds(supertest, log, [id]); - expect(signalsOpen.hits.hits.length).eql(4); - - for (const hit of signalsOpen.hits.hits) { - const originalTime = hit._source?.[ALERT_ORIGINAL_TIME]; - const hostName = hit._source?.['host.name']; - if (hostName === 'host-1') { - expect(originalTime).eql('2020-12-16T15:15:18.570Z'); - } else if (hostName === 'host-2') { - expect(originalTime).eql('2020-12-16T15:16:18.570Z'); - } else if (hostName === 'host-3') { - expect(originalTime).eql('2020-12-16T16:15:18.570Z'); - } else { - expect(originalTime).eql('2020-12-16T16:16:18.570Z'); - } - } - }); - - it('applies timestamp override when using multiple fields', async () => { - const rule: ThresholdRuleCreateProps = { - ...getThresholdRuleForSignalTesting(['timestamp-fallback-test']), - threshold: { - field: ['host.name', 'source.ip'], - value: 1, - }, - timestamp_override: 'event.ingested', - }; - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsByIds(supertest, log, [id]); - expect(signalsOpen.hits.hits.length).eql(4); - - for (const hit of signalsOpen.hits.hits) { - const originalTime = hit._source?.[ALERT_ORIGINAL_TIME]; - const hostName = hit._source?.['host.name']; - if (hostName === 'host-1') { - expect(originalTime).eql('2020-12-16T15:15:18.570Z'); - } else if (hostName === 'host-2') { - expect(originalTime).eql('2020-12-16T15:16:18.570Z'); - } else if (hostName === 'host-3') { - expect(originalTime).eql('2020-12-16T16:15:18.570Z'); - } else { - expect(originalTime).eql('2020-12-16T16:16:18.570Z'); - } - } - }); - }); - - describe('Threshold alerts should be be enriched', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/entity/host_risk'); - }); - - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/entity/host_risk'); - }); - - it('should be enriched with host risk score', async () => { - const rule: ThresholdRuleCreateProps = { - ...getThresholdRuleForSignalTesting(['auditbeat-*']), - threshold: { - field: 'host.name', - value: 100, - }, - }; - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsByIds(supertest, log, [id]); - - expect(signalsOpen.hits.hits[0]?._source?.host?.risk?.calculated_level).to.eql('Low'); - expect(signalsOpen.hits.hits[0]?._source?.host?.risk?.calculated_score_norm).to.eql(20); - expect(signalsOpen.hits.hits[1]?._source?.host?.risk?.calculated_level).to.eql( - 'Critical' - ); - expect(signalsOpen.hits.hits[1]?._source?.host?.risk?.calculated_score_norm).to.eql(96); - }); - }); - }); - - describe('Enrich alerts: query rule', () => { - describe('without index avalable', () => { - it('should do not have risk score fields', async () => { - const rule: QueryRuleCreateProps = { - ...getRuleForSignalTesting(['auditbeat-*']), - query: `_id:${ID}`, - }; - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsByIds(supertest, log, [id]); - expect(signalsOpen.hits.hits[0]?._source?.host?.risk).to.eql(undefined); - expect(signalsOpen.hits.hits[0]?._source?.user?.risk).to.eql(undefined); - }); - }); - - describe('with host risk score', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); - await esArchiver.load('x-pack/test/functional/es_archives/entity/host_risk'); - }); - - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); - await esArchiver.unload('x-pack/test/functional/es_archives/entity/host_risk'); - }); - - it('should host have risk score field and do not have user risk score', async () => { - const rule: QueryRuleCreateProps = { - ...getRuleForSignalTesting(['auditbeat-*']), - query: `_id:${ID} or _id:GBbXBmkBR346wHgn5_eR or _id:x10zJ2oE9v5HJNSHhyxi`, - }; - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsByIds(supertest, log, [id]); - - const alerts = signalsOpen.hits.hits ?? []; - const firstAlert = alerts.find( - (alert) => alert?._source?.host?.name === 'suricata-zeek-sensor-toronto' - ); - const secondAlert = alerts.find( - (alert) => alert?._source?.host?.name === 'suricata-sensor-london' - ); - const thirdAlert = alerts.find((alert) => alert?._source?.host?.name === 'IE11WIN8_1'); - - expect(firstAlert?._source?.host?.risk?.calculated_level).to.eql('Critical'); - expect(firstAlert?._source?.host?.risk?.calculated_score_norm).to.eql(96); - expect(firstAlert?._source?.user?.risk).to.eql(undefined); - expect(secondAlert?._source?.host?.risk?.calculated_level).to.eql('Low'); - expect(secondAlert?._source?.host?.risk?.calculated_score_norm).to.eql(20); - expect(thirdAlert?._source?.host?.risk).to.eql(undefined); - }); - }); - - describe('with host and risk score and user risk score', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); - await esArchiver.load('x-pack/test/functional/es_archives/entity/host_risk'); - await esArchiver.load('x-pack/test/functional/es_archives/entity/user_risk'); - }); - - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); - await esArchiver.unload('x-pack/test/functional/es_archives/entity/host_risk'); - await esArchiver.unload('x-pack/test/functional/es_archives/entity/user_risk'); - }); - - it('should have host and user risk score fields', async () => { - const rule: QueryRuleCreateProps = { - ...getRuleForSignalTesting(['auditbeat-*']), - query: `_id:${ID}`, - }; - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsByIds(supertest, log, [id]); - expect(signalsOpen.hits.hits[0]?._source?.host?.risk?.calculated_level).to.eql( - 'Critical' - ); - expect(signalsOpen.hits.hits[0]?._source?.host?.risk?.calculated_score_norm).to.eql(96); - expect(signalsOpen.hits.hits[0]?._source?.user?.risk?.calculated_level).to.eql('Low'); - expect(signalsOpen.hits.hits[0]?._source?.user?.risk?.calculated_score_norm).to.eql(11); - }); - }); - }); - }); - - /** - * Here we test the functionality of Severity and Risk Score overrides (also called "mappings" - * in the code). If the rule specifies a mapping, then the final Severity or Risk Score - * value of the signal will be taken from the mapped field of the source event. - */ - describe('Signals generated from events with custom severity and risk score fields', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/signals/severity_risk_overrides'); - }); - - after(async () => { - await esArchiver.unload( - 'x-pack/test/functional/es_archives/signals/severity_risk_overrides' - ); - }); - - const executeRuleAndGetSignals = async (rule: QueryRuleCreateProps) => { - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 4, [id]); - const signalsResponse = await getSignalsByIds(supertest, log, [id]); - const signals = signalsResponse.hits.hits.map((hit) => hit._source); - const signalsOrderedByEventId = orderBy(signals, 'signal.parent.id', 'asc'); - return signalsOrderedByEventId; - }; - - it('should get default severity and risk score if there is no mapping', async () => { - const rule: QueryRuleCreateProps = { - ...getRuleForSignalTesting(['signal_overrides']), - severity: 'medium', - risk_score: 75, - }; - - const signals = await executeRuleAndGetSignals(rule); - - expect(signals.length).equal(4); - signals.forEach((s) => { - expect(s?.[ALERT_SEVERITY]).equal('medium'); - expect(s?.[ALERT_RULE_PARAMETERS].severity_mapping).eql([]); - - expect(s?.[ALERT_RISK_SCORE]).equal(75); - expect(s?.[ALERT_RULE_PARAMETERS].risk_score_mapping).eql([]); - }); - }); - - it('should get overridden severity if the rule has a mapping for it', async () => { - const rule: QueryRuleCreateProps = { - ...getRuleForSignalTesting(['signal_overrides']), - severity: 'medium', - severity_mapping: [ - { field: 'my_severity', operator: 'equals', value: 'sev_900', severity: 'high' }, - { field: 'my_severity', operator: 'equals', value: 'sev_max', severity: 'critical' }, - ], - risk_score: 75, - }; - - const signals = await executeRuleAndGetSignals(rule); - const severities = signals.map((s) => ({ - id: (s?.[ALERT_ANCESTORS] as Ancestor[])[0].id, - value: s?.[ALERT_SEVERITY], - })); - - expect(signals.length).equal(4); - expect(severities).eql([ - { id: '1', value: 'high' }, - { id: '2', value: 'critical' }, - { id: '3', value: 'critical' }, - { id: '4', value: 'critical' }, - ]); - - signals.forEach((s) => { - expect(s?.[ALERT_RISK_SCORE]).equal(75); - expect(s?.[ALERT_RULE_PARAMETERS].risk_score_mapping).eql([]); - expect(s?.[ALERT_RULE_PARAMETERS].severity_mapping).eql([ - { field: 'my_severity', operator: 'equals', value: 'sev_900', severity: 'high' }, - { field: 'my_severity', operator: 'equals', value: 'sev_max', severity: 'critical' }, - ]); - }); - }); - - it('should get overridden risk score if the rule has a mapping for it', async () => { - const rule: QueryRuleCreateProps = { - ...getRuleForSignalTesting(['signal_overrides']), - severity: 'medium', - risk_score: 75, - risk_score_mapping: [ - { field: 'my_risk', operator: 'equals', value: '', risk_score: undefined }, - ], - }; - - const signals = await executeRuleAndGetSignals(rule); - const riskScores = signals.map((s) => ({ - id: (s?.[ALERT_ANCESTORS] as Ancestor[])[0].id, - value: s?.[ALERT_RISK_SCORE], - })); - - expect(signals.length).equal(4); - expect(riskScores).eql([ - { id: '1', value: 31.14 }, - { id: '2', value: 32.14 }, - { id: '3', value: 33.14 }, - { id: '4', value: 34.14 }, - ]); - - signals.forEach((s) => { - expect(s?.[ALERT_SEVERITY]).equal('medium'); - expect(s?.[ALERT_RULE_PARAMETERS].severity_mapping).eql([]); - expect(s?.[ALERT_RULE_PARAMETERS].risk_score_mapping).eql([ - { field: 'my_risk', operator: 'equals', value: '' }, - ]); - }); - }); - - it('should get overridden severity and risk score if the rule has both mappings', async () => { - const rule: QueryRuleCreateProps = { - ...getRuleForSignalTesting(['signal_overrides']), - severity: 'medium', - severity_mapping: [ - { field: 'my_severity', operator: 'equals', value: 'sev_900', severity: 'high' }, - { field: 'my_severity', operator: 'equals', value: 'sev_max', severity: 'critical' }, - ], - risk_score: 75, - risk_score_mapping: [ - { field: 'my_risk', operator: 'equals', value: '', risk_score: undefined }, - ], - }; - - const signals = await executeRuleAndGetSignals(rule); - const values = signals.map((s) => ({ - id: (s?.[ALERT_ANCESTORS] as Ancestor[])[0].id, - severity: s?.[ALERT_SEVERITY], - risk: s?.[ALERT_RISK_SCORE], - })); - - expect(signals.length).equal(4); - expect(values).eql([ - { id: '1', severity: 'high', risk: 31.14 }, - { id: '2', severity: 'critical', risk: 32.14 }, - { id: '3', severity: 'critical', risk: 33.14 }, - { id: '4', severity: 'critical', risk: 34.14 }, - ]); - - signals.forEach((s) => { - expect(s?.[ALERT_RULE_PARAMETERS].severity_mapping).eql([ - { field: 'my_severity', operator: 'equals', value: 'sev_900', severity: 'high' }, - { field: 'my_severity', operator: 'equals', value: 'sev_max', severity: 'critical' }, - ]); - expect(s?.[ALERT_RULE_PARAMETERS].risk_score_mapping).eql([ - { field: 'my_risk', operator: 'equals', value: '' }, - ]); - }); - }); - }); - - describe('Signals generated from events with name override field', async () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); - }); - - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); - }); - - beforeEach(async () => { - await deleteSignalsIndex(supertest, log); - await createSignalsIndex(supertest, log); - }); - - afterEach(async () => { - await deleteSignalsIndex(supertest, log); - await deleteAllAlerts(supertest, log); - }); - - it('should generate signals with name_override field', async () => { - const rule: QueryRuleCreateProps = { - ...getRuleForSignalTesting(['auditbeat-*']), - rule_name_override: 'event.action', - }; - - const { id } = await createRule(supertest, log, rule); - - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsResponse = await getSignalsByIds(supertest, log, [id], 1); - const signals = signalsResponse.hits.hits.map((hit) => hit._source); - const signalsOrderedByEventId = orderBy(signals, 'signal.parent.id', 'asc'); - const fullSignal = signalsOrderedByEventId[0]; - if (!fullSignal) { - return expect(fullSignal).to.be.ok(); - } - - expect(fullSignal).eql({ - ...fullSignal, - [EVENT_ACTION]: 'boot', - [ALERT_ANCESTORS]: [ - { - depth: 0, - id: 'UBXOBmkBR346wHgnLP8T', - index: 'auditbeat-8.0.0-2019.02.19-000001', - type: 'event', - }, - ], - [ALERT_WORKFLOW_STATUS]: 'open', - [ALERT_REASON]: `event on zeek-sensor-amsterdam created high alert boot.`, - [ALERT_RULE_NAME]: 'boot', - [ALERT_RULE_RULE_NAME_OVERRIDE]: 'event.action', - [ALERT_DEPTH]: 1, - ...flattenWithPrefix(ALERT_ORIGINAL_EVENT, { - action: 'boot', - dataset: 'login', - kind: 'event', - module: 'system', - origin: '/var/log/wtmp', - }), - }); - }); - }); - - describe('Signal deduplication', async () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); - }); - - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); - }); - - beforeEach(async () => { - await deleteSignalsIndex(supertest, log); - }); - - afterEach(async () => { - await deleteSignalsIndex(supertest, log); - await deleteAllAlerts(supertest, log); - }); - - it('should not generate duplicate signals', async () => { - const rule: QueryRuleCreateProps = { - ...getRuleForSignalTesting(['auditbeat-*']), - query: `_id:${ID}`, - }; - - const ruleResponse = await createRule(supertest, log, rule); - - const signals = await getOpenSignals(supertest, log, es, ruleResponse); - expect(signals.hits.hits.length).to.eql(1); - - const statusResponse = await supertest - .get(DETECTION_ENGINE_RULES_URL) - .set('kbn-xsrf', 'true') - .query({ id: ruleResponse.id }); - - // TODO: https://github.com/elastic/kibana/pull/121644 clean up, make type-safe - const ruleStatusDate = statusResponse.body?.execution_summary?.last_execution.date; - const initialStatusDate = new Date(ruleStatusDate); - - const initialSignal = signals.hits.hits[0]; - - // Disable the rule then re-enable to trigger another run - await supertest - .patch(DETECTION_ENGINE_RULES_URL) - .set('kbn-xsrf', 'true') - .send({ rule_id: ruleResponse.rule_id, enabled: false }) - .expect(200); - - await supertest - .patch(DETECTION_ENGINE_RULES_URL) - .set('kbn-xsrf', 'true') - .send({ rule_id: ruleResponse.rule_id, enabled: true }) - .expect(200); - - await waitForRuleSuccessOrStatus( - supertest, - log, - ruleResponse.id, - RuleExecutionStatus.succeeded, - initialStatusDate - ); - - const newSignals = await getOpenSignals(supertest, log, es, ruleResponse); - expect(newSignals.hits.hits.length).to.eql(1); - expect(newSignals.hits.hits[0]).to.eql(initialSignal); - }); - }); - }); -}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/index.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/index.ts index 3064d412da1b..690498a28753 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/index.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/index.ts @@ -23,16 +23,13 @@ export default ({ loadTestFile }: FtrProviderContext): void => { loadTestFile(require.resolve('./create_rules')); loadTestFile(require.resolve('./preview_rules')); loadTestFile(require.resolve('./create_rules_bulk')); - loadTestFile(require.resolve('./create_ml')); loadTestFile(require.resolve('./create_new_terms')); - loadTestFile(require.resolve('./create_threat_matching')); loadTestFile(require.resolve('./create_rule_exceptions')); loadTestFile(require.resolve('./delete_rules')); loadTestFile(require.resolve('./delete_rules_bulk')); loadTestFile(require.resolve('./export_rules')); loadTestFile(require.resolve('./find_rules')); loadTestFile(require.resolve('./find_rule_exception_references')); - loadTestFile(require.resolve('./generating_signals')); loadTestFile(require.resolve('./get_prepackaged_rules_status')); }); }; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/preview_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/preview_rules.ts index 3e0dd166a0cd..b38545e9c03c 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/preview_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/preview_rules.ts @@ -20,8 +20,8 @@ export default ({ getService }: FtrProviderContext) => { const supertestWithoutAuth = getService('supertestWithoutAuth'); const log = getService('log'); - describe('create_rules', () => { - describe('creating rules', () => { + describe('preview_rules', () => { + describe('previewing rules', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/README.md b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/README.md new file mode 100644 index 000000000000..3a72c90e3ec5 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/README.md @@ -0,0 +1,11 @@ +### Security Rule Execution Logic Tests + +These tests use the rule preview API as a fast way to verify that rules generate alerts as expected with various parameter settings. This avoids the costly overhead of creating a real rule and waiting for it to be scheduled. The preview route also returns rule statuses directly in the API response instead of writing the statuses to saved objects, which saves significant time as well. + +For assurance that the real rule execution works, one test for each rule type still creates a real rule and waits for the execution through the alerting framework and resulting alerts. + +As a result, the tests here typically run ~10x faster than the tests they replaced that were creating actual rules and running them. We can therefore add more tests here and get better coverage of the rule execution logic (which is currently, as of 8.5, somewhat lacking). + +Since the rule execution logic is primarily focused around generating and executing Elasticsearch queries, we need significant testing around whether or not the queries are returning the expected results. This is not achievable with unit tests at the moment, since we need to mock Elasticsearch results. The tests here are the preferred way to ensure that rules are executing the correct logic to generate alerts from source data. + +Testing rules with exceptions is still slow, even with the preview API, because the exception list has to be created for real and then cleaned up after the test - exceptions live in saved objects, so creating exceptions for individual tests slows them down significantly (>1s per test vs ~200ms for a test without exceptions). This is an area for future improvement. diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/config.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/config.ts new file mode 100644 index 000000000000..2430b8f2148d --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/config.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrConfigProviderContext } from '@kbn/test'; + +// eslint-disable-next-line import/no-default-export +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const functionalConfig = await readConfigFile(require.resolve('../config.base.ts')); + + return { + ...functionalConfig.getAll(), + testFiles: [require.resolve('.')], + }; +} diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/eql.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/eql.ts new file mode 100644 index 000000000000..cffc0a311ef3 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/eql.ts @@ -0,0 +1,606 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { + ALERT_REASON, + ALERT_RULE_UUID, + ALERT_WORKFLOW_STATUS, + EVENT_KIND, +} from '@kbn/rule-data-utils'; +import { flattenWithPrefix } from '@kbn/securitysolution-rules'; + +import { get } from 'lodash'; + +import { EqlRuleCreateProps } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; +import { Ancestor } from '@kbn/security-solution-plugin/server/lib/detection_engine/signals/types'; +import { + ALERT_ANCESTORS, + ALERT_DEPTH, + ALERT_ORIGINAL_TIME, + ALERT_ORIGINAL_EVENT, + ALERT_ORIGINAL_EVENT_CATEGORY, + ALERT_GROUP_ID, +} from '@kbn/security-solution-plugin/common/field_maps/field_names'; +import { + createRule, + deleteAllAlerts, + deleteSignalsIndex, + getEqlRuleForSignalTesting, + getOpenSignals, + getPreviewAlerts, + previewRule, +} from '../../utils'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + const es = getService('es'); + const log = getService('log'); + + describe('EQL type rules', () => { + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); + await esArchiver.load( + 'x-pack/test/functional/es_archives/security_solution/timestamp_override_6' + ); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); + await esArchiver.unload( + 'x-pack/test/functional/es_archives/security_solution/timestamp_override_6' + ); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); + }); + + // First test creates a real rule - remaining tests use preview API + it('generates a correctly formatted signal from EQL non-sequence queries', async () => { + const rule: EqlRuleCreateProps = { + ...getEqlRuleForSignalTesting(['auditbeat-*']), + query: 'configuration where agent.id=="a1d7b39c-f898-4dbe-a761-efb61939302d"', + }; + const createdRule = await createRule(supertest, log, rule); + const alerts = await getOpenSignals(supertest, log, es, createdRule); + expect(alerts.hits.hits.length).eql(1); + const fullSignal = alerts.hits.hits[0]._source; + if (!fullSignal) { + return expect(fullSignal).to.be.ok(); + } + + expect(fullSignal).eql({ + ...fullSignal, + agent: { + ephemeral_id: '0010d67a-14f7-41da-be30-489fea735967', + hostname: 'suricata-zeek-sensor-toronto', + id: 'a1d7b39c-f898-4dbe-a761-efb61939302d', + type: 'auditbeat', + version: '8.0.0', + }, + auditd: { + data: { + audit_enabled: '1', + old: '1', + }, + message_type: 'config_change', + result: 'success', + sequence: 1496, + session: 'unset', + summary: { + actor: { + primary: 'unset', + }, + object: { + primary: '1', + type: 'audit-config', + }, + }, + }, + cloud: { + instance: { + id: '133555295', + }, + provider: 'digitalocean', + region: 'tor1', + }, + ecs: { + version: '1.0.0-beta2', + }, + ...flattenWithPrefix('event', { + action: 'changed-audit-configuration', + category: 'configuration', + module: 'auditd', + kind: 'signal', + }), + host: { + architecture: 'x86_64', + containerized: false, + hostname: 'suricata-zeek-sensor-toronto', + id: '8cc95778cce5407c809480e8e32ad76b', + name: 'suricata-zeek-sensor-toronto', + os: { + codename: 'bionic', + family: 'debian', + kernel: '4.15.0-45-generic', + name: 'Ubuntu', + platform: 'ubuntu', + version: '18.04.2 LTS (Bionic Beaver)', + }, + }, + service: { + type: 'auditd', + }, + user: { + audit: { + id: 'unset', + }, + }, + [ALERT_REASON]: + 'configuration event on suricata-zeek-sensor-toronto created high alert Signal Testing Query.', + [ALERT_RULE_UUID]: fullSignal[ALERT_RULE_UUID], + [ALERT_ORIGINAL_TIME]: fullSignal[ALERT_ORIGINAL_TIME], + [ALERT_WORKFLOW_STATUS]: 'open', + [ALERT_DEPTH]: 1, + [ALERT_ANCESTORS]: [ + { + depth: 0, + id: '9xbRBmkBR346wHgngz2D', + index: 'auditbeat-8.0.0-2019.02.19-000001', + type: 'event', + }, + ], + ...flattenWithPrefix(ALERT_ORIGINAL_EVENT, { + action: 'changed-audit-configuration', + category: 'configuration', + module: 'auditd', + }), + }); + }); + + it('generates up to max_signals for non-sequence EQL queries', async () => { + const maxSignals = 200; + const rule: EqlRuleCreateProps = { + ...getEqlRuleForSignalTesting(['auditbeat-*']), + max_signals: maxSignals, + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId, size: maxSignals * 2 }); + expect(previewAlerts.length).eql(maxSignals); + }); + + it('uses the provided event_category_override', async () => { + const rule: EqlRuleCreateProps = { + ...getEqlRuleForSignalTesting(['auditbeat-*']), + query: 'config_change where agent.id=="a1d7b39c-f898-4dbe-a761-efb61939302d"', + event_category_override: 'auditd.message_type', + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).eql(1); + const fullSignal = previewAlerts[0]._source; + if (!fullSignal) { + return expect(fullSignal).to.be.ok(); + } + + expect(fullSignal).eql({ + ...fullSignal, + auditd: { + data: { + audit_enabled: '1', + old: '1', + }, + message_type: 'config_change', + result: 'success', + sequence: 1496, + session: 'unset', + summary: { + actor: { + primary: 'unset', + }, + object: { + primary: '1', + type: 'audit-config', + }, + }, + }, + ...flattenWithPrefix('event', { + action: 'changed-audit-configuration', + category: 'configuration', + module: 'auditd', + kind: 'signal', + }), + service: { + type: 'auditd', + }, + user: { + audit: { + id: 'unset', + }, + }, + [ALERT_REASON]: + 'configuration event on suricata-zeek-sensor-toronto created high alert Signal Testing Query.', + [ALERT_RULE_UUID]: fullSignal[ALERT_RULE_UUID], + [ALERT_ORIGINAL_TIME]: fullSignal[ALERT_ORIGINAL_TIME], + [ALERT_WORKFLOW_STATUS]: 'open', + [ALERT_DEPTH]: 1, + [ALERT_ANCESTORS]: [ + { + depth: 0, + id: '9xbRBmkBR346wHgngz2D', + index: 'auditbeat-8.0.0-2019.02.19-000001', + type: 'event', + }, + ], + ...flattenWithPrefix(ALERT_ORIGINAL_EVENT, { + action: 'changed-audit-configuration', + category: 'configuration', + module: 'auditd', + }), + }); + }); + + it('uses the provided timestamp_field', async () => { + const rule: EqlRuleCreateProps = { + ...getEqlRuleForSignalTesting(['fake.index.1']), + query: 'any where true', + timestamp_field: 'created_at', + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).eql(3); + + const createdAtHits = previewAlerts.map((hit) => hit._source?.created_at).sort(); + expect(createdAtHits).to.eql([1622676785, 1622676790, 1622676795]); + }); + + it('uses the provided tiebreaker_field', async () => { + const rule: EqlRuleCreateProps = { + ...getEqlRuleForSignalTesting(['fake.index.1']), + query: 'any where true', + tiebreaker_field: 'locale', + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).eql(3); + + const createdAtHits = previewAlerts.map((hit) => hit._source?.locale); + expect(createdAtHits).to.eql(['es', 'pt', 'ua']); + }); + + it('generates building block signals from EQL sequences in the expected form', async () => { + const rule: EqlRuleCreateProps = { + ...getEqlRuleForSignalTesting(['auditbeat-*']), + query: 'sequence by host.name [anomoly where true] [any where true]', // TODO: spelling + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + const buildingBlock = previewAlerts.find( + (alert) => + alert._source?.[ALERT_DEPTH] === 1 && + get(alert._source, ALERT_ORIGINAL_EVENT_CATEGORY) === 'anomoly' + ); + expect(buildingBlock).not.eql(undefined); + const fullSignal = buildingBlock?._source; + if (!fullSignal) { + return expect(fullSignal).to.be.ok(); + } + + expect(fullSignal).eql({ + ...fullSignal, + agent: { + ephemeral_id: '1b4978a0-48be-49b1-ac96-323425b389ab', + hostname: 'zeek-sensor-amsterdam', + id: 'e52588e6-7aa3-4c89-a2c4-d6bc5c286db1', + type: 'auditbeat', + version: '8.0.0', + }, + auditd: { + data: { + a0: '3', + a1: '107', + a2: '1', + a3: '7ffc186b58e0', + arch: 'x86_64', + auid: 'unset', + dev: 'eth0', + exit: '0', + gid: '0', + old_prom: '0', + prom: '256', + ses: 'unset', + syscall: 'setsockopt', + tty: '(none)', + uid: '0', + }, + message_type: 'anom_promiscuous', + result: 'success', + sequence: 1392, + session: 'unset', + summary: { + actor: { + primary: 'unset', + secondary: 'root', + }, + how: '/usr/bin/bro', + object: { + primary: 'eth0', + type: 'network-device', + }, + }, + }, + cloud: { instance: { id: '133551048' }, provider: 'digitalocean', region: 'ams3' }, + ecs: { version: '1.0.0-beta2' }, + ...flattenWithPrefix('event', { + action: 'changed-promiscuous-mode-on-device', + category: 'anomoly', + module: 'auditd', + kind: 'signal', + }), + host: { + architecture: 'x86_64', + containerized: false, + hostname: 'zeek-sensor-amsterdam', + id: '2ce8b1e7d69e4a1d9c6bcddc473da9d9', + name: 'zeek-sensor-amsterdam', + os: { + codename: 'bionic', + family: 'debian', + kernel: '4.15.0-45-generic', + name: 'Ubuntu', + platform: 'ubuntu', + version: '18.04.2 LTS (Bionic Beaver)', + }, + }, + process: { + executable: '/usr/bin/bro', + name: 'bro', + pid: 30157, + ppid: 30151, + title: + '/usr/bin/bro -i eth0 -U .status -p broctl -p broctl-live -p standalone -p local -p bro local.bro broctl broctl/standalone broctl', + }, + service: { type: 'auditd' }, + user: { + audit: { id: 'unset' }, + effective: { + group: { + id: '0', + name: 'root', + }, + id: '0', + name: 'root', + }, + filesystem: { + group: { + id: '0', + name: 'root', + }, + id: '0', + name: 'root', + }, + group: { id: '0', name: 'root' }, + id: '0', + name: 'root', + saved: { + group: { + id: '0', + name: 'root', + }, + id: '0', + name: 'root', + }, + }, + [ALERT_REASON]: + 'anomoly event with process bro, by root on zeek-sensor-amsterdam created high alert Signal Testing Query.', + [ALERT_RULE_UUID]: fullSignal[ALERT_RULE_UUID], + [ALERT_GROUP_ID]: fullSignal[ALERT_GROUP_ID], + [ALERT_ORIGINAL_TIME]: fullSignal[ALERT_ORIGINAL_TIME], + [ALERT_WORKFLOW_STATUS]: 'open', + [ALERT_DEPTH]: 1, + [ALERT_ANCESTORS]: [ + { + depth: 0, + id: 'VhXOBmkBR346wHgnLP8T', + index: 'auditbeat-8.0.0-2019.02.19-000001', + type: 'event', + }, + ], + ...flattenWithPrefix(ALERT_ORIGINAL_EVENT, { + action: 'changed-promiscuous-mode-on-device', + category: 'anomoly', + module: 'auditd', + }), + }); + }); + + it('generates shell signals from EQL sequences in the expected form', async () => { + const rule: EqlRuleCreateProps = { + ...getEqlRuleForSignalTesting(['auditbeat-*']), + query: 'sequence by host.name [anomoly where true] [any where true]', + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + const sequenceAlert = previewAlerts.find((alert) => alert._source?.[ALERT_DEPTH] === 2); + const source = sequenceAlert?._source; + if (!source) { + return expect(source).to.be.ok(); + } + const eventIds = (source?.[ALERT_ANCESTORS] as Ancestor[]) + .filter((event) => event.depth === 1) + .map((event) => event.id); + expect(source).eql({ + ...source, + agent: { + ephemeral_id: '1b4978a0-48be-49b1-ac96-323425b389ab', + hostname: 'zeek-sensor-amsterdam', + id: 'e52588e6-7aa3-4c89-a2c4-d6bc5c286db1', + type: 'auditbeat', + version: '8.0.0', + }, + auditd: { session: 'unset', summary: { actor: { primary: 'unset' } } }, + cloud: { instance: { id: '133551048' }, provider: 'digitalocean', region: 'ams3' }, + ecs: { version: '1.0.0-beta2' }, + [EVENT_KIND]: 'signal', + host: { + architecture: 'x86_64', + containerized: false, + hostname: 'zeek-sensor-amsterdam', + id: '2ce8b1e7d69e4a1d9c6bcddc473da9d9', + name: 'zeek-sensor-amsterdam', + os: { + codename: 'bionic', + family: 'debian', + kernel: '4.15.0-45-generic', + name: 'Ubuntu', + platform: 'ubuntu', + version: '18.04.2 LTS (Bionic Beaver)', + }, + }, + service: { type: 'auditd' }, + user: { audit: { id: 'unset' }, id: '0', name: 'root' }, + [ALERT_WORKFLOW_STATUS]: 'open', + [ALERT_DEPTH]: 2, + [ALERT_GROUP_ID]: source[ALERT_GROUP_ID], + [ALERT_REASON]: + 'event by root on zeek-sensor-amsterdam created high alert Signal Testing Query.', + [ALERT_RULE_UUID]: source[ALERT_RULE_UUID], + [ALERT_ANCESTORS]: [ + { + depth: 0, + id: 'VhXOBmkBR346wHgnLP8T', + index: 'auditbeat-8.0.0-2019.02.19-000001', + type: 'event', + }, + { + depth: 1, + id: eventIds[0], + index: '', + rule: source[ALERT_RULE_UUID], + type: 'signal', + }, + { + depth: 0, + id: '4hbXBmkBR346wHgn6fdp', + index: 'auditbeat-8.0.0-2019.02.19-000001', + type: 'event', + }, + { + depth: 1, + id: eventIds[1], + index: '', + rule: source[ALERT_RULE_UUID], + type: 'signal', + }, + ], + }); + }); + + it('generates up to max_signals with an EQL rule', async () => { + const maxSignals = 200; + const rule: EqlRuleCreateProps = { + ...getEqlRuleForSignalTesting(['auditbeat-*']), + query: 'sequence by host.name [any where true] [any where true]', + max_signals: maxSignals, + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId, size: maxSignals * 5 }); + // For EQL rules, max_signals is the maximum number of detected sequences: each sequence has a building block + // alert for each event in the sequence, so max_signals=200 results in 400 building blocks in addition to + // 200 regular alerts + expect(previewAlerts.length).eql(maxSignals * 3); + const shellSignals = previewAlerts.filter((alert) => alert._source?.[ALERT_DEPTH] === 2); + const buildingBlocks = previewAlerts.filter((alert) => alert._source?.[ALERT_DEPTH] === 1); + expect(shellSignals.length).eql(maxSignals); + expect(buildingBlocks.length).eql(maxSignals * 2); + }); + + it('generates signals when an index name contains special characters to encode', async () => { + const rule: EqlRuleCreateProps = { + ...getEqlRuleForSignalTesting(['auditbeat-*', '']), + query: 'configuration where agent.id=="a1d7b39c-f898-4dbe-a761-efb61939302d"', + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).eql(1); + }); + + it('uses the provided filters', async () => { + const rule: EqlRuleCreateProps = { + ...getEqlRuleForSignalTesting(['auditbeat-*']), + query: 'any where true', + filters: [ + { + meta: { + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'source.ip', + params: { + query: '46.148.18.163', + }, + }, + query: { + match_phrase: { + 'source.ip': '46.148.18.163', + }, + }, + }, + { + meta: { + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'event.action', + params: { + query: 'error', + }, + }, + query: { + match_phrase: { + 'event.action': 'error', + }, + }, + }, + ], + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).eql(2); + }); + + describe('with host risk index', async () => { + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/entity/host_risk'); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/entity/host_risk'); + }); + + it('should be enriched with host risk score', async () => { + const rule: EqlRuleCreateProps = { + ...getEqlRuleForSignalTesting(['auditbeat-*']), + query: 'configuration where agent.id=="a1d7b39c-f898-4dbe-a761-efb61939302d"', + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).eql(1); + const fullSignal = previewAlerts[0]._source; + if (!fullSignal) { + return expect(fullSignal).to.be.ok(); + } + expect(fullSignal?.host?.risk?.calculated_level).to.eql('Critical'); + expect(fullSignal?.host?.risk?.calculated_score_norm).to.eql(96); + }); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/index.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/index.ts new file mode 100644 index 000000000000..547e3a4706e3 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/index.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../common/ftr_provider_context'; + +// eslint-disable-next-line import/no-default-export +export default ({ loadTestFile }: FtrProviderContext): void => { + describe('detection engine api security and spaces enabled - rule execution logic', function () { + loadTestFile(require.resolve('./eql')); + loadTestFile(require.resolve('./machine_learning')); + loadTestFile(require.resolve('./new_terms')); + loadTestFile(require.resolve('./query')); + loadTestFile(require.resolve('./saved_query')); + loadTestFile(require.resolve('./threat_match')); + loadTestFile(require.resolve('./threshold')); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_ml.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/machine_learning.ts similarity index 69% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_ml.ts rename to x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/machine_learning.ts index e1a3d4f0796c..0949d8255bed 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_ml.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/machine_learning.ts @@ -33,9 +33,14 @@ import { import { FtrProviderContext } from '../../common/ftr_provider_context'; import { createRule, - createRuleWithExceptionEntries, deleteAllAlerts, + deleteSignalsIndex, + executeSetupModuleRequest, + forceStartDatafeeds, getOpenSignals, + getPreviewAlerts, + previewRule, + previewRuleWithExceptionEntries, } from '../../utils'; // eslint-disable-next-line import/no-default-export @@ -47,7 +52,7 @@ export default ({ getService }: FtrProviderContext) => { const siemModule = 'security_linux_v3'; const mlJobId = 'v3_linux_anomalous_network_activity'; - const testRule: MachineLearningRuleCreateProps = { + const rule: MachineLearningRuleCreateProps = { name: 'Test ML rule', description: 'Test ML rule description', risk_score: 50, @@ -56,63 +61,32 @@ export default ({ getService }: FtrProviderContext) => { anomaly_threshold: 30, machine_learning_job_id: mlJobId, from: '1900-01-01T00:00:00.000Z', + rule_id: 'ml-rule-id', }; - async function executeSetupModuleRequest(module: string, rspCode: number) { - const { body } = await supertest - .post(`/api/ml/modules/setup/${module}`) - .set('kbn-xsrf', 'true') - .send({ - prefix: '', - groups: ['auditbeat'], - indexPatternName: 'auditbeat-*', - startDatafeed: false, - useDedicatedIndex: true, - applyToAllSpaces: true, - }) - .expect(rspCode); - - return body; - } - - async function forceStartDatafeeds(jobId: string, rspCode: number) { - const { body } = await supertest - .post(`/api/ml/jobs/force_start_datafeeds`) - .set('kbn-xsrf', 'true') - .send({ - datafeedIds: [`datafeed-${jobId}`], - start: new Date().getUTCMilliseconds(), - }) - .expect(rspCode); - - return body; - } - - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/125033 // FLAKY: https://github.com/elastic/kibana/issues/142993 - describe.skip('Generating signals from ml anomalies', () => { + describe.skip('Machine learning type rules', () => { before(async () => { // Order is critical here: auditbeat data must be loaded before attempting to start the ML job, // as the job looks for certain indices on start await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); - await executeSetupModuleRequest(siemModule, 200); - await forceStartDatafeeds(mlJobId, 200); + await executeSetupModuleRequest({ module: siemModule, rspCode: 200, supertest }); + await forceStartDatafeeds({ jobId: mlJobId, rspCode: 200, supertest }); await esArchiver.load('x-pack/test/functional/es_archives/security_solution/anomalies'); }); after(async () => { await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); await esArchiver.unload('x-pack/test/functional/es_archives/security_solution/anomalies'); - }); - - afterEach(async () => { + await deleteSignalsIndex(supertest, log); await deleteAllAlerts(supertest, log); }); + // First test creates a real rule - remaining tests use preview API it('should create 1 alert from ML rule when record meets anomaly_threshold', async () => { - const createdRule = await createRule(supertest, log, testRule); - const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); - expect(signalsOpen.hits.hits.length).toBe(1); - const signal = signalsOpen.hits.hits[0]; + const createdRule = await createRule(supertest, log, rule); + const alerts = await getOpenSignals(supertest, log, es, createdRule); + expect(alerts.hits.hits.length).toBe(1); + const signal = alerts.hits.hits[0]; expect(signal._source).toEqual( expect.objectContaining({ @@ -162,7 +136,7 @@ export default ({ getService }: FtrProviderContext) => { required_fields: [], risk_score: 50, risk_score_mapping: [], - rule_id: createdRule.rule_id, + rule_id: 'ml-rule-id', setup: '', severity: 'critical', severity_mapping: [], @@ -185,13 +159,12 @@ export default ({ getService }: FtrProviderContext) => { }); it('should create 7 alerts from ML rule when records meet anomaly_threshold', async () => { - const rule: MachineLearningRuleCreateProps = { - ...testRule, - anomaly_threshold: 20, - }; - const createdRule = await createRule(supertest, log, rule); - const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); - expect(signalsOpen.hits.hits.length).toBe(7); + const { previewId } = await previewRule({ + supertest, + rule: { ...rule, anomaly_threshold: 20 }, + }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).toBe(7); }); describe('with non-value list exception', () => { @@ -199,18 +172,23 @@ export default ({ getService }: FtrProviderContext) => { await deleteAllExceptions(supertest, log); }); it('generates no signals when an exception is added for an ML rule', async () => { - const createdRule = await createRuleWithExceptionEntries(supertest, log, testRule, [ - [ - { - field: 'host.name', - operator: 'included', - type: 'match', - value: 'mothra', - }, + const { previewId } = await previewRuleWithExceptionEntries({ + supertest, + log, + rule, + entries: [ + [ + { + field: 'host.name', + operator: 'included', + type: 'match', + value: 'mothra', + }, + ], ], - ]); - const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); - expect(signalsOpen.hits.hits.length).toBe(0); + }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).toBe(0); }); }); @@ -227,21 +205,26 @@ export default ({ getService }: FtrProviderContext) => { it('generates no signals when a value list exception is added for an ML rule', async () => { const valueListId = 'value-list-id'; await importFile(supertest, log, 'keyword', ['mothra'], valueListId); - const createdRule = await createRuleWithExceptionEntries(supertest, log, testRule, [ - [ - { - field: 'host.name', - operator: 'included', - type: 'list', - list: { - id: valueListId, - type: 'keyword', + const { previewId } = await previewRuleWithExceptionEntries({ + supertest, + log, + rule, + entries: [ + [ + { + field: 'host.name', + operator: 'included', + type: 'list', + list: { + id: valueListId, + type: 'keyword', + }, }, - }, + ], ], - ]); - const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); - expect(signalsOpen.hits.hits.length).toBe(0); + }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).toBe(0); }); }); @@ -255,10 +238,10 @@ export default ({ getService }: FtrProviderContext) => { }); it('should be enriched with host risk score', async () => { - const createdRule = await createRule(supertest, log, testRule); - const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); - expect(signalsOpen.hits.hits.length).toBe(1); - const fullSignal = signalsOpen.hits.hits[0]._source; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).toBe(1); + const fullSignal = previewAlerts[0]._source; expect(fullSignal?.host?.risk?.calculated_level).toBe('Low'); expect(fullSignal?.host?.risk?.calculated_score_norm).toBe(1); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/new_terms.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/new_terms.ts new file mode 100644 index 000000000000..4bfbe9211859 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/new_terms.ts @@ -0,0 +1,385 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; + +import { NewTermsRuleCreateProps } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; +import { orderBy } from 'lodash'; +import { getCreateNewTermsRulesSchemaMock } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema/mocks'; +import { DetectionAlert } from '@kbn/security-solution-plugin/common/detection_engine/schemas/alerts'; +import { + createRule, + deleteAllAlerts, + deleteSignalsIndex, + getOpenSignals, + getPreviewAlerts, + previewRule, +} from '../../utils'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { previewRuleWithExceptionEntries } from '../../utils/preview_rule_with_exception_entries'; +import { deleteAllExceptions } from '../../../lists_api_integration/utils'; + +const removeRandomValuedProperties = (alert: DetectionAlert | undefined) => { + if (!alert) { + return undefined; + } + const { + 'kibana.version': version, + 'kibana.alert.rule.execution.uuid': execUuid, + 'kibana.alert.rule.uuid': uuid, + '@timestamp': timestamp, + 'kibana.alert.rule.created_at': createdAt, + 'kibana.alert.rule.updated_at': updatedAt, + 'kibana.alert.uuid': alertUuid, + ...restOfAlert + } = alert; + return restOfAlert; +}; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + const es = getService('es'); + const log = getService('log'); + + describe('New terms type rules', () => { + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); + }); + + // First test creates a real rule - remaining tests use preview API + + // This test also tests that alerts are NOT created for terms that are not new: the host name + // suricata-sensor-san-francisco appears in a document at 2019-02-19T20:42:08.230Z, but also appears + // in earlier documents so is not new. An alert should not be generated for that term. + it('should generate 1 alert with 1 selected field', async () => { + const rule: NewTermsRuleCreateProps = { + ...getCreateNewTermsRulesSchemaMock('rule-1', true), + new_terms_fields: ['host.name'], + from: '2019-02-19T20:42:00.000Z', + history_window_start: '2019-01-19T20:42:00.000Z', + }; + + const createdRule = await createRule(supertest, log, rule); + const alerts = await getOpenSignals(supertest, log, es, createdRule); + + expect(alerts.hits.hits.length).eql(1); + expect(removeRandomValuedProperties(alerts.hits.hits[0]._source)).eql({ + 'kibana.alert.new_terms': ['zeek-newyork-sha-aa8df15'], + 'kibana.alert.rule.category': 'New Terms Rule', + 'kibana.alert.rule.consumer': 'siem', + 'kibana.alert.rule.name': 'Query with a rule id', + 'kibana.alert.rule.producer': 'siem', + 'kibana.alert.rule.rule_type_id': 'siem.newTermsRule', + 'kibana.space_ids': ['default'], + 'kibana.alert.rule.tags': [], + agent: { + ephemeral_id: '7cc2091a-72f1-4c63-843b-fdeb622f9c69', + hostname: 'zeek-newyork-sha-aa8df15', + id: '4b4462ef-93d2-409c-87a6-299d942e5047', + type: 'auditbeat', + version: '8.0.0', + }, + cloud: { instance: { id: '139865230' }, provider: 'digitalocean', region: 'nyc1' }, + ecs: { version: '1.0.0-beta2' }, + host: { + architecture: 'x86_64', + hostname: 'zeek-newyork-sha-aa8df15', + id: '3729d06ce9964aa98549f41cbd99334d', + ip: ['157.230.208.30', '10.10.0.6', 'fe80::24ce:f7ff:fede:a571'], + mac: ['26:ce:f7:de:a5:71'], + name: 'zeek-newyork-sha-aa8df15', + os: { + codename: 'cosmic', + family: 'debian', + kernel: '4.18.0-10-generic', + name: 'Ubuntu', + platform: 'ubuntu', + version: '18.10 (Cosmic Cuttlefish)', + }, + }, + message: + 'Login by user root (UID: 0) on pts/0 (PID: 20638) from 8.42.77.171 (IP: 8.42.77.171)', + process: { pid: 20638 }, + service: { type: 'system' }, + source: { ip: '8.42.77.171' }, + user: { id: 0, name: 'root', terminal: 'pts/0' }, + 'event.action': 'user_login', + 'event.category': 'authentication', + 'event.dataset': 'login', + 'event.kind': 'signal', + 'event.module': 'system', + 'event.origin': '/var/log/wtmp', + 'event.outcome': 'success', + 'event.type': 'authentication_success', + 'kibana.alert.original_time': '2019-02-19T20:42:08.230Z', + 'kibana.alert.ancestors': [ + { + id: 'x07wJ2oB9v5HJNSHhyxi', + type: 'event', + index: 'auditbeat-8.0.0-2019.02.19-000001', + depth: 0, + }, + ], + 'kibana.alert.status': 'active', + 'kibana.alert.workflow_status': 'open', + 'kibana.alert.depth': 1, + 'kibana.alert.reason': + 'authentication event with source 8.42.77.171 by root on zeek-newyork-sha-aa8df15 created high alert Query with a rule id.', + 'kibana.alert.severity': 'high', + 'kibana.alert.risk_score': 55, + 'kibana.alert.rule.parameters': { + description: 'Detecting root and admin users', + risk_score: 55, + severity: 'high', + author: [], + false_positives: [], + from: '2019-02-19T20:42:00.000Z', + rule_id: 'rule-1', + max_signals: 100, + risk_score_mapping: [], + severity_mapping: [], + threat: [], + to: 'now', + references: [], + version: 1, + exceptions_list: [], + immutable: false, + related_integrations: [], + required_fields: [], + setup: '', + type: 'new_terms', + query: '*', + new_terms_fields: ['host.name'], + history_window_start: '2019-01-19T20:42:00.000Z', + index: ['auditbeat-*'], + language: 'kuery', + }, + 'kibana.alert.rule.actions': [], + 'kibana.alert.rule.author': [], + 'kibana.alert.rule.created_by': 'elastic', + 'kibana.alert.rule.description': 'Detecting root and admin users', + 'kibana.alert.rule.enabled': true, + 'kibana.alert.rule.exceptions_list': [], + 'kibana.alert.rule.false_positives': [], + 'kibana.alert.rule.from': '2019-02-19T20:42:00.000Z', + 'kibana.alert.rule.immutable': false, + 'kibana.alert.rule.indices': ['auditbeat-*'], + 'kibana.alert.rule.interval': '5m', + 'kibana.alert.rule.max_signals': 100, + 'kibana.alert.rule.references': [], + 'kibana.alert.rule.risk_score_mapping': [], + 'kibana.alert.rule.rule_id': 'rule-1', + 'kibana.alert.rule.severity_mapping': [], + 'kibana.alert.rule.threat': [], + 'kibana.alert.rule.to': 'now', + 'kibana.alert.rule.type': 'new_terms', + 'kibana.alert.rule.updated_by': 'elastic', + 'kibana.alert.rule.version': 1, + 'kibana.alert.rule.risk_score': 55, + 'kibana.alert.rule.severity': 'high', + 'kibana.alert.original_event.action': 'user_login', + 'kibana.alert.original_event.category': 'authentication', + 'kibana.alert.original_event.dataset': 'login', + 'kibana.alert.original_event.kind': 'event', + 'kibana.alert.original_event.module': 'system', + 'kibana.alert.original_event.origin': '/var/log/wtmp', + 'kibana.alert.original_event.outcome': 'success', + 'kibana.alert.original_event.type': 'authentication_success', + }); + }); + + it('should generate 3 alerts when 1 document has 3 new values', async () => { + const rule: NewTermsRuleCreateProps = { + ...getCreateNewTermsRulesSchemaMock('rule-1', true), + new_terms_fields: ['host.ip'], + from: '2019-02-19T20:42:00.000Z', + history_window_start: '2019-01-19T20:42:00.000Z', + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + + expect(previewAlerts.length).eql(3); + const previewAlertsOrderedByHostIp = orderBy( + previewAlerts, + '_source.kibana.alert.new_terms', + 'asc' + ); + expect(previewAlertsOrderedByHostIp[0]._source?.['kibana.alert.new_terms']).eql([ + '10.10.0.6', + ]); + expect(previewAlertsOrderedByHostIp[1]._source?.['kibana.alert.new_terms']).eql([ + '157.230.208.30', + ]); + expect(previewAlertsOrderedByHostIp[2]._source?.['kibana.alert.new_terms']).eql([ + 'fe80::24ce:f7ff:fede:a571', + ]); + }); + + it('should generate alerts for every term when history window is small', async () => { + const rule: NewTermsRuleCreateProps = { + ...getCreateNewTermsRulesSchemaMock('rule-1', true), + new_terms_fields: ['host.name'], + from: '2019-02-19T20:42:00.000Z', + // Set the history_window_start close to 'from' so we should alert on all terms in the time range + history_window_start: '2019-02-19T20:41:59.000Z', + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + + expect(previewAlerts.length).eql(5); + const hostNames = previewAlerts + .map((signal) => signal._source?.['kibana.alert.new_terms']) + .sort(); + expect(hostNames[0]).eql(['suricata-sensor-amsterdam']); + expect(hostNames[1]).eql(['suricata-sensor-san-francisco']); + expect(hostNames[2]).eql(['zeek-newyork-sha-aa8df15']); + expect(hostNames[3]).eql(['zeek-sensor-amsterdam']); + expect(hostNames[4]).eql(['zeek-sensor-san-francisco']); + }); + + describe('timestamp override and fallback', () => { + before(async () => { + await esArchiver.load( + 'x-pack/test/functional/es_archives/security_solution/timestamp_fallback' + ); + await esArchiver.load( + 'x-pack/test/functional/es_archives/security_solution/timestamp_override_3' + ); + }); + after(async () => { + await esArchiver.unload( + 'x-pack/test/functional/es_archives/security_solution/timestamp_fallback' + ); + await esArchiver.unload( + 'x-pack/test/functional/es_archives/security_solution/timestamp_override_3' + ); + }); + + it('should generate the correct alerts', async () => { + const rule: NewTermsRuleCreateProps = { + ...getCreateNewTermsRulesSchemaMock('rule-1', true), + // myfakeindex-3 does not have event.ingested mapped so we can test if the runtime field + // 'kibana.combined_timestamp' handles unmapped fields properly + index: ['timestamp-fallback-test', 'myfakeindex-3'], + new_terms_fields: ['host.name'], + from: '2020-12-16T16:00:00.000Z', + // Set the history_window_start close to 'from' so we should alert on all terms in the time range + history_window_start: '2020-12-16T15:59:00.000Z', + timestamp_override: 'event.ingested', + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + + expect(previewAlerts.length).eql(2); + const hostNames = previewAlerts + .map((signal) => signal._source?.['kibana.alert.new_terms']) + .sort(); + expect(hostNames[0]).eql(['host-3']); + expect(hostNames[1]).eql(['host-4']); + }); + }); + + describe('with exceptions', async () => { + afterEach(async () => { + await deleteAllExceptions(supertest, log); + }); + + it('should apply exceptions', async () => { + const rule: NewTermsRuleCreateProps = { + ...getCreateNewTermsRulesSchemaMock('rule-1', true), + new_terms_fields: ['host.name'], + from: '2019-02-19T20:42:00.000Z', + // Set the history_window_start close to 'from' so we should alert on all terms in the time range + history_window_start: '2019-02-19T20:41:59.000Z', + }; + + const { previewId } = await previewRuleWithExceptionEntries({ + supertest, + log, + rule, + entries: [ + [ + { + field: 'host.name', + operator: 'included', + type: 'match', + value: 'zeek-sensor-san-francisco', + }, + ], + ], + }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + + expect(previewAlerts.length).eql(4); + const hostNames = previewAlerts + .map((signal) => signal._source?.['kibana.alert.new_terms']) + .sort(); + expect(hostNames[0]).eql(['suricata-sensor-amsterdam']); + expect(hostNames[1]).eql(['suricata-sensor-san-francisco']); + expect(hostNames[2]).eql(['zeek-newyork-sha-aa8df15']); + expect(hostNames[3]).eql(['zeek-sensor-amsterdam']); + }); + }); + + it('should work for max signals > 100', async () => { + const maxSignals = 200; + const rule: NewTermsRuleCreateProps = { + ...getCreateNewTermsRulesSchemaMock('rule-1', true), + new_terms_fields: ['process.pid'], + from: '2018-02-19T20:42:00.000Z', + // Set the history_window_start close to 'from' so we should alert on all terms in the time range + history_window_start: '2018-02-19T20:41:59.000Z', + max_signals: maxSignals, + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId, size: maxSignals * 2 }); + + expect(previewAlerts.length).eql(maxSignals); + const processPids = previewAlerts + .map((signal) => signal._source?.['kibana.alert.new_terms']) + .sort(); + expect(processPids[0]).eql([1]); + }); + + describe('alerts should be be enriched', () => { + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/entity/host_risk'); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/entity/host_risk'); + }); + + it('should be enriched with host risk score', async () => { + const rule: NewTermsRuleCreateProps = { + ...getCreateNewTermsRulesSchemaMock('rule-1', true), + new_terms_fields: ['host.name'], + from: '2019-02-19T20:42:00.000Z', + history_window_start: '2019-01-19T20:42:00.000Z', + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + + expect(previewAlerts[0]?._source?.host?.risk?.calculated_level).to.eql('Low'); + expect(previewAlerts[0]?._source?.host?.risk?.calculated_score_norm).to.eql(23); + }); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/query.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/query.ts new file mode 100644 index 000000000000..8090e4d2ce70 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/query.ts @@ -0,0 +1,426 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { + ALERT_RISK_SCORE, + ALERT_RULE_PARAMETERS, + ALERT_RULE_RULE_ID, + ALERT_SEVERITY, + ALERT_WORKFLOW_STATUS, +} from '@kbn/rule-data-utils'; +import { flattenWithPrefix } from '@kbn/securitysolution-rules'; + +import { orderBy } from 'lodash'; + +import { QueryRuleCreateProps } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; +import { Ancestor } from '@kbn/security-solution-plugin/server/lib/detection_engine/signals/types'; +import { + ALERT_ANCESTORS, + ALERT_DEPTH, + ALERT_ORIGINAL_TIME, + ALERT_ORIGINAL_EVENT, +} from '@kbn/security-solution-plugin/common/field_maps/field_names'; +import { + createRule, + deleteAllAlerts, + deleteSignalsIndex, + getOpenSignals, + getPreviewAlerts, + getRuleForSignalTesting, + getSimpleRule, + previewRule, +} from '../../utils'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; + +/** + * Specific _id to use for some of the tests. If the archiver changes and you see errors + * here, update this to a new value of a chosen auditbeat record and update the tests values. + */ +const ID = 'BhbXBmkBR346wHgn4PeZ'; + +/** + * Test coverage: + * [x] - Happy path generating 1 alert + * [x] - Rule type respects max signals + * [x] - Alerts on alerts + */ + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + const es = getService('es'); + const log = getService('log'); + + describe('Query type rules', () => { + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); + await esArchiver.load('x-pack/test/functional/es_archives/security_solution/alerts/8.1.0'); + await esArchiver.load('x-pack/test/functional/es_archives/signals/severity_risk_overrides'); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); + await esArchiver.unload('x-pack/test/functional/es_archives/security_solution/alerts/8.1.0'); + await esArchiver.unload('x-pack/test/functional/es_archives/signals/severity_risk_overrides'); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); + }); + + // First test creates a real rule - remaining tests use preview API + it('should have the specific audit record for _id or none of these tests below will pass', async () => { + const rule: QueryRuleCreateProps = { + ...getRuleForSignalTesting(['auditbeat-*']), + query: `_id:${ID}`, + }; + const createdRule = await createRule(supertest, log, rule); + const alerts = await getOpenSignals(supertest, log, es, createdRule); + expect(alerts.hits.hits.length).greaterThan(0); + expect(alerts.hits.hits[0]._source?.['kibana.alert.ancestors'][0].id).eql(ID); + }); + + it('should abide by max_signals > 100', async () => { + const maxSignals = 200; + const rule: QueryRuleCreateProps = { + ...getRuleForSignalTesting(['auditbeat-*']), + max_signals: maxSignals, + }; + const { previewId } = await previewRule({ supertest, rule }); + // Search for 2x max_signals to make sure we aren't making more than max_signals + const previewAlerts = await getPreviewAlerts({ es, previewId, size: maxSignals * 2 }); + expect(previewAlerts.length).equal(maxSignals); + }); + + it('should have recorded the rule_id within the signal', async () => { + const rule: QueryRuleCreateProps = { + ...getRuleForSignalTesting(['auditbeat-*']), + query: `_id:${ID}`, + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts[0]._source?.[ALERT_RULE_RULE_ID]).eql(getSimpleRule().rule_id); + }); + + it('should query and get back expected signal structure using a basic KQL query', async () => { + const rule: QueryRuleCreateProps = { + ...getRuleForSignalTesting(['auditbeat-*']), + query: `_id:${ID}`, + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + const signal = previewAlerts[0]._source; + + expect(signal).eql({ + ...signal, + [ALERT_ANCESTORS]: [ + { + id: 'BhbXBmkBR346wHgn4PeZ', + type: 'event', + index: 'auditbeat-8.0.0-2019.02.19-000001', + depth: 0, + }, + ], + [ALERT_WORKFLOW_STATUS]: 'open', + [ALERT_DEPTH]: 1, + [ALERT_ORIGINAL_TIME]: '2019-02-19T17:40:03.790Z', + ...flattenWithPrefix(ALERT_ORIGINAL_EVENT, { + action: 'socket_closed', + dataset: 'socket', + kind: 'event', + module: 'system', + }), + }); + }); + + it('should query and get back expected signal structure when it is a signal on a signal', async () => { + const alertId = '30a75fe46d3dbdfab55982036f77a8d60e2d1112e96f277c3b8c22f9bb57817a'; + const rule: QueryRuleCreateProps = { + ...getRuleForSignalTesting([`.alerts-security.alerts-default*`]), + rule_id: 'signal-on-signal', + query: `_id:${alertId}`, + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + + expect(previewAlerts.length).to.eql(1); + + const signal = previewAlerts[0]._source; + + if (!signal) { + return expect(signal).to.be.ok(); + } + + expect(signal).eql({ + ...signal, + [ALERT_ANCESTORS]: [ + { + id: 'ahEToH8BK09aFtXZFVMq', + type: 'event', + index: 'events-index-000001', + depth: 0, + }, + { + rule: '031d5c00-a72f-11ec-a8a3-7b1c8077fc3e', + id: '30a75fe46d3dbdfab55982036f77a8d60e2d1112e96f277c3b8c22f9bb57817a', + type: 'signal', + index: '.internal.alerts-security.alerts-default-000001', + depth: 1, + }, + ], + [ALERT_WORKFLOW_STATUS]: 'open', + [ALERT_DEPTH]: 2, + [ALERT_ORIGINAL_TIME]: '2022-03-19T02:48:12.634Z', + ...flattenWithPrefix(ALERT_ORIGINAL_EVENT, { + agent_id_status: 'verified', + ingested: '2022-03-19T02:47:57.376Z', + dataset: 'elastic_agent.filebeat', + }), + }); + }); + + it('should not have risk score fields without risk indices', async () => { + const rule: QueryRuleCreateProps = { + ...getRuleForSignalTesting(['auditbeat-*']), + query: `_id:${ID}`, + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts[0]?._source?.host?.risk).to.eql(undefined); + expect(previewAlerts[0]?._source?.user?.risk).to.eql(undefined); + }); + + describe('with host risk index', () => { + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/entity/host_risk'); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/entity/host_risk'); + }); + + it('should host have risk score field and do not have user risk score', async () => { + const rule: QueryRuleCreateProps = { + ...getRuleForSignalTesting(['auditbeat-*']), + query: `_id:${ID} or _id:GBbXBmkBR346wHgn5_eR or _id:x10zJ2oE9v5HJNSHhyxi`, + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + + const firstAlert = previewAlerts.find( + (alert) => alert?._source?.host?.name === 'suricata-zeek-sensor-toronto' + ); + const secondAlert = previewAlerts.find( + (alert) => alert?._source?.host?.name === 'suricata-sensor-london' + ); + const thirdAlert = previewAlerts.find( + (alert) => alert?._source?.host?.name === 'IE11WIN8_1' + ); + + expect(firstAlert?._source?.host?.risk?.calculated_level).to.eql('Critical'); + expect(firstAlert?._source?.host?.risk?.calculated_score_norm).to.eql(96); + expect(firstAlert?._source?.user?.risk).to.eql(undefined); + expect(secondAlert?._source?.host?.risk?.calculated_level).to.eql('Low'); + expect(secondAlert?._source?.host?.risk?.calculated_score_norm).to.eql(20); + expect(thirdAlert?._source?.host?.risk).to.eql(undefined); + }); + }); + + describe('with host and user risk indices', () => { + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/entity/host_risk'); + await esArchiver.load('x-pack/test/functional/es_archives/entity/user_risk'); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/entity/host_risk'); + await esArchiver.unload('x-pack/test/functional/es_archives/entity/user_risk'); + }); + + it('should have host and user risk score fields', async () => { + const rule: QueryRuleCreateProps = { + ...getRuleForSignalTesting(['auditbeat-*']), + query: `_id:${ID}`, + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts[0]?._source?.host?.risk?.calculated_level).to.eql('Critical'); + expect(previewAlerts[0]?._source?.host?.risk?.calculated_score_norm).to.eql(96); + expect(previewAlerts[0]?._source?.user?.risk?.calculated_level).to.eql('Low'); + expect(previewAlerts[0]?._source?.user?.risk?.calculated_score_norm).to.eql(11); + }); + }); + + /** + * Here we test the functionality of Severity and Risk Score overrides (also called "mappings" + * in the code). If the rule specifies a mapping, then the final Severity or Risk Score + * value of the signal will be taken from the mapped field of the source event. + */ + it('should get default severity and risk score if there is no mapping', async () => { + const rule: QueryRuleCreateProps = { + ...getRuleForSignalTesting(['signal_overrides']), + severity: 'medium', + risk_score: 75, + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + + expect(previewAlerts.length).equal(4); + previewAlerts.forEach((alert) => { + expect(alert._source?.[ALERT_SEVERITY]).equal('medium'); + expect(alert._source?.[ALERT_RULE_PARAMETERS].severity_mapping).eql([]); + + expect(alert._source?.[ALERT_RISK_SCORE]).equal(75); + expect(alert._source?.[ALERT_RULE_PARAMETERS].risk_score_mapping).eql([]); + }); + }); + + it('should get overridden severity if the rule has a mapping for it', async () => { + const rule: QueryRuleCreateProps = { + ...getRuleForSignalTesting(['signal_overrides']), + severity: 'medium', + severity_mapping: [ + { field: 'my_severity', operator: 'equals', value: 'sev_900', severity: 'high' }, + { field: 'my_severity', operator: 'equals', value: 'sev_max', severity: 'critical' }, + ], + risk_score: 75, + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + const alertsOrderedByParentId = orderBy(previewAlerts, 'signal.parent.id', 'asc'); + const severities = alertsOrderedByParentId.map((alert) => ({ + id: (alert._source?.[ALERT_ANCESTORS] as Ancestor[])[0].id, + value: alert._source?.[ALERT_SEVERITY], + })); + + expect(alertsOrderedByParentId.length).equal(4); + expect(severities).eql([ + { id: '1', value: 'high' }, + { id: '2', value: 'critical' }, + { id: '3', value: 'critical' }, + { id: '4', value: 'critical' }, + ]); + + alertsOrderedByParentId.forEach((alert) => { + expect(alert._source?.[ALERT_RISK_SCORE]).equal(75); + expect(alert._source?.[ALERT_RULE_PARAMETERS].risk_score_mapping).eql([]); + expect(alert._source?.[ALERT_RULE_PARAMETERS].severity_mapping).eql([ + { field: 'my_severity', operator: 'equals', value: 'sev_900', severity: 'high' }, + { field: 'my_severity', operator: 'equals', value: 'sev_max', severity: 'critical' }, + ]); + }); + }); + + it('should get overridden risk score if the rule has a mapping for it', async () => { + const rule: QueryRuleCreateProps = { + ...getRuleForSignalTesting(['signal_overrides']), + severity: 'medium', + risk_score: 75, + risk_score_mapping: [ + { field: 'my_risk', operator: 'equals', value: '', risk_score: undefined }, + ], + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + const alertsOrderedByParentId = orderBy(previewAlerts, 'signal.parent.id', 'asc'); + const riskScores = alertsOrderedByParentId.map((alert) => ({ + id: (alert._source?.[ALERT_ANCESTORS] as Ancestor[])[0].id, + value: alert._source?.[ALERT_RISK_SCORE], + })); + + expect(alertsOrderedByParentId.length).equal(4); + expect(riskScores).eql([ + { id: '1', value: 31.14 }, + { id: '2', value: 32.14 }, + { id: '3', value: 33.14 }, + { id: '4', value: 34.14 }, + ]); + + alertsOrderedByParentId.forEach((alert) => { + expect(alert._source?.[ALERT_SEVERITY]).equal('medium'); + expect(alert._source?.[ALERT_RULE_PARAMETERS].severity_mapping).eql([]); + expect(alert._source?.[ALERT_RULE_PARAMETERS].risk_score_mapping).eql([ + { field: 'my_risk', operator: 'equals', value: '' }, + ]); + }); + }); + + it('should get overridden severity and risk score if the rule has both mappings', async () => { + const rule: QueryRuleCreateProps = { + ...getRuleForSignalTesting(['signal_overrides']), + severity: 'medium', + severity_mapping: [ + { field: 'my_severity', operator: 'equals', value: 'sev_900', severity: 'high' }, + { field: 'my_severity', operator: 'equals', value: 'sev_max', severity: 'critical' }, + ], + risk_score: 75, + risk_score_mapping: [ + { field: 'my_risk', operator: 'equals', value: '', risk_score: undefined }, + ], + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + const alertsOrderedByParentId = orderBy(previewAlerts, 'signal.parent.id', 'asc'); + const values = alertsOrderedByParentId.map((alert) => ({ + id: (alert._source?.[ALERT_ANCESTORS] as Ancestor[])[0].id, + severity: alert._source?.[ALERT_SEVERITY], + risk: alert._source?.[ALERT_RISK_SCORE], + })); + + expect(alertsOrderedByParentId.length).equal(4); + expect(values).eql([ + { id: '1', severity: 'high', risk: 31.14 }, + { id: '2', severity: 'critical', risk: 32.14 }, + { id: '3', severity: 'critical', risk: 33.14 }, + { id: '4', severity: 'critical', risk: 34.14 }, + ]); + + alertsOrderedByParentId.forEach((alert) => { + expect(alert._source?.[ALERT_RULE_PARAMETERS].severity_mapping).eql([ + { field: 'my_severity', operator: 'equals', value: 'sev_900', severity: 'high' }, + { field: 'my_severity', operator: 'equals', value: 'sev_max', severity: 'critical' }, + ]); + expect(alert._source?.[ALERT_RULE_PARAMETERS].risk_score_mapping).eql([ + { field: 'my_risk', operator: 'equals', value: '' }, + ]); + }); + }); + + it('should generate signals with name_override field', async () => { + const rule: QueryRuleCreateProps = { + ...getRuleForSignalTesting(['auditbeat-*']), + query: `event.action:boot`, + rule_name_override: 'event.action', + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + const fullSignal = previewAlerts[0]; + if (!fullSignal) { + return expect(fullSignal).to.be.ok(); + } + + expect(previewAlerts[0]._source?.['kibana.alert.rule.name']).to.eql('boot'); + }); + + it('should not generate duplicate signals', async () => { + const rule: QueryRuleCreateProps = { + ...getRuleForSignalTesting(['auditbeat-*']), + query: `_id:${ID}`, + }; + + const { previewId } = await previewRule({ supertest, rule, invocationCount: 2 }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).to.eql(1); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/saved_query.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/saved_query.ts new file mode 100644 index 000000000000..c6d26e994a99 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/saved_query.ts @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { ALERT_WORKFLOW_STATUS } from '@kbn/rule-data-utils'; +import { flattenWithPrefix } from '@kbn/securitysolution-rules'; + +import { SavedQueryRuleCreateProps } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; +import { + ALERT_ANCESTORS, + ALERT_DEPTH, + ALERT_ORIGINAL_TIME, + ALERT_ORIGINAL_EVENT, +} from '@kbn/security-solution-plugin/common/field_maps/field_names'; +import { + createRule, + deleteAllAlerts, + deleteSignalsIndex, + getOpenSignals, + getRuleForSignalTesting, +} from '../../utils'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; + +/** + * Specific _id to use for some of the tests. If the archiver changes and you see errors + * here, update this to a new value of a chosen auditbeat record and update the tests values. + */ +const ID = 'BhbXBmkBR346wHgn4PeZ'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + const es = getService('es'); + const log = getService('log'); + + describe('Saved query type rules', () => { + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); + }); + + // First test creates a real rule - remaining tests use preview API + it('should query and get back expected signal structure using a saved query rule', async () => { + const rule: SavedQueryRuleCreateProps = { + ...getRuleForSignalTesting(['auditbeat-*']), + type: 'saved_query', + query: `_id:${ID}`, + saved_id: 'doesnt-exist', + }; + const createdRule = await createRule(supertest, log, rule); + const alerts = await getOpenSignals(supertest, log, es, createdRule); + const signal = alerts.hits.hits[0]._source; + expect(signal).eql({ + ...signal, + [ALERT_ANCESTORS]: [ + { + id: 'BhbXBmkBR346wHgn4PeZ', + type: 'event', + index: 'auditbeat-8.0.0-2019.02.19-000001', + depth: 0, + }, + ], + [ALERT_WORKFLOW_STATUS]: 'open', + [ALERT_DEPTH]: 1, + [ALERT_ORIGINAL_TIME]: '2019-02-19T17:40:03.790Z', + ...flattenWithPrefix(ALERT_ORIGINAL_EVENT, { + action: 'socket_closed', + dataset: 'socket', + kind: 'event', + module: 'system', + }), + }); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/threat_match.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/threat_match.ts new file mode 100644 index 000000000000..dfa1f81f6c5d --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/threat_match.ts @@ -0,0 +1,1306 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { get, isEqual } from 'lodash'; +import expect from '@kbn/expect'; +import { + ALERT_REASON, + ALERT_RULE_UUID, + ALERT_STATUS, + ALERT_RULE_NAMESPACE, + ALERT_RULE_UPDATED_AT, + ALERT_UUID, + ALERT_WORKFLOW_STATUS, + SPACE_IDS, + VERSION, +} from '@kbn/rule-data-utils'; +import { flattenWithPrefix } from '@kbn/securitysolution-rules'; + +import { ThreatMatchRuleCreateProps } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; + +import { ENRICHMENT_TYPES } from '@kbn/security-solution-plugin/common/cti/constants'; +import { Ancestor } from '@kbn/security-solution-plugin/server/lib/detection_engine/signals/types'; +import { + ALERT_ANCESTORS, + ALERT_DEPTH, + ALERT_ORIGINAL_EVENT_ACTION, + ALERT_ORIGINAL_EVENT_CATEGORY, + ALERT_ORIGINAL_EVENT_MODULE, + ALERT_ORIGINAL_TIME, +} from '@kbn/security-solution-plugin/common/field_maps/field_names'; +import { + previewRule, + getOpenSignals, + getPreviewAlerts, + deleteSignalsIndex, + deleteAllAlerts, + createRule, +} from '../../utils'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; + +const format = (value: unknown): string => JSON.stringify(value, null, 2); + +// Asserts that each expected value is included in the subject, independent of +// ordering. Uses _.isEqual for value comparison. +const assertContains = (subject: unknown[], expected: unknown[]) => + expected.forEach((expectedValue) => + expect(subject.some((value) => isEqual(value, expectedValue))).to.eql( + true, + `expected ${format(subject)} to contain ${format(expectedValue)}` + ) + ); + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const esArchiver = getService('esArchiver'); + const supertest = getService('supertest'); + const es = getService('es'); + const log = getService('log'); + + /** + * Specific api integration tests for threat matching rule type + */ + describe('Threat match type rules', () => { + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); + }); + + // First test creates a real rule - remaining tests use preview API + it('should be able to execute and get 10 signals when doing a specific query', async () => { + const rule: ThreatMatchRuleCreateProps = { + description: 'Detecting root and admin users', + name: 'Query with a rule id', + severity: 'high', + index: ['auditbeat-*'], + type: 'threat_match', + risk_score: 55, + language: 'kuery', + rule_id: 'rule-1', + from: '1900-01-01T00:00:00.000Z', + query: '*:*', + threat_query: 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip + threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity + threat_mapping: [ + // We match host.name against host.name + { + entries: [ + { + field: 'host.name', + value: 'host.name', + type: 'mapping', + }, + ], + }, + ], + threat_filters: [], + }; + + const createdRule = await createRule(supertest, log, rule); + const alerts = await getOpenSignals(supertest, log, es, createdRule); + expect(alerts.hits.hits.length).equal(10); + const fullSource = alerts.hits.hits.find( + (signal) => + (signal._source?.[ALERT_ANCESTORS] as Ancestor[])[0].id === '7yJ-B2kBR346wHgnhlMn' + ); + const fullSignal = fullSource?._source; + if (!fullSignal) { + return expect(fullSignal).to.be.ok(); + } + expect(fullSignal).eql({ + ...fullSignal, + '@timestamp': fullSignal['@timestamp'], + agent: { + ephemeral_id: '1b4978a0-48be-49b1-ac96-323425b389ab', + hostname: 'zeek-sensor-amsterdam', + id: 'e52588e6-7aa3-4c89-a2c4-d6bc5c286db1', + type: 'auditbeat', + version: '8.0.0', + }, + auditd: { + data: { + hostname: '46.101.47.213', + op: 'PAM:bad_ident', + terminal: 'ssh', + }, + message_type: 'user_err', + result: 'fail', + sequence: 2267, + session: 'unset', + summary: { + actor: { + primary: 'unset', + secondary: 'root', + }, + how: '/usr/sbin/sshd', + object: { + primary: 'ssh', + secondary: '46.101.47.213', + type: 'user-session', + }, + }, + }, + cloud: { + instance: { + id: '133551048', + }, + provider: 'digitalocean', + region: 'ams3', + }, + ecs: { + version: '1.0.0-beta2', + }, + ...flattenWithPrefix('event', { + action: 'error', + category: 'user-login', + module: 'auditd', + kind: 'signal', + }), + host: { + architecture: 'x86_64', + containerized: false, + hostname: 'zeek-sensor-amsterdam', + id: '2ce8b1e7d69e4a1d9c6bcddc473da9d9', + name: 'zeek-sensor-amsterdam', + os: { + codename: 'bionic', + family: 'debian', + kernel: '4.15.0-45-generic', + name: 'Ubuntu', + platform: 'ubuntu', + version: '18.04.2 LTS (Bionic Beaver)', + }, + }, + network: { + direction: 'incoming', + }, + process: { + executable: '/usr/sbin/sshd', + pid: 32739, + }, + service: { + type: 'auditd', + }, + source: { + ip: '46.101.47.213', + }, + user: { + audit: { + id: 'unset', + }, + id: '0', + name: 'root', + }, + [ALERT_ANCESTORS]: [ + { + id: '7yJ-B2kBR346wHgnhlMn', + type: 'event', + index: 'auditbeat-8.0.0-2019.02.19-000001', + depth: 0, + }, + ], + [ALERT_DEPTH]: 1, + [ALERT_ORIGINAL_EVENT_ACTION]: 'error', + [ALERT_ORIGINAL_EVENT_CATEGORY]: 'user-login', + [ALERT_ORIGINAL_EVENT_MODULE]: 'auditd', + [ALERT_ORIGINAL_TIME]: fullSignal[ALERT_ORIGINAL_TIME], + [ALERT_REASON]: + 'user-login event with source 46.101.47.213 by root on zeek-sensor-amsterdam created high alert Query with a rule id.', + [ALERT_RULE_UUID]: fullSignal[ALERT_RULE_UUID], + [ALERT_STATUS]: 'active', + [ALERT_UUID]: fullSignal[ALERT_UUID], + [ALERT_WORKFLOW_STATUS]: 'open', + [SPACE_IDS]: ['default'], + [VERSION]: fullSignal[VERSION], + threat: { + enrichments: get(fullSignal, 'threat.enrichments'), + }, + ...flattenWithPrefix(ALERT_RULE_NAMESPACE, { + actions: [], + author: [], + category: 'Indicator Match Rule', + consumer: 'siem', + created_by: 'elastic', + description: 'Detecting root and admin users', + enabled: true, + exceptions_list: [], + false_positives: [], + from: '1900-01-01T00:00:00.000Z', + immutable: false, + interval: '5m', + max_signals: 100, + name: 'Query with a rule id', + producer: 'siem', + references: [], + risk_score: 55, + risk_score_mapping: [], + rule_type_id: 'siem.indicatorRule', + severity: 'high', + severity_mapping: [], + tags: [], + threat: [], + to: 'now', + type: 'threat_match', + updated_at: fullSignal[ALERT_RULE_UPDATED_AT], + updated_by: 'elastic', + uuid: fullSignal[ALERT_RULE_UUID], + version: 1, + }), + }); + }); + + it('should return 0 matches if the mapping does not match against anything in the mapping', async () => { + const rule: ThreatMatchRuleCreateProps = { + description: 'Detecting root and admin users', + name: 'Query with a rule id', + severity: 'high', + index: ['auditbeat-*'], + type: 'threat_match', + risk_score: 55, + language: 'kuery', + rule_id: 'rule-1', + from: '1900-01-01T00:00:00.000Z', + query: '*:*', + threat_query: 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip + threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity + threat_mapping: [ + // We match host.name against host.name + { + entries: [ + { + field: 'host.name', + value: 'invalid.mapping.value', // invalid mapping value + type: 'mapping', + }, + ], + }, + ], + threat_filters: [], + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).equal(0); + }); + + it('should return 0 signals when using an AND and one of the clauses does not have data', async () => { + const rule: ThreatMatchRuleCreateProps = { + description: 'Detecting root and admin users', + name: 'Query with a rule id', + severity: 'high', + index: ['auditbeat-*'], + type: 'threat_match', + risk_score: 55, + language: 'kuery', + rule_id: 'rule-1', + from: '1900-01-01T00:00:00.000Z', + query: '*:*', + threat_query: 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip + threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity + threat_mapping: [ + { + entries: [ + { + field: 'source.ip', + value: 'source.ip', + type: 'mapping', + }, + { + field: 'source.ip', + value: 'destination.ip', // All records from the threat query do NOT have destination.ip, so those records that do not should drop this entire AND clause. + type: 'mapping', + }, + ], + }, + ], + threat_filters: [], + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).equal(0); + }); + + it('should return 0 signals when using an AND and one of the clauses has a made up value that does not exist', async () => { + const rule: ThreatMatchRuleCreateProps = { + description: 'Detecting root and admin users', + name: 'Query with a rule id', + severity: 'high', + type: 'threat_match', + index: ['auditbeat-*'], + risk_score: 55, + language: 'kuery', + rule_id: 'rule-1', + from: '1900-01-01T00:00:00.000Z', + query: '*:*', + threat_query: 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip + threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity + threat_mapping: [ + { + entries: [ + { + field: 'source.ip', + value: 'source.ip', + type: 'mapping', + }, + { + field: 'source.ip', + value: 'made.up.non.existent.field', // made up field should not match + type: 'mapping', + }, + ], + }, + ], + threat_filters: [], + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).equal(0); + }); + + describe('timeout behavior', () => { + // TODO: unskip this and see if we can make it not flaky + it.skip('will return an error if a rule execution exceeds the rule interval', async () => { + const rule: ThreatMatchRuleCreateProps = { + description: 'Detecting root and admin users', + name: 'Query with a short interval', + severity: 'high', + index: ['auditbeat-*'], + type: 'threat_match', + risk_score: 55, + language: 'kuery', + rule_id: 'rule-1', + from: '1900-01-01T00:00:00.000Z', + query: '*:*', + threat_query: '*:*', // broad query to take more time + threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity + threat_mapping: [ + { + entries: [ + { + field: 'host.name', + value: 'host.name', + type: 'mapping', + }, + ], + }, + ], + threat_filters: [], + concurrent_searches: 1, + interval: '1s', // short interval + items_per_search: 1, // iterate only 1 threat item per loop to ensure we're slow + }; + + const { logs } = await previewRule({ supertest, rule }); + expect(logs[0].errors[0]).to.contain('execution has exceeded its allotted interval'); + }); + }); + + describe('indicator enrichment: threat-first search', () => { + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/filebeat/threat_intel'); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/filebeat/threat_intel'); + }); + + it('enriches signals with the single indicator that matched', async () => { + const rule: ThreatMatchRuleCreateProps = { + description: 'Detecting root and admin users', + name: 'Query with a rule id', + severity: 'high', + index: ['auditbeat-*'], + type: 'threat_match', + risk_score: 55, + language: 'kuery', + rule_id: 'rule-1', + from: '1900-01-01T00:00:00.000Z', + query: '*:*', // narrow events down to 2 with a destination.ip + threat_indicator_path: 'threat.indicator', + threat_query: 'threat.indicator.domain: 159.89.119.67', // narrow things down to indicators with a domain + threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module + threat_mapping: [ + { + entries: [ + { + value: 'threat.indicator.domain', + field: 'destination.ip', + type: 'mapping', + }, + ], + }, + ], + threat_filters: [], + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).equal(2); + + const threats = previewAlerts.map((hit) => hit._source?.threat); + expect(threats).to.eql([ + { + enrichments: [ + { + feed: {}, + indicator: { + description: "domain should match the auditbeat hosts' data's source.ip", + domain: '159.89.119.67', + first_seen: '2021-01-26T11:09:04.000Z', + provider: 'geenensp', + url: { + full: 'http://159.89.119.67:59600/bin.sh', + scheme: 'http', + }, + type: 'url', + }, + matched: { + atomic: '159.89.119.67', + id: '978783', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'destination.ip', + type: ENRICHMENT_TYPES.IndicatorMatchRule, + }, + }, + ], + }, + { + enrichments: [ + { + feed: {}, + indicator: { + description: "domain should match the auditbeat hosts' data's source.ip", + domain: '159.89.119.67', + first_seen: '2021-01-26T11:09:04.000Z', + provider: 'geenensp', + url: { + full: 'http://159.89.119.67:59600/bin.sh', + scheme: 'http', + }, + type: 'url', + }, + matched: { + atomic: '159.89.119.67', + id: '978783', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'destination.ip', + type: ENRICHMENT_TYPES.IndicatorMatchRule, + }, + }, + ], + }, + ]); + }); + + it('enriches signals with multiple indicators if several matched', async () => { + const rule: ThreatMatchRuleCreateProps = { + description: 'Detecting root and admin users', + name: 'Query with a rule id', + severity: 'high', + index: ['auditbeat-*'], + type: 'threat_match', + risk_score: 55, + language: 'kuery', + rule_id: 'rule-1', + from: '1900-01-01T00:00:00.000Z', + query: 'NOT source.port:35326', // specify query to have signals more than treat indicators, but only 1 will match + threat_indicator_path: 'threat.indicator', + threat_query: 'threat.indicator.ip: *', + threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module + threat_mapping: [ + { + entries: [ + { + value: 'threat.indicator.ip', + field: 'source.ip', + type: 'mapping', + }, + ], + }, + ], + threat_filters: [], + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).equal(1); + + const [threat] = previewAlerts.map((hit) => hit._source?.threat) as Array<{ + enrichments: unknown[]; + }>; + + assertContains(threat.enrichments, [ + { + feed: {}, + indicator: { + description: 'this should match auditbeat/hosts on both port and ip', + first_seen: '2021-01-26T11:06:03.000Z', + ip: '45.115.45.3', + port: 57324, + provider: 'geenensp', + type: 'url', + }, + matched: { + atomic: '45.115.45.3', + id: '978785', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'source.ip', + type: ENRICHMENT_TYPES.IndicatorMatchRule, + }, + }, + { + feed: {}, + indicator: { + description: 'this should match auditbeat/hosts on ip', + first_seen: '2021-01-26T11:06:03.000Z', + ip: '45.115.45.3', + provider: 'other_provider', + type: 'ip', + }, + + matched: { + atomic: '45.115.45.3', + id: '978787', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'source.ip', + type: ENRICHMENT_TYPES.IndicatorMatchRule, + }, + }, + ]); + }); + + it('adds a single indicator that matched multiple fields', async () => { + const rule: ThreatMatchRuleCreateProps = { + description: 'Detecting root and admin users', + name: 'Query with a rule id', + severity: 'high', + index: ['auditbeat-*'], + type: 'threat_match', + risk_score: 55, + language: 'kuery', + rule_id: 'rule-1', + from: '1900-01-01T00:00:00.000Z', + query: 'NOT source.port:35326', // specify query to have signals more than treat indicators, but only 1 will match + threat_indicator_path: 'threat.indicator', + threat_query: 'threat.indicator.port: 57324 or threat.indicator.ip:45.115.45.3', // narrow our query to a single indicator + threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module + threat_mapping: [ + { + entries: [ + { + value: 'threat.indicator.port', + field: 'source.port', + type: 'mapping', + }, + ], + }, + { + entries: [ + { + value: 'threat.indicator.ip', + field: 'source.ip', + type: 'mapping', + }, + ], + }, + ], + threat_filters: [], + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).equal(1); + + const [threat] = previewAlerts.map((hit) => hit._source?.threat) as Array<{ + enrichments: unknown[]; + }>; + + assertContains(threat.enrichments, [ + { + feed: {}, + indicator: { + description: 'this should match auditbeat/hosts on both port and ip', + first_seen: '2021-01-26T11:06:03.000Z', + ip: '45.115.45.3', + port: 57324, + provider: 'geenensp', + type: 'url', + }, + matched: { + atomic: '45.115.45.3', + id: '978785', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'source.ip', + type: ENRICHMENT_TYPES.IndicatorMatchRule, + }, + }, + // We do not merge matched indicators during enrichment, so in + // certain circumstances a given indicator document could appear + // multiple times in an enriched alert (albeit with different + // threat.indicator.matched data). That's the case with the + // first and third indicators matched, here. + { + feed: {}, + indicator: { + description: 'this should match auditbeat/hosts on both port and ip', + first_seen: '2021-01-26T11:06:03.000Z', + ip: '45.115.45.3', + port: 57324, + provider: 'geenensp', + type: 'url', + }, + + matched: { + atomic: 57324, + id: '978785', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'source.port', + type: ENRICHMENT_TYPES.IndicatorMatchRule, + }, + }, + { + feed: {}, + indicator: { + description: 'this should match auditbeat/hosts on ip', + first_seen: '2021-01-26T11:06:03.000Z', + ip: '45.115.45.3', + provider: 'other_provider', + type: 'ip', + }, + matched: { + atomic: '45.115.45.3', + id: '978787', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'source.ip', + type: ENRICHMENT_TYPES.IndicatorMatchRule, + }, + }, + ]); + }); + + it('generates multiple signals with multiple matches', async () => { + const rule: ThreatMatchRuleCreateProps = { + description: 'Detecting root and admin users', + name: 'Query with a rule id', + severity: 'high', + index: ['auditbeat-*'], + type: 'threat_match', + risk_score: 55, + language: 'kuery', + threat_language: 'kuery', + rule_id: 'rule-1', + from: '1900-01-01T00:00:00.000Z', + query: '*:*', // narrow our query to a single record that matches two indicators + threat_indicator_path: 'threat.indicator', + threat_query: '*:*', + threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module + threat_mapping: [ + { + entries: [ + { + value: 'threat.indicator.port', + field: 'source.port', + type: 'mapping', + }, + { + value: 'threat.indicator.ip', + field: 'source.ip', + type: 'mapping', + }, + ], + }, + { + entries: [ + { + value: 'threat.indicator.domain', + field: 'destination.ip', + type: 'mapping', + }, + ], + }, + ], + threat_filters: [], + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).equal(2); + + const threats = previewAlerts.map((hit) => hit._source?.threat) as Array<{ + enrichments: unknown[]; + }>; + + assertContains(threats[0].enrichments, [ + { + feed: {}, + indicator: { + description: "domain should match the auditbeat hosts' data's source.ip", + domain: '159.89.119.67', + first_seen: '2021-01-26T11:09:04.000Z', + provider: 'geenensp', + type: 'url', + url: { + full: 'http://159.89.119.67:59600/bin.sh', + scheme: 'http', + }, + }, + matched: { + atomic: '159.89.119.67', + id: '978783', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'destination.ip', + type: ENRICHMENT_TYPES.IndicatorMatchRule, + }, + }, + { + feed: {}, + indicator: { + description: 'this should match auditbeat/hosts on both port and ip', + first_seen: '2021-01-26T11:06:03.000Z', + ip: '45.115.45.3', + port: 57324, + provider: 'geenensp', + type: 'url', + }, + matched: { + atomic: '45.115.45.3', + id: '978785', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'source.ip', + type: ENRICHMENT_TYPES.IndicatorMatchRule, + }, + }, + { + feed: {}, + indicator: { + description: 'this should match auditbeat/hosts on both port and ip', + first_seen: '2021-01-26T11:06:03.000Z', + ip: '45.115.45.3', + port: 57324, + provider: 'geenensp', + type: 'url', + }, + matched: { + atomic: 57324, + id: '978785', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'source.port', + type: ENRICHMENT_TYPES.IndicatorMatchRule, + }, + }, + ]); + + assertContains(threats[1].enrichments, [ + { + feed: {}, + indicator: { + description: "domain should match the auditbeat hosts' data's source.ip", + domain: '159.89.119.67', + first_seen: '2021-01-26T11:09:04.000Z', + provider: 'geenensp', + type: 'url', + url: { + full: 'http://159.89.119.67:59600/bin.sh', + scheme: 'http', + }, + }, + matched: { + atomic: '159.89.119.67', + id: '978783', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'destination.ip', + type: ENRICHMENT_TYPES.IndicatorMatchRule, + }, + }, + ]); + }); + }); + + describe('indicator enrichment: event-first search', () => { + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/filebeat/threat_intel'); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/filebeat/threat_intel'); + }); + + it('enriches signals with the single indicator that matched', async () => { + const rule: ThreatMatchRuleCreateProps = { + description: 'Detecting root and admin users', + name: 'Query with a rule id', + severity: 'high', + index: ['auditbeat-*'], + type: 'threat_match', + risk_score: 55, + language: 'kuery', + rule_id: 'rule-1', + from: '1900-01-01T00:00:00.000Z', + query: 'destination.ip:159.89.119.67', + threat_indicator_path: 'threat.indicator', + threat_query: 'threat.indicator.domain: *', // narrow things down to indicators with a domain + threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module + threat_mapping: [ + { + entries: [ + { + value: 'threat.indicator.domain', + field: 'destination.ip', + type: 'mapping', + }, + ], + }, + ], + threat_filters: [], + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).equal(2); + + const threats = previewAlerts.map((hit) => hit._source?.threat); + expect(threats).to.eql([ + { + enrichments: [ + { + feed: {}, + indicator: { + description: "domain should match the auditbeat hosts' data's source.ip", + domain: '159.89.119.67', + first_seen: '2021-01-26T11:09:04.000Z', + provider: 'geenensp', + url: { + full: 'http://159.89.119.67:59600/bin.sh', + scheme: 'http', + }, + type: 'url', + }, + matched: { + atomic: '159.89.119.67', + id: '978783', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'destination.ip', + type: ENRICHMENT_TYPES.IndicatorMatchRule, + }, + }, + ], + }, + { + enrichments: [ + { + feed: {}, + indicator: { + description: "domain should match the auditbeat hosts' data's source.ip", + domain: '159.89.119.67', + first_seen: '2021-01-26T11:09:04.000Z', + provider: 'geenensp', + url: { + full: 'http://159.89.119.67:59600/bin.sh', + scheme: 'http', + }, + type: 'url', + }, + matched: { + atomic: '159.89.119.67', + id: '978783', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'destination.ip', + type: ENRICHMENT_TYPES.IndicatorMatchRule, + }, + }, + ], + }, + ]); + }); + + it('enriches signals with multiple indicators if several matched', async () => { + const rule: ThreatMatchRuleCreateProps = { + description: 'Detecting root and admin users', + name: 'Query with a rule id', + severity: 'high', + index: ['auditbeat-*'], + type: 'threat_match', + risk_score: 55, + language: 'kuery', + rule_id: 'rule-1', + from: '1900-01-01T00:00:00.000Z', + query: 'source.port: 57324', // narrow our query to a single record that matches two indicators + threat_indicator_path: 'threat.indicator', + threat_query: 'threat.indicator.ip: *', + threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module + threat_mapping: [ + { + entries: [ + { + value: 'threat.indicator.ip', + field: 'source.ip', + type: 'mapping', + }, + ], + }, + ], + threat_filters: [], + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).equal(1); + + const [threat] = previewAlerts.map((hit) => hit._source?.threat) as Array<{ + enrichments: unknown[]; + }>; + + assertContains(threat.enrichments, [ + { + feed: {}, + indicator: { + description: 'this should match auditbeat/hosts on both port and ip', + first_seen: '2021-01-26T11:06:03.000Z', + ip: '45.115.45.3', + port: 57324, + provider: 'geenensp', + type: 'url', + }, + matched: { + atomic: '45.115.45.3', + id: '978785', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'source.ip', + type: ENRICHMENT_TYPES.IndicatorMatchRule, + }, + }, + { + feed: {}, + indicator: { + description: 'this should match auditbeat/hosts on ip', + first_seen: '2021-01-26T11:06:03.000Z', + ip: '45.115.45.3', + provider: 'other_provider', + type: 'ip', + }, + + matched: { + atomic: '45.115.45.3', + id: '978787', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'source.ip', + type: ENRICHMENT_TYPES.IndicatorMatchRule, + }, + }, + ]); + }); + + it('adds a single indicator that matched multiple fields', async () => { + const rule: ThreatMatchRuleCreateProps = { + description: 'Detecting root and admin users', + name: 'Query with a rule id', + severity: 'high', + index: ['auditbeat-*'], + type: 'threat_match', + risk_score: 55, + language: 'kuery', + rule_id: 'rule-1', + from: '1900-01-01T00:00:00.000Z', + query: 'source.port: 57324', // narrow our query to a single record that matches two indicators + threat_indicator_path: 'threat.indicator', + threat_query: 'threat.indicator.ip: *', + threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module + threat_mapping: [ + { + entries: [ + { + value: 'threat.indicator.port', + field: 'source.port', + type: 'mapping', + }, + ], + }, + { + entries: [ + { + value: 'threat.indicator.ip', + field: 'source.ip', + type: 'mapping', + }, + ], + }, + ], + threat_filters: [], + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).equal(1); + + const [threat] = previewAlerts.map((hit) => hit._source?.threat) as Array<{ + enrichments: unknown[]; + }>; + + assertContains(threat.enrichments, [ + { + feed: {}, + indicator: { + description: 'this should match auditbeat/hosts on both port and ip', + first_seen: '2021-01-26T11:06:03.000Z', + ip: '45.115.45.3', + port: 57324, + provider: 'geenensp', + type: 'url', + }, + matched: { + atomic: '45.115.45.3', + id: '978785', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'source.ip', + type: ENRICHMENT_TYPES.IndicatorMatchRule, + }, + }, + // We do not merge matched indicators during enrichment, so in + // certain circumstances a given indicator document could appear + // multiple times in an enriched alert (albeit with different + // threat.indicator.matched data). That's the case with the + // first and third indicators matched, here. + { + feed: {}, + indicator: { + description: 'this should match auditbeat/hosts on both port and ip', + first_seen: '2021-01-26T11:06:03.000Z', + ip: '45.115.45.3', + port: 57324, + provider: 'geenensp', + type: 'url', + }, + + matched: { + atomic: 57324, + id: '978785', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'source.port', + type: ENRICHMENT_TYPES.IndicatorMatchRule, + }, + }, + { + feed: {}, + indicator: { + description: 'this should match auditbeat/hosts on ip', + first_seen: '2021-01-26T11:06:03.000Z', + ip: '45.115.45.3', + provider: 'other_provider', + type: 'ip', + }, + matched: { + atomic: '45.115.45.3', + id: '978787', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'source.ip', + type: ENRICHMENT_TYPES.IndicatorMatchRule, + }, + }, + ]); + }); + + it('generates multiple signals with multiple matches', async () => { + const rule: ThreatMatchRuleCreateProps = { + description: 'Detecting root and admin users', + name: 'Query with a rule id', + severity: 'high', + index: ['auditbeat-*'], + type: 'threat_match', + risk_score: 55, + language: 'kuery', + threat_language: 'kuery', + rule_id: 'rule-1', + from: '1900-01-01T00:00:00.000Z', + query: '(source.port:57324 and source.ip:45.115.45.3) or destination.ip:159.89.119.67', // narrow our query to a single record that matches two indicators + threat_indicator_path: 'threat.indicator', + threat_query: '*:*', + threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module + threat_mapping: [ + { + entries: [ + { + value: 'threat.indicator.port', + field: 'source.port', + type: 'mapping', + }, + { + value: 'threat.indicator.ip', + field: 'source.ip', + type: 'mapping', + }, + ], + }, + { + entries: [ + { + value: 'threat.indicator.domain', + field: 'destination.ip', + type: 'mapping', + }, + ], + }, + ], + threat_filters: [], + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).equal(2); + + const threats = previewAlerts.map((hit) => hit._source?.threat) as Array<{ + enrichments: unknown[]; + }>; + + assertContains(threats[0].enrichments, [ + { + feed: {}, + indicator: { + description: "domain should match the auditbeat hosts' data's source.ip", + domain: '159.89.119.67', + first_seen: '2021-01-26T11:09:04.000Z', + provider: 'geenensp', + type: 'url', + url: { + full: 'http://159.89.119.67:59600/bin.sh', + scheme: 'http', + }, + }, + matched: { + atomic: '159.89.119.67', + id: '978783', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'destination.ip', + type: ENRICHMENT_TYPES.IndicatorMatchRule, + }, + }, + { + feed: {}, + indicator: { + description: 'this should match auditbeat/hosts on both port and ip', + first_seen: '2021-01-26T11:06:03.000Z', + ip: '45.115.45.3', + port: 57324, + provider: 'geenensp', + type: 'url', + }, + matched: { + atomic: '45.115.45.3', + id: '978785', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'source.ip', + type: ENRICHMENT_TYPES.IndicatorMatchRule, + }, + }, + { + feed: {}, + indicator: { + description: 'this should match auditbeat/hosts on both port and ip', + first_seen: '2021-01-26T11:06:03.000Z', + ip: '45.115.45.3', + port: 57324, + provider: 'geenensp', + type: 'url', + }, + matched: { + atomic: 57324, + id: '978785', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'source.port', + type: ENRICHMENT_TYPES.IndicatorMatchRule, + }, + }, + ]); + + assertContains(threats[1].enrichments, [ + { + feed: {}, + indicator: { + description: "domain should match the auditbeat hosts' data's source.ip", + domain: '159.89.119.67', + first_seen: '2021-01-26T11:09:04.000Z', + provider: 'geenensp', + type: 'url', + url: { + full: 'http://159.89.119.67:59600/bin.sh', + scheme: 'http', + }, + }, + matched: { + atomic: '159.89.119.67', + id: '978783', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'destination.ip', + type: ENRICHMENT_TYPES.IndicatorMatchRule, + }, + }, + ]); + }); + }); + + describe('alerts should be enriched', () => { + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/entity/host_risk'); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/entity/host_risk'); + }); + + it('should be enriched with host risk score', async () => { + const rule: ThreatMatchRuleCreateProps = { + description: 'Detecting root and admin users', + name: 'Query with a rule id', + severity: 'high', + index: ['auditbeat-*'], + type: 'threat_match', + risk_score: 55, + language: 'kuery', + rule_id: 'rule-1', + from: '1900-01-01T00:00:00.000Z', + query: '*:*', + threat_query: 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip + threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity + threat_mapping: [ + // We match host.name against host.name + { + entries: [ + { + field: 'host.name', + value: 'host.name', + type: 'mapping', + }, + ], + }, + ], + threat_filters: [], + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId, size: 100 }); + expect(previewAlerts.length).equal(88); + const fullSource = previewAlerts.find( + (signal) => + (signal._source?.[ALERT_ANCESTORS] as Ancestor[])[0].id === '7yJ-B2kBR346wHgnhlMn' + ); + const fullSignal = fullSource?._source; + if (!fullSignal) { + return expect(fullSignal).to.be.ok(); + } + + expect(fullSignal?.host?.risk?.calculated_level).to.eql('Critical'); + expect(fullSignal?.host?.risk?.calculated_score_norm).to.eql(70); + }); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/threshold.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/threshold.ts new file mode 100644 index 000000000000..e3294ae9a815 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/threshold.ts @@ -0,0 +1,385 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { + ALERT_REASON, + ALERT_RULE_UUID, + ALERT_WORKFLOW_STATUS, + EVENT_KIND, +} from '@kbn/rule-data-utils'; + +import { ThresholdRuleCreateProps } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; +import { Ancestor } from '@kbn/security-solution-plugin/server/lib/detection_engine/signals/types'; +import { + ALERT_ANCESTORS, + ALERT_DEPTH, + ALERT_ORIGINAL_TIME, + ALERT_THRESHOLD_RESULT, +} from '@kbn/security-solution-plugin/common/field_maps/field_names'; +import { + createRule, + getOpenSignals, + getPreviewAlerts, + getThresholdRuleForSignalTesting, + previewRule, +} from '../../utils'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + const es = getService('es'); + const log = getService('log'); + + describe('Threshold type rules', () => { + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); + }); + + // First test creates a real rule - remaining tests use preview API + it('generates 1 signal from Threshold rules when threshold is met', async () => { + const rule: ThresholdRuleCreateProps = { + ...getThresholdRuleForSignalTesting(['auditbeat-*']), + threshold: { + field: ['host.id'], + value: 700, + }, + }; + const createdRule = await createRule(supertest, log, rule); + const alerts = await getOpenSignals(supertest, log, es, createdRule); + expect(alerts.hits.hits.length).eql(1); + const fullSignal = alerts.hits.hits[0]._source; + if (!fullSignal) { + return expect(fullSignal).to.be.ok(); + } + const eventIds = (fullSignal?.[ALERT_ANCESTORS] as Ancestor[]).map((event) => event.id); + expect(fullSignal).eql({ + ...fullSignal, + 'host.id': '8cc95778cce5407c809480e8e32ad76b', + [EVENT_KIND]: 'signal', + [ALERT_ANCESTORS]: [ + { + depth: 0, + id: eventIds[0], + index: 'auditbeat-*', + type: 'event', + }, + ], + [ALERT_WORKFLOW_STATUS]: 'open', + [ALERT_REASON]: 'event created high alert Signal Testing Query.', + [ALERT_RULE_UUID]: fullSignal[ALERT_RULE_UUID], + [ALERT_ORIGINAL_TIME]: fullSignal[ALERT_ORIGINAL_TIME], + [ALERT_DEPTH]: 1, + [ALERT_THRESHOLD_RESULT]: { + terms: [ + { + field: 'host.id', + value: '8cc95778cce5407c809480e8e32ad76b', + }, + ], + count: 788, + from: '2019-02-19T07:12:05.332Z', + }, + }); + }); + + it('generates 2 signals from Threshold rules when threshold is met', async () => { + const rule: ThresholdRuleCreateProps = { + ...getThresholdRuleForSignalTesting(['auditbeat-*']), + threshold: { + field: 'host.id', + value: 100, + }, + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).eql(2); + }); + + it('applies the provided query before bucketing ', async () => { + const rule: ThresholdRuleCreateProps = { + ...getThresholdRuleForSignalTesting(['auditbeat-*']), + query: 'host.id:"2ab45fc1c41e4c84bbd02202a7e5761f"', + threshold: { + field: 'process.name', + value: 21, + }, + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).eql(1); + }); + + it('generates no signals from Threshold rules when threshold is met and cardinality is not met', async () => { + const rule: ThresholdRuleCreateProps = { + ...getThresholdRuleForSignalTesting(['auditbeat-*']), + threshold: { + field: 'host.id', + value: 100, + cardinality: [ + { + field: 'destination.ip', + value: 100, + }, + ], + }, + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).eql(0); + }); + + it('generates no signals from Threshold rules when cardinality is met and threshold is not met', async () => { + const rule: ThresholdRuleCreateProps = { + ...getThresholdRuleForSignalTesting(['auditbeat-*']), + threshold: { + field: 'host.id', + value: 1000, + cardinality: [ + { + field: 'destination.ip', + value: 5, + }, + ], + }, + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).eql(0); + }); + + it('generates signals from Threshold rules when threshold and cardinality are both met', async () => { + const rule: ThresholdRuleCreateProps = { + ...getThresholdRuleForSignalTesting(['auditbeat-*']), + threshold: { + field: 'host.id', + value: 100, + cardinality: [ + { + field: 'destination.ip', + value: 5, + }, + ], + }, + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).eql(1); + const fullSignal = previewAlerts[0]._source; + if (!fullSignal) { + return expect(fullSignal).to.be.ok(); + } + const eventIds = (fullSignal?.[ALERT_ANCESTORS] as Ancestor[]).map((event) => event.id); + expect(fullSignal).eql({ + ...fullSignal, + 'host.id': '8cc95778cce5407c809480e8e32ad76b', + [EVENT_KIND]: 'signal', + [ALERT_ANCESTORS]: [ + { + depth: 0, + id: eventIds[0], + index: 'auditbeat-*', + type: 'event', + }, + ], + [ALERT_WORKFLOW_STATUS]: 'open', + [ALERT_REASON]: `event created high alert Signal Testing Query.`, + [ALERT_RULE_UUID]: fullSignal[ALERT_RULE_UUID], + [ALERT_ORIGINAL_TIME]: fullSignal[ALERT_ORIGINAL_TIME], + [ALERT_DEPTH]: 1, + [ALERT_THRESHOLD_RESULT]: { + terms: [ + { + field: 'host.id', + value: '8cc95778cce5407c809480e8e32ad76b', + }, + ], + cardinality: [ + { + field: 'destination.ip', + value: 7, + }, + ], + count: 788, + from: '2019-02-19T07:12:05.332Z', + }, + }); + }); + + it('should not generate signals if only one field meets the threshold requirement', async () => { + const rule: ThresholdRuleCreateProps = { + ...getThresholdRuleForSignalTesting(['auditbeat-*']), + threshold: { + field: ['host.id', 'process.name'], + value: 22, + }, + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).eql(0); + }); + + it('generates signals from Threshold rules when bucketing by multiple fields', async () => { + const rule: ThresholdRuleCreateProps = { + ...getThresholdRuleForSignalTesting(['auditbeat-*']), + threshold: { + field: ['host.id', 'process.name', 'event.module'], + value: 21, + }, + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).eql(1); + const fullSignal = previewAlerts[0]._source; + if (!fullSignal) { + return expect(fullSignal).to.be.ok(); + } + const eventIds = (fullSignal[ALERT_ANCESTORS] as Ancestor[]).map((event) => event.id); + expect(fullSignal).eql({ + ...fullSignal, + 'event.module': 'system', + 'host.id': '2ab45fc1c41e4c84bbd02202a7e5761f', + 'process.name': 'sshd', + [EVENT_KIND]: 'signal', + [ALERT_ANCESTORS]: [ + { + depth: 0, + id: eventIds[0], + index: 'auditbeat-*', + type: 'event', + }, + ], + [ALERT_WORKFLOW_STATUS]: 'open', + [ALERT_REASON]: `event with process sshd, created high alert Signal Testing Query.`, + [ALERT_RULE_UUID]: fullSignal[ALERT_RULE_UUID], + [ALERT_ORIGINAL_TIME]: fullSignal[ALERT_ORIGINAL_TIME], + [ALERT_DEPTH]: 1, + [ALERT_THRESHOLD_RESULT]: { + terms: [ + { + field: 'host.id', + value: '2ab45fc1c41e4c84bbd02202a7e5761f', + }, + { + field: 'process.name', + value: 'sshd', + }, + { + field: 'event.module', + value: 'system', + }, + ], + count: 21, + from: '2019-02-19T20:22:03.561Z', + }, + }); + }); + + describe('Timestamp override and fallback', async () => { + before(async () => { + await esArchiver.load( + 'x-pack/test/functional/es_archives/security_solution/timestamp_fallback' + ); + }); + + after(async () => { + await esArchiver.unload( + 'x-pack/test/functional/es_archives/security_solution/timestamp_fallback' + ); + }); + + it('applies timestamp override when using single field', async () => { + const rule: ThresholdRuleCreateProps = { + ...getThresholdRuleForSignalTesting(['timestamp-fallback-test']), + threshold: { + field: 'host.name', + value: 1, + }, + timestamp_override: 'event.ingested', + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).eql(4); + + for (const hit of previewAlerts) { + const originalTime = hit._source?.[ALERT_ORIGINAL_TIME]; + const hostName = hit._source?.['host.name']; + if (hostName === 'host-1') { + expect(originalTime).eql('2020-12-16T15:15:18.570Z'); + } else if (hostName === 'host-2') { + expect(originalTime).eql('2020-12-16T15:16:18.570Z'); + } else if (hostName === 'host-3') { + expect(originalTime).eql('2020-12-16T16:15:18.570Z'); + } else { + expect(originalTime).eql('2020-12-16T16:16:18.570Z'); + } + } + }); + + it('applies timestamp override when using multiple fields', async () => { + const rule: ThresholdRuleCreateProps = { + ...getThresholdRuleForSignalTesting(['timestamp-fallback-test']), + threshold: { + field: ['host.name', 'source.ip'], + value: 1, + }, + timestamp_override: 'event.ingested', + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).eql(4); + + for (const hit of previewAlerts) { + const originalTime = hit._source?.[ALERT_ORIGINAL_TIME]; + const hostName = hit._source?.['host.name']; + if (hostName === 'host-1') { + expect(originalTime).eql('2020-12-16T15:15:18.570Z'); + } else if (hostName === 'host-2') { + expect(originalTime).eql('2020-12-16T15:16:18.570Z'); + } else if (hostName === 'host-3') { + expect(originalTime).eql('2020-12-16T16:15:18.570Z'); + } else { + expect(originalTime).eql('2020-12-16T16:16:18.570Z'); + } + } + }); + }); + + describe('with host risk index', async () => { + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/entity/host_risk'); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/entity/host_risk'); + }); + + it('should be enriched with host risk score', async () => { + const rule: ThresholdRuleCreateProps = { + ...getThresholdRuleForSignalTesting(['auditbeat-*']), + threshold: { + field: 'host.name', + value: 100, + }, + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + + expect(previewAlerts[0]?._source?.host?.risk?.calculated_level).to.eql('Low'); + expect(previewAlerts[0]?._source?.host?.risk?.calculated_score_norm).to.eql(20); + expect(previewAlerts[1]?._source?.host?.risk?.calculated_level).to.eql('Critical'); + expect(previewAlerts[1]?._source?.host?.risk?.calculated_score_norm).to.eql(96); + }); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/utils/get_preview_alerts.ts b/x-pack/test/detection_engine_api_integration/utils/get_preview_alerts.ts new file mode 100644 index 000000000000..48682e6b1e8b --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/utils/get_preview_alerts.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Client } from '@elastic/elasticsearch'; +import { ALERT_RULE_UUID } from '@kbn/rule-data-utils'; +import { DetectionAlert } from '@kbn/security-solution-plugin/common/detection_engine/schemas/alerts'; +import { RiskEnrichmentFields } from '@kbn/security-solution-plugin/server/lib/detection_engine/signals/enrichments/types'; +import { refreshIndex } from './refresh_index'; + +/** + * Refresh an index, making changes available to search. + * Useful for tests where we want to ensure that a rule does NOT create alerts, e.g. testing exceptions. + * @param es The ElasticSearch handle + */ +export const getPreviewAlerts = async ({ + es, + previewId, + size, +}: { + es: Client; + previewId: string; + size?: number; +}) => { + const index = '.preview.alerts-security.alerts-*'; + await refreshIndex(es, index); + const query = { + bool: { + filter: { + term: { + [ALERT_RULE_UUID]: previewId, + }, + }, + }, + }; + const result = await es.search({ + index, + size, + query, + }); + return result.hits.hits; +}; diff --git a/x-pack/test/detection_engine_api_integration/utils/index.ts b/x-pack/test/detection_engine_api_integration/utils/index.ts index 866136f172f1..b686589addc0 100644 --- a/x-pack/test/detection_engine_api_integration/utils/index.ts +++ b/x-pack/test/detection_engine_api_integration/utils/index.ts @@ -41,6 +41,7 @@ export * from './get_legacy_action_so'; export * from './get_legacy_actions_so_by_id'; export * from './get_open_signals'; export * from './get_prepackaged_rule_status'; +export * from './get_preview_alerts'; export * from './get_query_all_signals'; export * from './get_query_signal_ids'; export * from './get_query_signals_ids'; @@ -79,6 +80,9 @@ export * from './get_slack_action'; export * from './get_web_hook_action'; export * from './index_event_log_execution_events'; export * from './install_prepackaged_rules'; +export * from './machine_learning_setup'; +export * from './preview_rule_with_exception_entries'; +export * from './preview_rule'; export * from './refresh_index'; export * from './remove_time_fields_from_telemetry_stats'; export * from './remove_server_generated_properties'; diff --git a/x-pack/test/detection_engine_api_integration/utils/machine_learning_setup.ts b/x-pack/test/detection_engine_api_integration/utils/machine_learning_setup.ts new file mode 100644 index 000000000000..b376df9407c4 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/utils/machine_learning_setup.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type SuperTest from 'supertest'; + +export const executeSetupModuleRequest = async ({ + module, + rspCode, + supertest, +}: { + module: string; + rspCode: number; + supertest: SuperTest.SuperTest; +}) => { + const { body } = await supertest + .post(`/api/ml/modules/setup/${module}`) + .set('kbn-xsrf', 'true') + .send({ + prefix: '', + groups: ['auditbeat'], + indexPatternName: 'auditbeat-*', + startDatafeed: false, + useDedicatedIndex: true, + applyToAllSpaces: true, + }) + .expect(rspCode); + + return body; +}; + +export const forceStartDatafeeds = async ({ + jobId, + rspCode, + supertest, +}: { + jobId: string; + rspCode: number; + supertest: SuperTest.SuperTest; +}) => { + const { body } = await supertest + .post(`/api/ml/jobs/force_start_datafeeds`) + .set('kbn-xsrf', 'true') + .send({ + datafeedIds: [`datafeed-${jobId}`], + start: new Date().getUTCMilliseconds(), + }) + .expect(rspCode); + + return body; +}; diff --git a/x-pack/test/detection_engine_api_integration/utils/preview_rule.ts b/x-pack/test/detection_engine_api_integration/utils/preview_rule.ts new file mode 100644 index 000000000000..1360209d9a17 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/utils/preview_rule.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type SuperTest from 'supertest'; +import type { + RuleCreateProps, + PreviewRulesSchema, + RulePreviewLogs, +} from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; + +import { DETECTION_ENGINE_RULES_PREVIEW } from '@kbn/security-solution-plugin/common/constants'; + +/** + * Runs the preview for a rule. Any generated alerts will be written to .preview.alerts. + * This is much faster than actually running the rule, and can also quickly simulate multiple + * consecutive rule runs, e.g. for ensuring that rule state is properly handled across runs. + * @param supertest The supertest deps + * @param rule The rule to create + */ +export const previewRule = async ({ + supertest, + rule, + invocationCount = 1, + timeframeEnd = new Date(), +}: { + supertest: SuperTest.SuperTest; + rule: RuleCreateProps; + invocationCount?: number; + timeframeEnd?: Date; +}): Promise<{ + previewId: string; + logs: RulePreviewLogs[]; + isAborted: boolean; +}> => { + const previewRequest: PreviewRulesSchema = { + ...rule, + invocationCount, + timeframeEnd: timeframeEnd.toISOString(), + }; + const response = await supertest + .post(DETECTION_ENGINE_RULES_PREVIEW) + .set('kbn-xsrf', 'true') + .send(previewRequest) + .expect(200); + return response.body; +}; diff --git a/x-pack/test/detection_engine_api_integration/utils/preview_rule_with_exception_entries.ts b/x-pack/test/detection_engine_api_integration/utils/preview_rule_with_exception_entries.ts new file mode 100644 index 000000000000..efd5c71ac704 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/utils/preview_rule_with_exception_entries.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ToolingLog } from '@kbn/tooling-log'; +import type SuperTest from 'supertest'; +import type { NonEmptyEntriesArray, OsTypeArray } from '@kbn/securitysolution-io-ts-list-types'; +import type { RuleCreateProps } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; + +import { createContainerWithEntries } from './create_container_with_entries'; +import { createContainerWithEndpointEntries } from './create_container_with_endpoint_entries'; +import { previewRule } from './preview_rule'; + +/** + * Convenience testing function where you can pass in just the entries and you will + * get a rule created with the entries added to an exception list and exception list item + * all auto-created at once. + * @param supertest super test agent + * @param rule The rule to create and attach an exception list to + * @param entries The entries to create the rule and exception list from + * @param endpointEntries The endpoint entries to create the rule and exception list from + * @param osTypes The os types to optionally add or not to add to the container + */ +export const previewRuleWithExceptionEntries = async ({ + supertest, + log, + rule, + entries, + endpointEntries, + invocationCount, + timeframeEnd, +}: { + supertest: SuperTest.SuperTest; + log: ToolingLog; + rule: RuleCreateProps; + entries: NonEmptyEntriesArray[]; + endpointEntries?: Array<{ + entries: NonEmptyEntriesArray; + osTypes: OsTypeArray | undefined; + }>; + invocationCount?: number; + timeframeEnd?: Date; +}) => { + const maybeExceptionList = await createContainerWithEntries(supertest, log, entries); + const maybeEndpointList = await createContainerWithEndpointEntries( + supertest, + log, + endpointEntries ?? [] + ); + + return previewRule({ + supertest, + rule: { + ...rule, + exceptions_list: [...maybeExceptionList, ...maybeEndpointList], + }, + invocationCount, + timeframeEnd, + }); +}; diff --git a/x-pack/test/fleet_api_integration/apis/agent_policy/agent_policy.ts b/x-pack/test/fleet_api_integration/apis/agent_policy/agent_policy.ts index 277bafb99876..b8703cd5a138 100644 --- a/x-pack/test/fleet_api_integration/apis/agent_policy/agent_policy.ts +++ b/x-pack/test/fleet_api_integration/apis/agent_policy/agent_policy.ts @@ -348,7 +348,7 @@ export default function (providerContext: FtrProviderContext) { return 0; } - const policyId = 'package-policy-test-1'; + const policyId = 'package-policy-test-'; packagePoliciesToDeleteIds.push(policyId); const getPkRes = await supertest .get(`/api/fleet/epm/packages/system`) @@ -438,6 +438,92 @@ export default function (providerContext: FtrProviderContext) { expect(await getSystemPackagePolicyCopyVersion(copy3Id)).to.be(3); }); + it('should work with package policy with space in name', async () => { + const policyId = 'package-policy-test-1'; + packagePoliciesToDeleteIds.push(policyId); + const getPkRes = await supertest + .get(`/api/fleet/epm/packages/system`) + .set('kbn-xsrf', 'xxxx') + .expect(200); + systemPkgVersion = getPkRes.body.item.version; + // we must first force install the system package to override package verification error on policy create + const installPromise = supertest + .post(`/api/fleet/epm/packages/system-${systemPkgVersion}`) + .set('kbn-xsrf', 'xxxx') + .send({ force: true }) + .expect(200); + + await Promise.all([ + installPromise, + kibanaServer.savedObjects.create({ + id: policyId, + type: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + overwrite: true, + attributes: { + name: `system-1`, + package: { + name: 'system', + }, + }, + }), + ]); + + const { + body: { + item: { id: originalPolicyId }, + }, + } = await supertest + .post(`/api/fleet/agent_policies`) + .set('kbn-xsrf', 'xxxx') + .query({ + sys_monitoring: false, + }) + .send({ + name: 'original policy with package policy with space in name', + namespace: 'default', + }) + .expect(200); + + await supertest + .post(`/api/fleet/package_policies`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: 'Filetest with space in name', + description: '', + namespace: 'default', + policy_id: originalPolicyId, + enabled: true, + inputs: [], + package: { + name: 'filetest', + title: 'For File Tests', + version: '0.1.0', + }, + }) + .expect(200); + + const { + body: { + item: { id: copy1Id }, + }, + } = await supertest + .post(`/api/fleet/agent_policies/${originalPolicyId}/copy`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: 'copy 123', + description: 'Test', + }) + .expect(200); + + const { + body: { + item: { package_policies: packagePolicies }, + }, + } = await supertest.get(`/api/fleet/agent_policies/${copy1Id}`).expect(200); + + expect(packagePolicies[0].name).to.eql('Filetest with space in name (copy)'); + }); + it('should return a 404 with invalid source policy', async () => { await supertest .post(`/api/fleet/agent_policies/INVALID_POLICY_ID/copy`) diff --git a/x-pack/test/functional/apps/lens/group1/index.ts b/x-pack/test/functional/apps/lens/group1/index.ts index aa2b078a50a6..47f08a59e734 100644 --- a/x-pack/test/functional/apps/lens/group1/index.ts +++ b/x-pack/test/functional/apps/lens/group1/index.ts @@ -79,6 +79,7 @@ export default ({ getService, loadTestFile, getPageObjects }: FtrProviderContext loadTestFile(require.resolve('./table_dashboard')); loadTestFile(require.resolve('./table')); loadTestFile(require.resolve('./text_based_languages')); + loadTestFile(require.resolve('./layer_actions')); } }); }; diff --git a/x-pack/test/functional/apps/lens/group1/layer_actions.ts b/x-pack/test/functional/apps/lens/group1/layer_actions.ts new file mode 100644 index 000000000000..be22e4ad6251 --- /dev/null +++ b/x-pack/test/functional/apps/lens/group1/layer_actions.ts @@ -0,0 +1,91 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const PageObjects = getPageObjects(['visualize', 'lens', 'common', 'header']); + const find = getService('find'); + const testSubjects = getService('testSubjects'); + + describe('lens layer actions tests', () => { + it('should allow creation of lens xy chart', async () => { + await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.clickVisType('lens'); + await PageObjects.lens.goToTimeRange(); + + await PageObjects.lens.openLayerContextMenu(); + + // should be 3 actions available + expect( + (await find.allByCssSelector('[data-test-subj=lnsLayerActionsMenu] button')).length + ).to.eql(3); + }); + + it('should open layer settings for a data layer', async () => { + // click on open layer settings + await testSubjects.click('lnsLayerSettings'); + // random sampling available + await testSubjects.existOrFail('lns-indexPattern-random-sampling-row'); + // tweak the value + await PageObjects.lens.dragRangeInput('lns-indexPattern-random-sampling', 2, 'left'); + + expect(await PageObjects.lens.getRangeInputValue('lns-indexPattern-random-sampling')).to.eql( + 2 // 0.01 + ); + await testSubjects.click('lns-indexPattern-dimensionContainerBack'); + }); + + it('should add an annotation layer and settings shoud not be available', async () => { + // configure a date histogram + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', + operation: 'date_histogram', + field: '@timestamp', + }); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'average', + field: 'bytes', + }); + // add annotation layer + await testSubjects.click('lnsLayerAddButton'); + await testSubjects.click(`lnsLayerAddButton-annotations`); + await PageObjects.lens.openLayerContextMenu(1); + // layer settings not available + await testSubjects.missingOrFail('lnsLayerSettings'); + }); + + it('should switch to pie chart and have layer settings available', async () => { + await PageObjects.lens.switchToVisualization('pie'); + await PageObjects.lens.openLayerContextMenu(); + // layer settings still available + // open the panel + await testSubjects.click('lnsLayerSettings'); + // check the sampling value + expect(await PageObjects.lens.getRangeInputValue('lns-indexPattern-random-sampling')).to.eql( + 2 // 0.01 + ); + await testSubjects.click('lns-indexPattern-dimensionContainerBack'); + }); + + it('should switch to table and still have layer settings', async () => { + await PageObjects.lens.switchToVisualization('lnsDatatable'); + await PageObjects.lens.openLayerContextMenu(); + // layer settings still available + // open the panel + await testSubjects.click('lnsLayerSettings'); + // check the sampling value + expect(await PageObjects.lens.getRangeInputValue('lns-indexPattern-random-sampling')).to.eql( + 2 // 0.01 + ); + await testSubjects.click('lns-indexPattern-dimensionContainerBack'); + }); + }); +} diff --git a/x-pack/test/functional/apps/lens/open_in_lens/tsvb/index.ts b/x-pack/test/functional/apps/lens/open_in_lens/tsvb/index.ts index c0b5197983aa..8428d145c60e 100644 --- a/x-pack/test/functional/apps/lens/open_in_lens/tsvb/index.ts +++ b/x-pack/test/functional/apps/lens/open_in_lens/tsvb/index.ts @@ -14,5 +14,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./timeseries')); loadTestFile(require.resolve('./dashboard')); loadTestFile(require.resolve('./top_n')); + loadTestFile(require.resolve('./table')); }); } diff --git a/x-pack/test/functional/apps/lens/open_in_lens/tsvb/table.ts b/x-pack/test/functional/apps/lens/open_in_lens/tsvb/table.ts new file mode 100644 index 000000000000..e3d52852cd61 --- /dev/null +++ b/x-pack/test/functional/apps/lens/open_in_lens/tsvb/table.ts @@ -0,0 +1,218 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const { visualize, visualBuilder, lens, header } = getPageObjects([ + 'visualBuilder', + 'visualize', + 'header', + 'lens', + ]); + + const testSubjects = getService('testSubjects'); + const retry = getService('retry'); + + describe('Table', function describeIndexTests() { + before(async () => { + await visualize.initTests(); + }); + + beforeEach(async () => { + await visualBuilder.resetPage(); + await visualBuilder.clickTable(); + await header.waitUntilLoadingHasFinished(); + await visualBuilder.checkTableTabIsPresent(); + await visualBuilder.selectGroupByField('machine.os.raw'); + }); + + it('should not allow converting of not valid panel', async () => { + await visualBuilder.selectAggType('Max'); + await header.waitUntilLoadingHasFinished(); + expect(await visualize.hasNavigateToLensButton()).to.be(false); + }); + + it('should not allow converting of unsupported aggregations', async () => { + await visualBuilder.selectAggType('Sum of Squares'); + await visualBuilder.setFieldForAggregation('machine.ram'); + + await header.waitUntilLoadingHasFinished(); + expect(await visualize.hasNavigateToLensButton()).to.be(false); + }); + + it('should not allow converting sibling pipeline aggregations', async () => { + await visualBuilder.createNewAgg(); + + await visualBuilder.selectAggType('Overall Average', 1); + await visualBuilder.setFieldForAggregation('Count', 1); + await header.waitUntilLoadingHasFinished(); + expect(await visualize.hasNavigateToLensButton()).to.be(false); + }); + + it('should not allow converting parent pipeline aggregations', async () => { + await visualBuilder.clickPanelOptions('table'); + await visualBuilder.setMetricsDataTimerangeMode('Last value'); + await visualBuilder.clickDataTab('table'); + await visualBuilder.createNewAgg(); + + await visualBuilder.selectAggType('Cumulative Sum', 1); + await visualBuilder.setFieldForAggregation('Count', 1); + await header.waitUntilLoadingHasFinished(); + expect(await visualize.hasNavigateToLensButton()).to.be(false); + }); + + it('should not allow converting not valid aggregation function', async () => { + await visualBuilder.clickSeriesOption(); + await visualBuilder.setFieldForAggregateBy('clientip'); + await visualBuilder.setFunctionForAggregateFunction('Cumulative Sum'); + await header.waitUntilLoadingHasFinished(); + expect(await visualize.hasNavigateToLensButton()).to.be(false); + }); + + it('should not allow converting series with different aggregation fucntion or aggregation by', async () => { + await visualBuilder.createNewAggSeries(); + await visualBuilder.selectAggType('Static Value', 1); + await visualBuilder.setStaticValue(10); + await visualBuilder.clickSeriesOption(); + await visualBuilder.setFieldForAggregateBy('bytes'); + await visualBuilder.setFunctionForAggregateFunction('Sum'); + await visualBuilder.clickSeriesOption(1); + await visualBuilder.setFieldForAggregateBy('bytes'); + await visualBuilder.setFunctionForAggregateFunction('Min'); + await header.waitUntilLoadingHasFinished(); + expect(await visualize.hasNavigateToLensButton()).to.be(false); + }); + + it('should allow converting a count aggregation', async () => { + expect(await visualize.hasNavigateToLensButton()).to.be(true); + }); + + it('should convert last value mode to reduced time range', async () => { + await visualBuilder.clickPanelOptions('table'); + await visualBuilder.setMetricsDataTimerangeMode('Last value'); + await visualBuilder.setIntervalValue('1m'); + await visualBuilder.clickDataTab('table'); + await header.waitUntilLoadingHasFinished(); + + await visualize.navigateToLensFromAnotherVisulization(); + await lens.waitForVisualization('lnsDataTable'); + await lens.openDimensionEditor('lnsDatatable_metrics > lns-dimensionTrigger'); + await testSubjects.click('indexPattern-advanced-accordion'); + const reducedTimeRange = await testSubjects.find('indexPattern-dimension-reducedTimeRange'); + expect(await reducedTimeRange.getVisibleText()).to.be('1 minute (1m)'); + await retry.try(async () => { + const layerCount = await lens.getLayerCount(); + expect(layerCount).to.be(1); + const metricDimensionText = await lens.getDimensionTriggerText('lnsDatatable_metrics', 0); + expect(metricDimensionText).to.be('Count of records last 1m'); + }); + }); + + it('should convert static value to the metric dimension', async () => { + await visualBuilder.createNewAggSeries(); + await visualBuilder.selectAggType('Static Value', 1); + await visualBuilder.setStaticValue(10); + + await header.waitUntilLoadingHasFinished(); + + await visualize.navigateToLensFromAnotherVisulization(); + await lens.waitForVisualization('lnsDataTable'); + await retry.try(async () => { + const layerCount = await lens.getLayerCount(); + expect(layerCount).to.be(1); + const metricDimensionText1 = await lens.getDimensionTriggerText('lnsDatatable_metrics', 0); + const metricDimensionText2 = await lens.getDimensionTriggerText('lnsDatatable_metrics', 1); + expect(metricDimensionText1).to.be('Count of records'); + expect(metricDimensionText2).to.be('10'); + }); + }); + + it('should convert aggregate by to split row dimension', async () => { + await visualBuilder.clickSeriesOption(); + await visualBuilder.setFieldForAggregateBy('clientip'); + await visualBuilder.setFunctionForAggregateFunction('Sum'); + await header.waitUntilLoadingHasFinished(); + + await visualize.navigateToLensFromAnotherVisulization(); + await lens.waitForVisualization('lnsDataTable'); + await retry.try(async () => { + const layerCount = await lens.getLayerCount(); + expect(layerCount).to.be(1); + const splitRowsText1 = await lens.getDimensionTriggerText('lnsDatatable_rows', 0); + const splitRowsText2 = await lens.getDimensionTriggerText('lnsDatatable_rows', 1); + expect(splitRowsText1).to.be('Top 10 values of machine.os.raw'); + expect(splitRowsText2).to.be('Top 10 values of clientip'); + }); + + await lens.openDimensionEditor('lnsDatatable_rows > lns-dimensionTrigger', 0, 1); + const collapseBy = await testSubjects.find('indexPattern-collapse-by'); + expect(await collapseBy.getAttribute('value')).to.be('sum'); + }); + + it('should convert group by field with custom label', async () => { + await visualBuilder.setColumnLabelValue('test'); + await header.waitUntilLoadingHasFinished(); + + await visualize.navigateToLensFromAnotherVisulization(); + await lens.waitForVisualization('lnsDataTable'); + await retry.try(async () => { + const layerCount = await lens.getLayerCount(); + expect(layerCount).to.be(1); + const splitRowsText = await lens.getDimensionTriggerText('lnsDatatable_rows', 0); + expect(splitRowsText).to.be('test'); + }); + }); + + it('should convert color ranges', async () => { + await visualBuilder.clickSeriesOption(); + + await visualBuilder.setColorRuleOperator('>= greater than or equal'); + await visualBuilder.setColorRuleValue(10); + await visualBuilder.setColorPickerValue('#54B399'); + + await visualBuilder.createColorRule(1); + + await visualBuilder.setColorRuleOperator('>= greater than or equal'); + await visualBuilder.setColorRuleValue(100, 1); + await visualBuilder.setColorPickerValue('#54A000', 1); + + await header.waitUntilLoadingHasFinished(); + await visualize.navigateToLensFromAnotherVisulization(); + + await lens.waitForVisualization('lnsDataTable'); + await retry.try(async () => { + const closePalettePanels = await testSubjects.findAll( + 'lns-indexPattern-PalettePanelContainerBack' + ); + if (closePalettePanels.length) { + await lens.closePalettePanel(); + await lens.closeDimensionEditor(); + } + + await lens.openDimensionEditor('lnsDatatable_metrics > lns-dimensionTrigger'); + + await lens.openPalettePanel('lnsDatatable'); + const colorStops = await lens.getPaletteColorStops(); + + expect(colorStops).to.eql([ + { stop: '10', color: 'rgba(84, 179, 153, 1)' }, + { stop: '100', color: 'rgba(84, 160, 0, 1)' }, + { stop: '', color: undefined }, + ]); + }); + }); + }); +} diff --git a/x-pack/test/functional/es_archives/security_solution/alerts/8.0.0/data.json.gz b/x-pack/test/functional/es_archives/security_solution/alerts/8.0.0/data.json.gz index 26952621f10e..6a7559434f70 100644 Binary files a/x-pack/test/functional/es_archives/security_solution/alerts/8.0.0/data.json.gz and b/x-pack/test/functional/es_archives/security_solution/alerts/8.0.0/data.json.gz differ diff --git a/x-pack/test/functional/es_archives/security_solution/alerts/8.0.0/mappings.json.gz b/x-pack/test/functional/es_archives/security_solution/alerts/8.0.0/mappings.json.gz index 3a26e140e7ea..5a7347bcb63d 100644 Binary files a/x-pack/test/functional/es_archives/security_solution/alerts/8.0.0/mappings.json.gz and b/x-pack/test/functional/es_archives/security_solution/alerts/8.0.0/mappings.json.gz differ diff --git a/x-pack/test/functional/es_archives/security_solution/alerts/8.1.0/data.json.gz b/x-pack/test/functional/es_archives/security_solution/alerts/8.1.0/data.json.gz index 1bd5cf631c28..50d664aa24ce 100644 Binary files a/x-pack/test/functional/es_archives/security_solution/alerts/8.1.0/data.json.gz and b/x-pack/test/functional/es_archives/security_solution/alerts/8.1.0/data.json.gz differ diff --git a/x-pack/test/functional/es_archives/security_solution/alerts/8.1.0/mappings.json.gz b/x-pack/test/functional/es_archives/security_solution/alerts/8.1.0/mappings.json.gz index 05640f1e93e3..27ba06da3ad3 100644 Binary files a/x-pack/test/functional/es_archives/security_solution/alerts/8.1.0/mappings.json.gz and b/x-pack/test/functional/es_archives/security_solution/alerts/8.1.0/mappings.json.gz differ diff --git a/x-pack/test/functional/page_objects/lens_page.ts b/x-pack/test/functional/page_objects/lens_page.ts index a336a0da3e4b..8dd95aa10792 100644 --- a/x-pack/test/functional/page_objects/lens_page.ts +++ b/x-pack/test/functional/page_objects/lens_page.ts @@ -600,6 +600,19 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont await this.waitForVisualization(); }, + async dragRangeInput(testId: string, steps: number = 1, direction: 'left' | 'right' = 'right') { + const inputEl = await testSubjects.find(testId); + await inputEl.focus(); + const browserKey = direction === 'left' ? browser.keys.LEFT : browser.keys.RIGHT; + while (steps--) { + await browser.pressKeys(browserKey); + } + }, + + async getRangeInputValue(testId: string) { + return (await testSubjects.find(testId)).getAttribute('value'); + }, + async isTopLevelAggregation() { return await testSubjects.isEuiSwitchChecked('indexPattern-nesting-switch'); }, @@ -1117,6 +1130,10 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont ); }, + async openLayerContextMenu(index: number = 0) { + await testSubjects.click(`lnsLayerSplitButton--${index}`); + }, + async toggleColumnVisibility(dimension: string, no = 1) { await this.openDimensionEditor(dimension); const id = 'lns-table-column-hidden'; diff --git a/x-pack/test/plugin_api_integration/test_suites/task_manager/task_management.ts b/x-pack/test/plugin_api_integration/test_suites/task_manager/task_management.ts index 9eb23f62e06f..c4b5a2831183 100644 --- a/x-pack/test/plugin_api_integration/test_suites/task_manager/task_management.ts +++ b/x-pack/test/plugin_api_integration/test_suites/task_manager/task_management.ts @@ -54,7 +54,8 @@ export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const testHistoryIndex = '.kibana_task_manager_test_result'; - describe('scheduling and running tasks', () => { + // FLAKY: https://github.com/elastic/kibana/issues/141055 + describe.skip('scheduling and running tasks', () => { beforeEach(async () => { // clean up before each test return await supertest.delete('/api/sample_tasks').set('kbn-xsrf', 'xxx').expect(200); diff --git a/x-pack/test/rule_registry/security_and_spaces/tests/basic/search_strategy.ts b/x-pack/test/rule_registry/security_and_spaces/tests/basic/search_strategy.ts index 2785fece6d90..b5c72abac632 100644 --- a/x-pack/test/rule_registry/security_and_spaces/tests/basic/search_strategy.ts +++ b/x-pack/test/rule_registry/security_and_spaces/tests/basic/search_strategy.ts @@ -19,7 +19,6 @@ import { waitForSignalsToBePresent, waitForRuleSuccessOrStatus, } from '../../../../detection_engine_api_integration/utils'; -import { ID } from '../../../../detection_engine_api_integration/security_and_spaces/group1/generating_signals'; import { obsOnlySpacesAllEsRead, obsOnlySpacesAll, @@ -31,6 +30,8 @@ type RuleRegistrySearchResponseWithErrors = RuleRegistrySearchResponse & { message: string; }; +const ID = 'BhbXBmkBR346wHgn4PeZ'; + // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); diff --git a/x-pack/test/stack_functional_integration/apps/filebeat/filebeat.ts b/x-pack/test/stack_functional_integration/apps/filebeat/filebeat.ts index c8ddb8aabec3..5539125d770a 100644 --- a/x-pack/test/stack_functional_integration/apps/filebeat/filebeat.ts +++ b/x-pack/test/stack_functional_integration/apps/filebeat/filebeat.ts @@ -18,7 +18,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.selectIndexPattern('filebeat-*'); await PageObjects.timePicker.setCommonlyUsedTime('Last_1 year'); await retry.try(async () => { - const hitCount = parseInt(await PageObjects.discover.getHitCount(), 10); + const hitCount = await PageObjects.discover.getHitCountInt(); expect(hitCount).to.be.greaterThan(0); }); }); diff --git a/x-pack/test/stack_functional_integration/apps/metricbeat/_metricbeat.ts b/x-pack/test/stack_functional_integration/apps/metricbeat/_metricbeat.ts index f6a8aa787530..50b254a65559 100644 --- a/x-pack/test/stack_functional_integration/apps/metricbeat/_metricbeat.ts +++ b/x-pack/test/stack_functional_integration/apps/metricbeat/_metricbeat.ts @@ -28,7 +28,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.selectIndexPattern('metricbeat-*'); await PageObjects.timePicker.setCommonlyUsedTime('Last_1 year'); await retry.try(async function () { - const hitCount = parseInt(await PageObjects.discover.getHitCount(), 10); + const hitCount = await PageObjects.discover.getHitCountInt(); expect(hitCount).to.be.greaterThan(0); }); }); diff --git a/x-pack/test/stack_functional_integration/apps/packetbeat/_packetbeat.ts b/x-pack/test/stack_functional_integration/apps/packetbeat/_packetbeat.ts index e4bf8288a209..367f7a34f700 100644 --- a/x-pack/test/stack_functional_integration/apps/packetbeat/_packetbeat.ts +++ b/x-pack/test/stack_functional_integration/apps/packetbeat/_packetbeat.ts @@ -32,7 +32,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.selectIndexPattern('packetbeat-*'); await PageObjects.timePicker.setCommonlyUsedTime('Last_1 year'); await retry.try(async function () { - const hitCount = parseInt(await PageObjects.discover.getHitCount(), 10); + const hitCount = await PageObjects.discover.getHitCountInt(); expect(hitCount).to.be.greaterThan(0); }); }); diff --git a/x-pack/test/stack_functional_integration/apps/winlogbeat/_winlogbeat.ts b/x-pack/test/stack_functional_integration/apps/winlogbeat/_winlogbeat.ts index 4f8107f937a7..99bcf4e15dcd 100644 --- a/x-pack/test/stack_functional_integration/apps/winlogbeat/_winlogbeat.ts +++ b/x-pack/test/stack_functional_integration/apps/winlogbeat/_winlogbeat.ts @@ -27,7 +27,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.selectIndexPattern('winlogbeat-*'); await PageObjects.timePicker.setCommonlyUsedTime('Last_1 year'); await retry.try(async function () { - const hitCount = parseInt(await PageObjects.discover.getHitCount(), 10); + const hitCount = await PageObjects.discover.getHitCountInt(); expect(hitCount).to.be.greaterThan(0); }); }); diff --git a/x-pack/test/upgrade/apps/discover/discover_smoke_tests.ts b/x-pack/test/upgrade/apps/discover/discover_smoke_tests.ts index bfb51f3e5336..64adb488ca2b 100644 --- a/x-pack/test/upgrade/apps/discover/discover_smoke_tests.ts +++ b/x-pack/test/upgrade/apps/discover/discover_smoke_tests.ts @@ -40,12 +40,12 @@ export default function ({ getPageObjects }: FtrProviderContext) { await PageObjects.discover.selectIndexPattern(String(index)); await PageObjects.discover.waitUntilSearchingHasFinished(); if (timefield) { - await PageObjects.timePicker.setCommonlyUsedTime('Last_24 hours'); + await PageObjects.timePicker.setCommonlyUsedTime('Last_1 year'); await PageObjects.discover.waitUntilSearchingHasFinished(); } }); it('shows hit count greater than zero', async () => { - const hitCount = await PageObjects.discover.getHitCount(); + const hitCount = await PageObjects.discover.getHitCountInt(); if (hits === '') { expect(hitCount).to.be.greaterThan(0); } else { @@ -69,12 +69,12 @@ export default function ({ getPageObjects }: FtrProviderContext) { await PageObjects.home.launchSampleDiscover(name); await PageObjects.header.waitUntilLoadingHasFinished(); if (timefield) { - await PageObjects.timePicker.setCommonlyUsedTime('Last_24 hours'); + await PageObjects.timePicker.setCommonlyUsedTime('Last_1 year'); await PageObjects.discover.waitUntilSearchingHasFinished(); } }); it('shows hit count greater than zero', async () => { - const hitCount = await PageObjects.discover.getHitCount(); + const hitCount = await PageObjects.discover.getHitCountInt(); if (hits === '') { expect(hitCount).to.be.greaterThan(0); } else { diff --git a/yarn.lock b/yarn.lock index f97953886c2a..554c99f5ce85 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"@adobe/css-tools@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.0.1.tgz#b38b444ad3aa5fedbb15f2f746dcd934226a12dd" + integrity sha512-+u76oB43nOHrF4DDWRLWDCtci7f3QJoEBigemIdIeTi1ODqjx6Tad9NCVnPRwewWlKkVab5PlK8DCtPTyX7S8g== + "@ampproject/remapping@^2.1.0": version "2.1.2" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.1.2.tgz#4edca94973ded9630d20101cd8559cedb8d8bd34" @@ -81,10 +86,10 @@ dependencies: "@babel/highlight" "^7.18.6" -"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.18.8", "@babel/compat-data@^7.19.3": - version "7.19.3" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.19.3.tgz#707b939793f867f5a73b2666e6d9a3396eb03151" - integrity sha512-prBHMK4JYYK+wDjJF1q99KK4JLL+egWS4nmNqdlMUgCExMZ+iZW0hGhyC3VEbsPjvaN0TBhW//VIFwBrk8sEiw== +"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.19.3", "@babel/compat-data@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.19.4.tgz#95c86de137bf0317f3a570e1b6e996b427299747" + integrity sha512-CHIGpJcUQ5lU9KrPHTjBMhVwQG6CQjxfg36fGXl3qk/Gik1WwWachaXFuo0uCWJT/mStOKtcbFJCaVLihC1CMw== "@babel/core@7.12.9": version "7.12.9" @@ -108,21 +113,21 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/core@^7.1.0", "@babel/core@^7.12.10", "@babel/core@^7.12.3", "@babel/core@^7.19.3", "@babel/core@^7.7.5": - version "7.19.3" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.19.3.tgz#2519f62a51458f43b682d61583c3810e7dcee64c" - integrity sha512-WneDJxdsjEvyKtXKsaBGbDeiyOjR5vYq4HcShxnIbG0qixpoHjI3MqeZM9NDvsojNCEBItQE4juOo/bU6e72gQ== +"@babel/core@^7.1.0", "@babel/core@^7.12.10", "@babel/core@^7.12.3", "@babel/core@^7.19.6", "@babel/core@^7.7.2", "@babel/core@^7.7.5", "@babel/core@^7.8.0": + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.19.6.tgz#7122ae4f5c5a37c0946c066149abd8e75f81540f" + integrity sha512-D2Ue4KHpc6Ys2+AxpIx1BZ8+UegLLLE2p3KJEuJRKmokHOtl49jQ5ny1773KsGLZs8MQvBidAF6yWUJxRqtKtg== dependencies: "@ampproject/remapping" "^2.1.0" "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.19.3" + "@babel/generator" "^7.19.6" "@babel/helper-compilation-targets" "^7.19.3" - "@babel/helper-module-transforms" "^7.19.0" - "@babel/helpers" "^7.19.0" - "@babel/parser" "^7.19.3" + "@babel/helper-module-transforms" "^7.19.6" + "@babel/helpers" "^7.19.4" + "@babel/parser" "^7.19.6" "@babel/template" "^7.18.10" - "@babel/traverse" "^7.19.3" - "@babel/types" "^7.19.3" + "@babel/traverse" "^7.19.6" + "@babel/types" "^7.19.4" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" @@ -145,12 +150,12 @@ dependencies: eslint-rule-composer "^0.3.0" -"@babel/generator@^7.12.11", "@babel/generator@^7.12.5", "@babel/generator@^7.19.3": - version "7.19.3" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.19.3.tgz#d7f4d1300485b4547cb6f94b27d10d237b42bf59" - integrity sha512-fqVZnmp1ncvZU757UzDheKZpfPgatqY59XtW2/j/18H7u76akb8xqvjw82f+i2UKd/ksYsSick/BCLQUUtJ/qQ== +"@babel/generator@^7.12.11", "@babel/generator@^7.12.5", "@babel/generator@^7.19.6", "@babel/generator@^7.7.2": + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.19.6.tgz#9e481a3fe9ca6261c972645ae3904ec0f9b34a1d" + integrity sha512-oHGRUQeoX1QrKeJIKVe0hwjGqNnVYsM5Nep5zo0uE0m42sLH+Fsd2pStJ5sRM1bNyTUUoz0pe2lTeMJrb/taTA== dependencies: - "@babel/types" "^7.19.3" + "@babel/types" "^7.19.4" "@jridgewell/gen-mapping" "^0.3.2" jsesc "^2.5.1" @@ -267,19 +272,19 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-module-transforms@^7.12.1", "@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.19.0.tgz#309b230f04e22c58c6a2c0c0c7e50b216d350c30" - integrity sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ== +"@babel/helper-module-transforms@^7.12.1", "@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.19.0", "@babel/helper-module-transforms@^7.19.6": + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.19.6.tgz#6c52cc3ac63b70952d33ee987cbee1c9368b533f" + integrity sha512-fCmcfQo/KYr/VXXDIyd3CBGZ6AFhPFy1TfSEJ+PilGVlQT6jcbqtHAM4C1EciRqMza7/TpOUZliuSH+U6HAhJw== dependencies: "@babel/helper-environment-visitor" "^7.18.9" "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-simple-access" "^7.18.6" + "@babel/helper-simple-access" "^7.19.4" "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/helper-validator-identifier" "^7.18.6" + "@babel/helper-validator-identifier" "^7.19.1" "@babel/template" "^7.18.10" - "@babel/traverse" "^7.19.0" - "@babel/types" "^7.19.0" + "@babel/traverse" "^7.19.6" + "@babel/types" "^7.19.4" "@babel/helper-optimise-call-expression@^7.18.6": version "7.18.6" @@ -319,12 +324,12 @@ "@babel/traverse" "^7.18.9" "@babel/types" "^7.18.9" -"@babel/helper-simple-access@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz#d6d8f51f4ac2978068df934b569f08f29788c7ea" - integrity sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g== +"@babel/helper-simple-access@^7.18.6", "@babel/helper-simple-access@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.19.4.tgz#be553f4951ac6352df2567f7daa19a0ee15668e7" + integrity sha512-f9Xq6WqBFqaDfbCzn2w85hwklswz5qsKlh7f08w4Y9yhJHpnNC0QemtSkK5YyOY8kPGvyiwdzZksGUhnGdaUIg== dependencies: - "@babel/types" "^7.18.6" + "@babel/types" "^7.19.4" "@babel/helper-skip-transparent-expression-wrappers@^7.18.9": version "7.18.9" @@ -340,10 +345,10 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-string-parser@^7.18.10": - version "7.18.10" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz#181f22d28ebe1b3857fa575f5c290b1aaf659b56" - integrity sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw== +"@babel/helper-string-parser@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63" + integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw== "@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1": version "7.19.1" @@ -365,14 +370,14 @@ "@babel/traverse" "^7.18.9" "@babel/types" "^7.18.9" -"@babel/helpers@^7.12.5", "@babel/helpers@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.19.0.tgz#f30534657faf246ae96551d88dd31e9d1fa1fc18" - integrity sha512-DRBCKGwIEdqY3+rPJgG/dKfQy9+08rHIAJx8q2p+HSWP87s2HCrQmaAMMyMll2kIXKCW0cO1RdQskx15Xakftg== +"@babel/helpers@^7.12.5", "@babel/helpers@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.19.4.tgz#42154945f87b8148df7203a25c31ba9a73be46c5" + integrity sha512-G+z3aOx2nfDHwX/kyVii5fJq+bgscg89/dJNWpYeKeBv3v9xX8EIabmx1k6u9LS04H7nROFVRVK+e3k0VHp+sw== dependencies: "@babel/template" "^7.18.10" - "@babel/traverse" "^7.19.0" - "@babel/types" "^7.19.0" + "@babel/traverse" "^7.19.4" + "@babel/types" "^7.19.4" "@babel/highlight@^7.10.4", "@babel/highlight@^7.18.6": version "7.18.6" @@ -383,10 +388,10 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.10.3", "@babel/parser@^7.12.11", "@babel/parser@^7.12.7", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.19.3": - version "7.19.3" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.19.3.tgz#8dd36d17c53ff347f9e55c328710321b49479a9a" - integrity sha512-pJ9xOlNWHiy9+FuFP09DEAFbAn4JskgRsVcc169w2xRBC3FRGuQEwjeIMMND9L2zc0iEhO/tGv4Zq+km+hxNpQ== +"@babel/parser@^7.1.0", "@babel/parser@^7.10.3", "@babel/parser@^7.12.11", "@babel/parser@^7.12.7", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.19.6": + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.19.6.tgz#b923430cb94f58a7eae8facbffa9efd19130e7f8" + integrity sha512-h1IUp81s2JYJ3mRkdxJgs4UvmSsRvDrx5ICSJbPvtWYv5i1nTBGcBpnog+89rAFMwvvru6E5NUHdBe01UeSzYA== "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": version "7.18.6" @@ -505,14 +510,14 @@ "@babel/plugin-syntax-object-rest-spread" "^7.8.0" "@babel/plugin-transform-parameters" "^7.12.1" -"@babel/plugin-proposal-object-rest-spread@^7.12.1", "@babel/plugin-proposal-object-rest-spread@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.9.tgz#f9434f6beb2c8cae9dfcf97d2a5941bbbf9ad4e7" - integrity sha512-kDDHQ5rflIeY5xl69CEqGEZ0KY369ehsCIEbTGb4siHG5BE9sga/T0r0OUwyZNLMmZE79E1kbsqAjwFCW4ds6Q== +"@babel/plugin-proposal-object-rest-spread@^7.12.1", "@babel/plugin-proposal-object-rest-spread@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.19.4.tgz#a8fc86e8180ff57290c91a75d83fe658189b642d" + integrity sha512-wHmj6LDxVDnL+3WhXteUBaoM1aVILZODAUjg11kHqG4cOlfgMQGxw6aCgvrXrmaJR3Bn14oZhImyCPZzRpC93Q== dependencies: - "@babel/compat-data" "^7.18.8" - "@babel/helper-compilation-targets" "^7.18.9" - "@babel/helper-plugin-utils" "^7.18.9" + "@babel/compat-data" "^7.19.4" + "@babel/helper-compilation-targets" "^7.19.3" + "@babel/helper-plugin-utils" "^7.19.0" "@babel/plugin-syntax-object-rest-spread" "^7.8.3" "@babel/plugin-transform-parameters" "^7.18.8" @@ -713,7 +718,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-syntax-typescript@^7.18.6": +"@babel/plugin-syntax-typescript@^7.18.6", "@babel/plugin-syntax-typescript@^7.7.2": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.18.6.tgz#1c09cd25795c7c2b8a4ba9ae49394576d4133285" integrity sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA== @@ -743,12 +748,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-block-scoping@^7.12.12", "@babel/plugin-transform-block-scoping@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.18.9.tgz#f9b7e018ac3f373c81452d6ada8bd5a18928926d" - integrity sha512-5sDIJRV1KtQVEbt/EIBwGy4T01uYIo4KRB3VUqzkhrAIOGx7AoctL9+Ux88btY0zXdDyPJ9mW+bg+v+XEkGmtw== +"@babel/plugin-transform-block-scoping@^7.12.12", "@babel/plugin-transform-block-scoping@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.19.4.tgz#315d70f68ce64426db379a3d830e7ac30be02e9b" + integrity sha512-934S2VLLlt2hRJwPf4MczaOr4hYF0z+VKPwqTNxyKX7NthTiPfhuKFWQZHXRM0vh/wo/VyXB3s4bZUNA08l+tQ== dependencies: - "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-plugin-utils" "^7.19.0" "@babel/plugin-transform-classes@^7.12.1", "@babel/plugin-transform-classes@^7.19.0": version "7.19.0" @@ -772,12 +777,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.9" -"@babel/plugin-transform-destructuring@^7.12.1", "@babel/plugin-transform-destructuring@^7.18.13": - version "7.18.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.13.tgz#9e03bc4a94475d62b7f4114938e6c5c33372cbf5" - integrity sha512-TodpQ29XekIsex2A+YJPj5ax2plkGa8YYY6mFjCohk/IG9IY42Rtuj1FuDeemfg2ipxIFLzPeA83SIBnlhSIow== +"@babel/plugin-transform-destructuring@^7.12.1", "@babel/plugin-transform-destructuring@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.19.4.tgz#46890722687b9b89e1369ad0bd8dc6c5a3b4319d" + integrity sha512-t0j0Hgidqf0aM86dF8U+vXYReUgJnlv4bZLsyoPnwZNrGY+7/38o8YjaELrvHeVfTZao15kjR0PVv0nju2iduA== dependencies: - "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-plugin-utils" "^7.19.0" "@babel/plugin-transform-dotall-regex@^7.18.6", "@babel/plugin-transform-dotall-regex@^7.4.4": version "7.18.6" @@ -963,10 +968,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-runtime@^7.19.1": - version "7.19.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.19.1.tgz#a3df2d7312eea624c7889a2dcd37fd1dfd25b2c6" - integrity sha512-2nJjTUFIzBMP/f/miLxEK9vxwW/KUXsdvN4sR//TmuDhe6yU2h57WmIOE12Gng3MDP/xpjUV/ToZRdcf8Yj4fA== +"@babel/plugin-transform-runtime@^7.19.6": + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.19.6.tgz#9d2a9dbf4e12644d6f46e5e75bfbf02b5d6e9194" + integrity sha512-PRH37lz4JU156lYFW1p8OxE5i7d6Sl/zV58ooyr+q1J1lnQPyg5tIiXlIwNVhJaY4W3TmOtdc8jqdXQcB1v5Yw== dependencies: "@babel/helper-module-imports" "^7.18.6" "@babel/helper-plugin-utils" "^7.19.0" @@ -1035,12 +1040,12 @@ "@babel/helper-create-regexp-features-plugin" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" -"@babel/preset-env@^7.12.11", "@babel/preset-env@^7.19.3": - version "7.19.3" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.19.3.tgz#52cd19abaecb3f176a4ff9cc5e15b7bf06bec754" - integrity sha512-ziye1OTc9dGFOAXSWKUqQblYHNlBOaDl8wzqf2iKXJAltYiR3hKHUKmkt+S9PppW7RQpq4fFCrwwpIDj/f5P4w== +"@babel/preset-env@^7.12.11", "@babel/preset-env@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.19.4.tgz#4c91ce2e1f994f717efb4237891c3ad2d808c94b" + integrity sha512-5QVOTXUdqTCjQuh2GGtdd7YEhoRXBMVGROAtsBeLGIbIz3obCBIfRMT1I3ZKkMgNzwkyCkftDXSSkHxnfVf4qg== dependencies: - "@babel/compat-data" "^7.19.3" + "@babel/compat-data" "^7.19.4" "@babel/helper-compilation-targets" "^7.19.3" "@babel/helper-plugin-utils" "^7.19.0" "@babel/helper-validator-option" "^7.18.6" @@ -1055,7 +1060,7 @@ "@babel/plugin-proposal-logical-assignment-operators" "^7.18.9" "@babel/plugin-proposal-nullish-coalescing-operator" "^7.18.6" "@babel/plugin-proposal-numeric-separator" "^7.18.6" - "@babel/plugin-proposal-object-rest-spread" "^7.18.9" + "@babel/plugin-proposal-object-rest-spread" "^7.19.4" "@babel/plugin-proposal-optional-catch-binding" "^7.18.6" "@babel/plugin-proposal-optional-chaining" "^7.18.9" "@babel/plugin-proposal-private-methods" "^7.18.6" @@ -1079,10 +1084,10 @@ "@babel/plugin-transform-arrow-functions" "^7.18.6" "@babel/plugin-transform-async-to-generator" "^7.18.6" "@babel/plugin-transform-block-scoped-functions" "^7.18.6" - "@babel/plugin-transform-block-scoping" "^7.18.9" + "@babel/plugin-transform-block-scoping" "^7.19.4" "@babel/plugin-transform-classes" "^7.19.0" "@babel/plugin-transform-computed-properties" "^7.18.9" - "@babel/plugin-transform-destructuring" "^7.18.13" + "@babel/plugin-transform-destructuring" "^7.19.4" "@babel/plugin-transform-dotall-regex" "^7.18.6" "@babel/plugin-transform-duplicate-keys" "^7.18.9" "@babel/plugin-transform-exponentiation-operator" "^7.18.6" @@ -1109,7 +1114,7 @@ "@babel/plugin-transform-unicode-escapes" "^7.18.10" "@babel/plugin-transform-unicode-regex" "^7.18.6" "@babel/preset-modules" "^0.1.5" - "@babel/types" "^7.19.3" + "@babel/types" "^7.19.4" babel-plugin-polyfill-corejs2 "^0.3.3" babel-plugin-polyfill-corejs3 "^0.6.0" babel-plugin-polyfill-regenerator "^0.4.1" @@ -1175,10 +1180,10 @@ core-js-pure "^3.0.0" regenerator-runtime "^0.13.4" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.3", "@babel/runtime@^7.19.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.19.0.tgz#22b11c037b094d27a8a2504ea4dcff00f50e2259" - integrity sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA== +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.3", "@babel/runtime@^7.19.4", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.19.4.tgz#a42f814502ee467d55b38dd1c256f53a7b885c78" + integrity sha512-EXpLCrk55f+cYqmHsSR+yD/0gAIMxxA9QK9lnQWzhMCvt+YmoBN7Zx94s++Kv0+unHk39vxNO8t+CMA2WSS3wA== dependencies: regenerator-runtime "^0.13.4" @@ -1191,28 +1196,28 @@ "@babel/parser" "^7.18.10" "@babel/types" "^7.18.10" -"@babel/traverse@^7.1.0", "@babel/traverse@^7.10.3", "@babel/traverse@^7.12.11", "@babel/traverse@^7.12.9", "@babel/traverse@^7.13.0", "@babel/traverse@^7.18.9", "@babel/traverse@^7.19.0", "@babel/traverse@^7.19.3", "@babel/traverse@^7.4.5": - version "7.19.3" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.19.3.tgz#3a3c5348d4988ba60884e8494b0592b2f15a04b4" - integrity sha512-qh5yf6149zhq2sgIXmwjnsvmnNQC2iw70UFjp4olxucKrWd/dvlUsBI88VSLUsnMNF7/vnOiA+nk1+yLoCqROQ== +"@babel/traverse@^7.10.3", "@babel/traverse@^7.12.11", "@babel/traverse@^7.12.9", "@babel/traverse@^7.13.0", "@babel/traverse@^7.18.9", "@babel/traverse@^7.19.4", "@babel/traverse@^7.19.6", "@babel/traverse@^7.4.5", "@babel/traverse@^7.7.2": + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.19.6.tgz#7b4c865611df6d99cb131eec2e8ac71656a490dc" + integrity sha512-6l5HrUCzFM04mfbG09AagtYyR2P0B71B1wN7PfSPiksDPz2k5H9CBC1tcZpz2M8OxbKTPccByoOJ22rUKbpmQQ== dependencies: "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.19.3" + "@babel/generator" "^7.19.6" "@babel/helper-environment-visitor" "^7.18.9" "@babel/helper-function-name" "^7.19.0" "@babel/helper-hoist-variables" "^7.18.6" "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.19.3" - "@babel/types" "^7.19.3" + "@babel/parser" "^7.19.6" + "@babel/types" "^7.19.4" debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.10.3", "@babel/types@^7.12.11", "@babel/types@^7.12.7", "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.19.3", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": - version "7.19.3" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.19.3.tgz#fc420e6bbe54880bce6779ffaf315f5e43ec9624" - integrity sha512-hGCaQzIY22DJlDh9CH7NOxgKkFjBk0Cw9xDO1Xmh2151ti7wiGfQ3LauXzL4HP1fmFlTX6XjpRETTpUcv7wQLw== +"@babel/types@^7.0.0", "@babel/types@^7.10.3", "@babel/types@^7.12.11", "@babel/types@^7.12.7", "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.19.4", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.19.4.tgz#0dd5c91c573a202d600490a35b33246fed8a41c7" + integrity sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw== dependencies: - "@babel/helper-string-parser" "^7.18.10" + "@babel/helper-string-parser" "^7.19.4" "@babel/helper-validator-identifier" "^7.19.1" to-fast-properties "^2.0.0" @@ -2348,61 +2353,61 @@ resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd" integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw== -"@jest/console@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-26.6.2.tgz#4e04bc464014358b03ab4937805ee36a0aeb98f2" - integrity sha512-IY1R2i2aLsLr7Id3S6p2BA82GNWryt4oSvEXLAKc+L2zdi89dSkE8xC1C+0kpATG4JhBJREnQOH7/zmccM2B0g== +"@jest/console@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-27.5.1.tgz#260fe7239602fe5130a94f1aa386eff54b014bba" + integrity sha512-kZ/tNpS3NXn0mlXXXPNuDZnb4c0oZ20r4K5eemM2k30ZC3G0T02nXUvyhf5YdbXWHPEJLc9qGLxEZ216MdL+Zg== dependencies: - "@jest/types" "^26.6.2" + "@jest/types" "^27.5.1" "@types/node" "*" chalk "^4.0.0" - jest-message-util "^26.6.2" - jest-util "^26.6.2" + jest-message-util "^27.5.1" + jest-util "^27.5.1" slash "^3.0.0" -"@jest/core@^26.6.3": - version "26.6.3" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-26.6.3.tgz#7639fcb3833d748a4656ada54bde193051e45fad" - integrity sha512-xvV1kKbhfUqFVuZ8Cyo+JPpipAHHAV3kcDBftiduK8EICXmTFddryy3P7NfZt8Pv37rA9nEJBKCCkglCPt/Xjw== - dependencies: - "@jest/console" "^26.6.2" - "@jest/reporters" "^26.6.2" - "@jest/test-result" "^26.6.2" - "@jest/transform" "^26.6.2" - "@jest/types" "^26.6.2" +"@jest/core@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-27.5.1.tgz#267ac5f704e09dc52de2922cbf3af9edcd64b626" + integrity sha512-AK6/UTrvQD0Cd24NSqmIA6rKsu0tKIxfiCducZvqxYdmMisOYAsdItspT+fQDQYARPf8XgjAFZi0ogW2agH5nQ== + dependencies: + "@jest/console" "^27.5.1" + "@jest/reporters" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" + emittery "^0.8.1" exit "^0.1.2" - graceful-fs "^4.2.4" - jest-changed-files "^26.6.2" - jest-config "^26.6.3" - jest-haste-map "^26.6.2" - jest-message-util "^26.6.2" - jest-regex-util "^26.0.0" - jest-resolve "^26.6.2" - jest-resolve-dependencies "^26.6.3" - jest-runner "^26.6.3" - jest-runtime "^26.6.3" - jest-snapshot "^26.6.2" - jest-util "^26.6.2" - jest-validate "^26.6.2" - jest-watcher "^26.6.2" - micromatch "^4.0.2" - p-each-series "^2.1.0" + graceful-fs "^4.2.9" + jest-changed-files "^27.5.1" + jest-config "^27.5.1" + jest-haste-map "^27.5.1" + jest-message-util "^27.5.1" + jest-regex-util "^27.5.1" + jest-resolve "^27.5.1" + jest-resolve-dependencies "^27.5.1" + jest-runner "^27.5.1" + jest-runtime "^27.5.1" + jest-snapshot "^27.5.1" + jest-util "^27.5.1" + jest-validate "^27.5.1" + jest-watcher "^27.5.1" + micromatch "^4.0.4" rimraf "^3.0.0" slash "^3.0.0" strip-ansi "^6.0.0" -"@jest/environment@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-26.6.2.tgz#ba364cc72e221e79cc8f0a99555bf5d7577cf92c" - integrity sha512-nFy+fHl28zUrRsCeMB61VDThV1pVTtlEokBRgqPrcT1JNq4yRNIyTHfyht6PqtUvY9IsuLGTrbG8kPXjSZIZwA== +"@jest/environment@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-27.5.1.tgz#d7425820511fe7158abbecc010140c3fd3be9c74" + integrity sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA== dependencies: - "@jest/fake-timers" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/fake-timers" "^27.5.1" + "@jest/types" "^27.5.1" "@types/node" "*" - jest-mock "^26.6.2" + jest-mock "^27.5.1" "@jest/expect-utils@^28.1.1": version "28.1.1" @@ -2411,58 +2416,57 @@ dependencies: jest-get-type "^28.0.2" -"@jest/fake-timers@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-26.6.2.tgz#459c329bcf70cee4af4d7e3f3e67848123535aad" - integrity sha512-14Uleatt7jdzefLPYM3KLcnUl1ZNikaKq34enpb5XG9i81JpppDb5muZvonvKyrl7ftEHkKS5L5/eB/kxJ+bvA== +"@jest/fake-timers@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-27.5.1.tgz#76979745ce0579c8a94a4678af7a748eda8ada74" + integrity sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ== dependencies: - "@jest/types" "^26.6.2" - "@sinonjs/fake-timers" "^6.0.1" + "@jest/types" "^27.5.1" + "@sinonjs/fake-timers" "^8.0.1" "@types/node" "*" - jest-message-util "^26.6.2" - jest-mock "^26.6.2" - jest-util "^26.6.2" + jest-message-util "^27.5.1" + jest-mock "^27.5.1" + jest-util "^27.5.1" -"@jest/globals@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-26.6.2.tgz#5b613b78a1aa2655ae908eba638cc96a20df720a" - integrity sha512-85Ltnm7HlB/KesBUuALwQ68YTU72w9H2xW9FjZ1eL1U3lhtefjjl5c2MiUbpXt/i6LaPRvoOFJ22yCBSfQ0JIA== +"@jest/globals@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-27.5.1.tgz#7ac06ce57ab966566c7963431cef458434601b2b" + integrity sha512-ZEJNB41OBQQgGzgyInAv0UUfDDj3upmHydjieSxFvTRuZElrx7tXg/uVQ5hYVEwiXs3+aMsAeEc9X7xiSKCm4Q== dependencies: - "@jest/environment" "^26.6.2" - "@jest/types" "^26.6.2" - expect "^26.6.2" + "@jest/environment" "^27.5.1" + "@jest/types" "^27.5.1" + expect "^27.5.1" -"@jest/reporters@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-26.6.2.tgz#1f518b99637a5f18307bd3ecf9275f6882a667f6" - integrity sha512-h2bW53APG4HvkOnVMo8q3QXa6pcaNt1HkwVsOPMBV6LD/q9oSpxNSYZQYkAnjdMjrJ86UuYeLo+aEZClV6opnw== +"@jest/reporters@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-27.5.1.tgz#ceda7be96170b03c923c37987b64015812ffec04" + integrity sha512-cPXh9hWIlVJMQkVk84aIvXuBB4uQQmFqZiacloFuGiP3ah1sbCxCosidXFDfqG8+6fO1oR2dTJTlsOy4VFmUfw== dependencies: "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^26.6.2" - "@jest/test-result" "^26.6.2" - "@jest/transform" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/console" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" chalk "^4.0.0" collect-v8-coverage "^1.0.0" exit "^0.1.2" glob "^7.1.2" - graceful-fs "^4.2.4" + graceful-fs "^4.2.9" istanbul-lib-coverage "^3.0.0" - istanbul-lib-instrument "^4.0.3" + istanbul-lib-instrument "^5.1.0" istanbul-lib-report "^3.0.0" istanbul-lib-source-maps "^4.0.0" - istanbul-reports "^3.0.2" - jest-haste-map "^26.6.2" - jest-resolve "^26.6.2" - jest-util "^26.6.2" - jest-worker "^26.6.2" + istanbul-reports "^3.1.3" + jest-haste-map "^27.5.1" + jest-resolve "^27.5.1" + jest-util "^27.5.1" + jest-worker "^27.5.1" slash "^3.0.0" source-map "^0.6.0" string-length "^4.0.1" terminal-link "^2.0.0" - v8-to-istanbul "^7.0.0" - optionalDependencies: - node-notifier "^8.0.0" + v8-to-istanbul "^8.1.0" "@jest/schemas@^28.0.2": version "28.0.2" @@ -2471,35 +2475,34 @@ dependencies: "@sinclair/typebox" "^0.23.3" -"@jest/source-map@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-26.6.2.tgz#29af5e1e2e324cafccc936f218309f54ab69d535" - integrity sha512-YwYcCwAnNmOVsZ8mr3GfnzdXDAl4LaenZP5z+G0c8bzC9/dugL8zRmxZzdoTl4IaS3CryS1uWnROLPFmb6lVvA== +"@jest/source-map@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-27.5.1.tgz#6608391e465add4205eae073b55e7f279e04e8cf" + integrity sha512-y9NIHUYF3PJRlHk98NdC/N1gl88BL08aQQgu4k4ZopQkCw9t9cV8mtl3TV8b/YCB8XaVTFrmUTAJvjsntDireg== dependencies: callsites "^3.0.0" - graceful-fs "^4.2.4" + graceful-fs "^4.2.9" source-map "^0.6.0" -"@jest/test-result@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-26.6.2.tgz#55da58b62df134576cc95476efa5f7949e3f5f18" - integrity sha512-5O7H5c/7YlojphYNrK02LlDIV2GNPYisKwHm2QTKjNZeEzezCbwYs9swJySv2UfPMyZ0VdsmMv7jIlD/IKYQpQ== +"@jest/test-result@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-27.5.1.tgz#56a6585fa80f7cdab72b8c5fc2e871d03832f5bb" + integrity sha512-EW35l2RYFUcUQxFJz5Cv5MTOxlJIQs4I7gxzi2zVU7PJhOwfYq1MdC5nhSmYjX1gmMmLPvB3sIaC+BkcHRBfag== dependencies: - "@jest/console" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/console" "^27.5.1" + "@jest/types" "^27.5.1" "@types/istanbul-lib-coverage" "^2.0.0" collect-v8-coverage "^1.0.0" -"@jest/test-sequencer@^26.6.3": - version "26.6.3" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-26.6.3.tgz#98e8a45100863886d074205e8ffdc5a7eb582b17" - integrity sha512-YHlVIjP5nfEyjlrSr8t/YdNfU/1XEt7c5b4OxcXCjyRhjzLYu/rO69/WHPuYcbCWkz8kAeZVZp2N2+IOLLEPGw== +"@jest/test-sequencer@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-27.5.1.tgz#4057e0e9cea4439e544c6353c6affe58d095745b" + integrity sha512-LCheJF7WB2+9JuCS7VB/EmGIdQuhtqjRNI9A43idHv3E4KltCTsPsLxvdaubFHSYwY/fNjMWjl6vNRhDiN7vpQ== dependencies: - "@jest/test-result" "^26.6.2" - graceful-fs "^4.2.4" - jest-haste-map "^26.6.2" - jest-runner "^26.6.3" - jest-runtime "^26.6.3" + "@jest/test-result" "^27.5.1" + graceful-fs "^4.2.9" + jest-haste-map "^27.5.1" + jest-runtime "^27.5.1" "@jest/transform@^26.6.2": version "26.6.2" @@ -2522,7 +2525,28 @@ source-map "^0.6.1" write-file-atomic "^3.0.0" -"@jest/types@^26", "@jest/types@^26.6.2": +"@jest/transform@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-27.5.1.tgz#6c3501dcc00c4c08915f292a600ece5ecfe1f409" + integrity sha512-ipON6WtYgl/1329g5AIJVbUuEh0wZVbdpGwC99Jw4LwuoBNS95MVphU6zOeD9pDkon+LLbFL7lOQRapbB8SCHw== + dependencies: + "@babel/core" "^7.1.0" + "@jest/types" "^27.5.1" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^1.4.0" + fast-json-stable-stringify "^2.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^27.5.1" + jest-regex-util "^27.5.1" + jest-util "^27.5.1" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + source-map "^0.6.1" + write-file-atomic "^3.0.0" + +"@jest/types@^26.6.2": version "26.6.2" resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e" integrity sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ== @@ -2533,10 +2557,10 @@ "@types/yargs" "^15.0.0" chalk "^4.0.0" -"@jest/types@^27.1.1": - version "27.1.1" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.1.1.tgz#77a3fc014f906c65752d12123a0134359707c0ad" - integrity sha512-yqJPDDseb0mXgKqmNqypCsb85C22K1aY5+LUxh7syIM9n/b0AsaltxNy+o6tt29VcfGDpYEve175bm3uOhcehA== +"@jest/types@^27", "@jest/types@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.5.1.tgz#3c79ec4a8ba61c170bf937bcf9e98a9df175ec80" + integrity sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw== dependencies: "@types/istanbul-lib-coverage" "^2.0.0" "@types/istanbul-reports" "^3.0.0" @@ -4868,10 +4892,10 @@ dependencies: type-detect "4.0.8" -"@sinonjs/fake-timers@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz#293674fccb3262ac782c7aadfdeca86b10c75c40" - integrity sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA== +"@sinonjs/fake-timers@^8.0.1": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz#3fdc2b6cb58935b21bfb8d1625eb1300484316e7" + integrity sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg== dependencies: "@sinonjs/commons" "^1.7.0" @@ -5853,10 +5877,10 @@ resolved "https://registry.yarnpkg.com/@testim/chrome-version/-/chrome-version-1.1.3.tgz#fbb68696899d7b8c1b9b891eded9c04fe2cd5529" integrity sha512-g697J3WxV/Zytemz8aTuKjTGYtta9+02kva3C1xc7KXB8GdbfE1akGJIsZLyY/FSh2QrnE+fiB7vmWU3XNcb6A== -"@testing-library/dom@^8.0.0", "@testing-library/dom@^8.12.0": - version "8.12.0" - resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.12.0.tgz#fef5e545533fb084175dda6509ee71d7d2f72e23" - integrity sha512-rBrJk5WjI02X1edtiUcZhgyhgBhiut96r5Jp8J5qktKdcvLcZpKDW8i2hkGMMItxrghjXuQ5AM6aE0imnFawaw== +"@testing-library/dom@^8.0.0", "@testing-library/dom@^8.17.1": + version "8.17.1" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.17.1.tgz#2d7af4ff6dad8d837630fecd08835aee08320ad7" + integrity sha512-KnH2MnJUzmFNPW6RIKfd+zf2Wue8mEKX0M3cpX6aKl5ZXrJM1/c/Pc8c2xDNYQCnJO48Sm5ITbMXgqTr3h4jxQ== dependencies: "@babel/code-frame" "^7.10.4" "@babel/runtime" "^7.12.5" @@ -5867,30 +5891,27 @@ lz-string "^1.4.4" pretty-format "^27.0.2" -"@testing-library/jest-dom@^5.16.3": - version "5.16.3" - resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.16.3.tgz#b76851a909586113c20486f1679ffb4d8ec27bfa" - integrity sha512-u5DfKj4wfSt6akfndfu1eG06jsdyA/IUrlX2n3pyq5UXgXMhXY+NJb8eNK/7pqPWAhCKsCGWDdDO0zKMKAYkEA== +"@testing-library/jest-dom@^5.16.5": + version "5.16.5" + resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.16.5.tgz#3912846af19a29b2dbf32a6ae9c31ef52580074e" + integrity sha512-N5ixQ2qKpi5OLYfwQmUb/5mSV9LneAcaUfp32pn4yCnpb8r/Yz0pXFPck21dIicKmi+ta5WRAknkZCfA8refMA== dependencies: + "@adobe/css-tools" "^4.0.1" "@babel/runtime" "^7.9.2" "@types/testing-library__jest-dom" "^5.9.1" aria-query "^5.0.0" chalk "^3.0.0" - css "^3.0.0" css.escape "^1.5.1" dom-accessibility-api "^0.5.6" lodash "^4.17.15" redent "^3.0.0" -"@testing-library/react-hooks@^7.0.2": - version "7.0.2" - resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-7.0.2.tgz#3388d07f562d91e7f2431a4a21b5186062ecfee0" - integrity sha512-dYxpz8u9m4q1TuzfcUApqi8iFfR6R0FaMbr2hjZJy1uC8z+bO/K4v8Gs9eogGKYQop7QsrBTFkv/BCF7MzD2Cg== +"@testing-library/react-hooks@^8.0.1": + version "8.0.1" + resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-8.0.1.tgz#0924bbd5b55e0c0c0502d1754657ada66947ca12" + integrity sha512-Aqhl2IVmLt8IovEVarNDFuJDVWVvhnr9/GCU6UUnrYXwgDFF9h2L2o2P9KBni1AST5sT6riAyoukFLyjQUgD/g== dependencies: "@babel/runtime" "^7.12.5" - "@types/react" ">=16.9.0" - "@types/react-dom" ">=16.9.0" - "@types/react-test-renderer" ">=16.9.0" react-error-boundary "^3.1.0" "@testing-library/react@^12.1.5": @@ -5914,6 +5935,11 @@ resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== +"@tootallnate/once@2": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" + integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== + "@trysound/sax@0.2.0": version "0.2.0" resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" @@ -6127,7 +6153,7 @@ resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.0.tgz#14264692a9d6e2fa4db3df5e56e94b5e25647ac0" integrity sha512-iIgQNzCm0v7QMhhe4Jjn9uRh+I6GoPmt03CbEtwx3ao8/EfoQcmgtqH4vQ5Db/lxiIGaWDv6nwvunuh0RyX0+A== -"@types/babel__core@*", "@types/babel__core@^7.0.0", "@types/babel__core@^7.1.19", "@types/babel__core@^7.1.7": +"@types/babel__core@*", "@types/babel__core@^7.1.14", "@types/babel__core@^7.1.19": version "7.1.19" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.19.tgz#7b497495b7d1b4812bdb9d02804d0576f43ee460" integrity sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw== @@ -6138,7 +6164,25 @@ "@types/babel__template" "*" "@types/babel__traverse" "*" -"@types/babel__generator@*", "@types/babel__generator@^7.6.4": +"@types/babel__core@^7.0.0": + version "7.1.10" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.10.tgz#ca58fc195dd9734e77e57c6f2df565623636ab40" + integrity sha512-x8OM8XzITIMyiwl5Vmo2B1cR1S1Ipkyv4mdlbJjMa1lmuKvKY9FrBbEANIaMlnWn5Rf7uO+rC/VgYabNkE17Hw== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.0.2" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.0.2.tgz#d2112a6b21fad600d7674274293c85dce0cb47fc" + integrity sha512-NHcOfab3Zw4q5sEE2COkpfXjoE7o+PmqD9DQW4koUT3roNxwziUdXGnRndMat/LJNUtePwn1TlP4do3uoe3KZQ== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__generator@^7.6.4": version "7.6.4" resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.4.tgz#1f20ce4c5b1990b37900b63f050182d28c2439b7" integrity sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg== @@ -6649,13 +6693,6 @@ resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz#9140779736aa2655635ee756e2467d787cfe8a2a" integrity sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A== -"@types/http-proxy-agent@^2.0.2": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@types/http-proxy-agent/-/http-proxy-agent-2.0.2.tgz#942c1f35c7e1f0edd1b6ffae5d0f9051cfb32be1" - integrity sha512-2S6IuBRhqUnH1/AUx9k8KWtY3Esg4eqri946MnxTG5HwehF1S5mqLln8fcyMiuQkY72p2gH3W+rIPqp5li0LyQ== - dependencies: - "@types/node" "*" - "@types/http-proxy@^1.17.4", "@types/http-proxy@^1.17.8": version "1.17.9" resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.9.tgz#7f0e7931343761efde1e2bf48c40f02f3f75705a" @@ -6715,7 +6752,7 @@ dependencies: "@types/jest" "*" -"@types/jest@*", "@types/jest@^26.0.16", "@types/jest@^26.0.22": +"@types/jest@*", "@types/jest@^26.0.16": version "26.0.22" resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.22.tgz#8308a1debdf1b807aa47be2838acdcd91e88fbe6" integrity sha512-eeWwWjlqxvBxc4oQdkueW5OF/gtfSceKk4OnOAGlUSwS/liBRtZppbJuz1YkgbrbfGOoeBHun9fOvXnjNwrSOw== @@ -6723,6 +6760,14 @@ jest-diff "^26.0.0" pretty-format "^26.0.0" +"@types/jest@^27.4.1": + version "27.5.2" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-27.5.2.tgz#ec49d29d926500ffb9fd22b84262e862049c026c" + integrity sha512-mpT8LJJ4CMeeahobofYWIjFo0xonRS/HfxnVEPMPFSQdGUt1uHCnoPT7Zhb+sjDU2wz0oKV0OLUR0WzrHNgfeA== + dependencies: + jest-matcher-utils "^27.0.0" + pretty-format "^27.0.0" + "@types/joi@^17.2.3": version "17.2.3" resolved "https://registry.yarnpkg.com/@types/joi/-/joi-17.2.3.tgz#b7768ed9d84f1ebd393328b9f97c1cf3d2b94798" @@ -8451,7 +8496,7 @@ dependencies: "@types/node" "*" -"@types/prettier@^2.0.0", "@types/prettier@^2.3.2": +"@types/prettier@^2.0.0", "@types/prettier@^2.1.5", "@types/prettier@^2.3.2": version "2.3.2" resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.3.2.tgz#fc8c2825e4ed2142473b4a81064e6e081463d1b3" integrity sha512-eI5Yrz3Qv4KPUa/nSIAi0h+qX0XyewOliug5F2QAtuRg6Kjg6jfmxe1GIwoIRhZspD1A0RP8ANrPwvEXXtRFog== @@ -8515,7 +8560,7 @@ dependencies: "@types/react" "*" -"@types/react-dom@<18.0.0", "@types/react-dom@>=16.9.0", "@types/react-dom@^17.0.17": +"@types/react-dom@<18.0.0", "@types/react-dom@^17.0.17": version "17.0.17" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.17.tgz#2e3743277a793a96a99f1bf87614598289da68a1" integrity sha512-VjnqEmqGnasQKV0CWLevqMTXBYG9GbwuE6x3VetERLh0cq2LTptFE73MrQi2S7GkKXCf2GgwItB/melLnxfnsg== @@ -8606,7 +8651,7 @@ dependencies: "@types/react" "*" -"@types/react-test-renderer@>=16.9.0", "@types/react-test-renderer@^17.0.2": +"@types/react-test-renderer@^17.0.2": version "17.0.2" resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-17.0.2.tgz#5f800a39b12ac8d2a2149e7e1885215bcf4edbbf" integrity sha512-+F1KONQTBHDBBhbHuT2GNydeMpPuviduXIVJRB7Y4nma4NR5DrTJfMMZ+jbhEHbpwL+Uqhs1WXh4KHiyrtYTPg== @@ -8649,7 +8694,7 @@ dependencies: "@types/react" "*" -"@types/react@*", "@types/react@>=16.9.0", "@types/react@^17", "@types/react@^17.0.45": +"@types/react@*", "@types/react@^17", "@types/react@^17.0.45": version "17.0.45" resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.45.tgz#9b3d5b661fd26365fefef0e766a1c6c30ccf7b3f" integrity sha512-YfhQ22Lah2e3CHPsb93tRwIGNiSwkuz1/blk4e6QrWS0jQzCSNbGLtOEYhPg02W0yGTTmpajp7dCTbBAMN3qsg== @@ -9559,7 +9604,7 @@ JSONStream@1.3.5: jsonparse "^1.2.0" through ">=2.2.7 <3" -abab@^2.0.3, abab@^2.0.4: +abab@^2.0.3, abab@^2.0.4, abab@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" integrity sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q== @@ -9636,7 +9681,7 @@ acorn@^7.0.0, acorn@^7.1.1, acorn@^7.4.0, acorn@^7.4.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== -acorn@^8.0.4, acorn@^8.4.1, acorn@^8.5.0, acorn@^8.7.1: +acorn@^8.0.4, acorn@^8.2.4, acorn@^8.4.1, acorn@^8.5.0, acorn@^8.7.1: version "8.8.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8" integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w== @@ -9656,13 +9701,6 @@ after-all-results@^2.0.0: resolved "https://registry.yarnpkg.com/after-all-results/-/after-all-results-2.0.0.tgz#6ac2fc202b500f88da8f4f5530cfa100f4c6a2d0" integrity sha1-asL8ICtQD4jaj09VMM+hAPTGotA= -agent-base@4: - version "4.3.0" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" - integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg== - dependencies: - es6-promisify "^5.0.0" - agent-base@6, agent-base@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" @@ -10338,7 +10376,7 @@ atob-lite@^2.0.0: resolved "https://registry.yarnpkg.com/atob-lite/-/atob-lite-2.0.0.tgz#0fef5ad46f1bd7a8502c65727f0367d5ee43d696" integrity sha1-D+9a1G8b16hQLGVyfwNn1e5D1pY= -atob@^2.1.1, atob@^2.1.2: +atob@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== @@ -10451,18 +10489,18 @@ axobject-query@^2.2.0: resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be" integrity sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA== -babel-jest@^26.6.3: - version "26.6.3" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-26.6.3.tgz#d87d25cb0037577a0c89f82e5755c5d293c01056" - integrity sha512-pl4Q+GAVOHwvjrck6jKjvmGhnO3jHX/xuB9d27f+EJZ/6k+6nMuPjorrYp7s++bKKdANwzElBWnLWaObvTnaZA== - dependencies: - "@jest/transform" "^26.6.2" - "@jest/types" "^26.6.2" - "@types/babel__core" "^7.1.7" - babel-plugin-istanbul "^6.0.0" - babel-preset-jest "^26.6.2" +babel-jest@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-27.5.1.tgz#a1bf8d61928edfefd21da27eb86a695bfd691444" + integrity sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg== + dependencies: + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^27.5.1" chalk "^4.0.0" - graceful-fs "^4.2.4" + graceful-fs "^4.2.9" slash "^3.0.0" babel-loader@^8.0.0, babel-loader@^8.2.5: @@ -10534,10 +10572,10 @@ babel-plugin-istanbul@^6.0.0, babel-plugin-istanbul@^6.1.1: istanbul-lib-instrument "^5.0.4" test-exclude "^6.0.0" -babel-plugin-jest-hoist@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.6.2.tgz#8185bd030348d254c6d7dd974355e6a28b21e62d" - integrity sha512-PO9t0697lNTmcEHH69mdtYiOIkkOlj9fySqfO3K1eCcdISevLAE0xY59VLLUj0SoiPiTX/JU2CYFpILydUa5Lw== +babel-plugin-jest-hoist@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz#9be98ecf28c331eb9f5df9c72d6f89deb8181c2e" + integrity sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ== dependencies: "@babel/template" "^7.3.3" "@babel/types" "^7.3.3" @@ -10656,12 +10694,12 @@ babel-preset-current-node-syntax@^1.0.0: "@babel/plugin-syntax-optional-chaining" "^7.8.3" "@babel/plugin-syntax-top-level-await" "^7.8.3" -babel-preset-jest@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-26.6.2.tgz#747872b1171df032252426586881d62d31798fee" - integrity sha512-YvdtlVm9t3k777c5NPQIv6cxFFFapys25HiUmuSgHwIZhfifweR5c5Sf5nwE3MAbfu327CYSvps8Yx6ANLyleQ== +babel-preset-jest@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz#91f10f58034cb7989cb4f962b69fa6eef6a6bc81" + integrity sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag== dependencies: - babel-plugin-jest-hoist "^26.6.2" + babel-plugin-jest-hoist "^27.5.1" babel-preset-current-node-syntax "^1.0.0" babel-runtime@6.x, babel-runtime@^6.26.0: @@ -11622,10 +11660,10 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: inherits "^2.0.1" safe-buffer "^5.0.1" -cjs-module-lexer@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz#4186fcca0eae175970aee870b9fe2d6cf8d5655f" - integrity sha512-uc2Vix1frTfnuzxxu1Hp4ktSvM3QaI4oXl4ZUqL1wjTu/BGki9TrCWoqLTg/drR1KwAEarXuRFCG2Svr1GxPFw== +cjs-module-lexer@^1.0.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" + integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== clamp@^1.0.1: version "1.0.1" @@ -12573,15 +12611,6 @@ css@2.X, css@^2.2.1, css@^2.2.4: source-map-resolve "^0.5.2" urix "^0.1.0" -css@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/css/-/css-3.0.0.tgz#4447a4d58fdd03367c516ca9f64ae365cee4aa5d" - integrity sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ== - dependencies: - inherits "^2.0.4" - source-map "^0.6.1" - source-map-resolve "^0.6.0" - csscolorparser@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/csscolorparser/-/csscolorparser-1.0.3.tgz#b34f391eea4da8f3e98231e2ccd8df9c041f171b" @@ -12663,7 +12692,7 @@ cssom@~0.3.6: resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== -cssstyle@^2.2.0: +cssstyle@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852" integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A== @@ -13288,7 +13317,7 @@ decamelize@^4.0.0: resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== -decimal.js@^10.2.0: +decimal.js@^10.2.1: version "10.3.1" resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783" integrity sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ== @@ -13992,45 +14021,7 @@ elastic-apm-http-client@11.0.1, elastic-apm-http-client@^11.0.1: semver "^6.3.0" stream-chopper "^3.0.1" -elastic-apm-node@^3.38.0: - version "3.38.0" - resolved "https://registry.yarnpkg.com/elastic-apm-node/-/elastic-apm-node-3.38.0.tgz#4d0dc9279c0e23e09b3b30aa4a9f9aeccfa59cc0" - integrity sha512-/d6YuWFtsfkVRpFD0YJ2rYJVq0rI0PGqG/C+cW1JpMZ4IOU8dA9xzUkxbT0G3B8gpHNug07Xo6bJdQa2oUaFbQ== - dependencies: - "@elastic/ecs-pino-format" "^1.2.0" - "@opentelemetry/api" "^1.1.0" - after-all-results "^2.0.0" - async-cache "^1.1.0" - async-value-promise "^1.1.1" - basic-auth "^2.0.1" - cookie "^0.5.0" - core-util-is "^1.0.2" - elastic-apm-http-client "11.0.1" - end-of-stream "^1.4.4" - error-callsites "^2.0.4" - error-stack-parser "^2.0.6" - escape-string-regexp "^4.0.0" - fast-safe-stringify "^2.0.7" - http-headers "^3.0.2" - is-native "^1.0.1" - lru-cache "^6.0.0" - measured-reporting "^1.51.1" - monitor-event-loop-delay "^1.0.0" - object-filter-sequence "^1.0.0" - object-identity-map "^1.0.2" - original-url "^1.2.3" - pino "^6.11.2" - relative-microtime "^2.0.0" - require-in-the-middle "^5.0.3" - semver "^6.3.0" - set-cookie-serde "^1.0.0" - shallow-clone-shim "^2.0.0" - source-map "^0.8.0-beta.0" - sql-summary "^1.0.1" - traverse "^0.6.6" - unicode-byte-truncate "^1.0.0" - -elastic-apm-node@^3.39.0: +elastic-apm-node@^3.38.0, elastic-apm-node@^3.39.0: version "3.39.0" resolved "https://registry.yarnpkg.com/elastic-apm-node/-/elastic-apm-node-3.39.0.tgz#51ca1dfc11e6b48b53967518461a959ac1623da1" integrity sha512-aNRLDMQreZ+u23HStmppdDNtfS7Z651MWf3wLjw72haCNpGczuXsb4EuBRfJOk0IXWXTYgX1cDy2hiy4PAxlSQ== @@ -14112,10 +14103,10 @@ email-addresses@^5.0.0: resolved "https://registry.yarnpkg.com/email-addresses/-/email-addresses-5.0.0.tgz#7ae9e7f58eef7d5e3e2c2c2d3ea49b78dc854fa6" integrity sha512-4OIPYlA6JXqtVn8zpHpGiI7vE6EQOAg16aGnDMIAlZVinnoZ8208tW1hAbjWydgN/4PLTT9q+O1K6AH/vALJGw== -emittery@^0.7.1: - version "0.7.1" - resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.7.1.tgz#c02375a927a40948c0345cc903072597f5270451" - integrity sha512-d34LN4L6h18Bzz9xpoku2nPwKxCPlPMr3EEKTkoEBi+1/+b0lcRkRJ1UVyyZaKNeqGR3swcGl6s390DNO4YVgQ== +emittery@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.8.1.tgz#bb23cc86d03b30aa75a7f734819dee2e1ba70860" + integrity sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg== "emoji-regex@>=6.0.0 <=6.1.1": version "6.1.1" @@ -14242,13 +14233,13 @@ enzyme-shallow-equal@^1.0.0, enzyme-shallow-equal@^1.0.1: has "^1.0.3" object-is "^1.1.2" -enzyme-to-json@^3.6.1: - version "3.6.1" - resolved "https://registry.yarnpkg.com/enzyme-to-json/-/enzyme-to-json-3.6.1.tgz#d60740950bc7ca6384dfe6fe405494ec5df996bc" - integrity sha512-15tXuONeq5ORoZjV/bUo2gbtZrN2IH+Z6DvL35QmZyKHgbY1ahn6wcnLd9Xv9OjiwbAXiiP8MRZwbZrCv1wYNg== +enzyme-to-json@^3.6.2: + version "3.6.2" + resolved "https://registry.yarnpkg.com/enzyme-to-json/-/enzyme-to-json-3.6.2.tgz#94f85c413bcae8ab67be53b0a94b69a560e27823" + integrity sha512-Ynm6Z6R6iwQ0g2g1YToz6DWhxVnt8Dy1ijR2zynRKxTyBGA8rCDXU3rs2Qc4OKvUvc2Qoe1bcFK6bnPs20TrTg== dependencies: "@types/cheerio" "^0.22.22" - lodash "^4.17.15" + lodash "^4.17.21" react-is "^16.12.0" enzyme@^3.11.0: @@ -14415,18 +14406,11 @@ es6-map@^0.1.5: es6-symbol "~3.1.1" event-emitter "~0.3.5" -es6-promise@^4.0.3, es6-promise@^4.2.8: +es6-promise@^4.2.8: version "4.2.8" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== -es6-promisify@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" - integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM= - dependencies: - es6-promise "^4.0.3" - es6-set@^0.1.5, es6-set@~0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.5.tgz#d2b3ec5d4d800ced818db538d28974db0a73ccb1" @@ -14491,7 +14475,7 @@ escape-string-regexp@^2.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== -escodegen@^1.11.1, escodegen@^1.14.1: +escodegen@^1.11.1: version "1.14.3" resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw== @@ -14928,7 +14912,7 @@ exec-sh@^0.3.2: resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.2.tgz#6738de2eb7c8e671d0366aea0b0db8c6f7d7391b" integrity sha512-9sLAvzhI5nc8TpuQUh4ahMdCrWT00wPWz7j47/emR5+2qEfoZP5zzUXvx+vdx+H6ohhnsYC31iX04QLYJK8zTg== -execa@4.1.0, execa@^4.0.0, execa@^4.0.2: +execa@4.1.0, execa@^4.0.2: version "4.1.0" resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA== @@ -15030,17 +15014,15 @@ expect@^26.6.2: jest-message-util "^26.6.2" jest-regex-util "^26.0.0" -expect@^27.0.2: - version "27.2.0" - resolved "https://registry.yarnpkg.com/expect/-/expect-27.2.0.tgz#40eb89a492afb726a3929ccf3611ee0799ab976f" - integrity sha512-oOTbawMQv7AK1FZURbPTgGSzmhxkjFzoARSvDjOMnOpeWuYQx1tP6rXu9MIX5mrACmyCAM7fSNP8IJO2f1p0CQ== +expect@^27.0.2, expect@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/expect/-/expect-27.5.1.tgz#83ce59f1e5bdf5f9d2b94b61d2050db48f3fef74" + integrity sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw== dependencies: - "@jest/types" "^27.1.1" - ansi-styles "^5.0.0" - jest-get-type "^27.0.6" - jest-matcher-utils "^27.2.0" - jest-message-util "^27.2.0" - jest-regex-util "^27.0.6" + "@jest/types" "^27.5.1" + jest-get-type "^27.5.1" + jest-matcher-utils "^27.5.1" + jest-message-util "^27.5.1" expect@^28.1.1: version "28.1.1" @@ -15650,6 +15632,15 @@ form-data@^2.3.1, form-data@^2.3.3: combined-stream "^1.0.6" mime-types "^2.1.12" +form-data@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" + integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + form-data@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" @@ -15829,7 +15820,7 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= -fsevents@^2.1.2, fsevents@~2.3.2: +fsevents@^2.1.2, fsevents@^2.3.2, fsevents@~2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== @@ -16396,11 +16387,6 @@ graphql@^16.6.0: resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.6.0.tgz#c2dcffa4649db149f6282af726c8c83f1c7c5fdb" integrity sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw== -growly@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" - integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= - gulp-brotli@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/gulp-brotli/-/gulp-brotli-3.0.0.tgz#7f5a1d8a6d43cab28056f9e56f29ae071dcfe4b4" @@ -17000,14 +16986,6 @@ http-parser-js@>=0.5.1: resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.3.tgz#01d2709c79d41698bb01d4decc5e9da4e4a033d9" integrity sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg== -http-proxy-agent@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405" - integrity sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg== - dependencies: - agent-base "4" - debug "3.1.0" - http-proxy-agent@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" @@ -17017,6 +16995,15 @@ http-proxy-agent@^4.0.1: agent-base "6" debug "4" +http-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" + integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w== + dependencies: + "@tootallnate/once" "2" + agent-base "6" + debug "4" + http-proxy-middleware@^2.0.3: version "2.0.6" resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz#e1a4dd6979572c7ab5a4e4b55095d1f32a74963f" @@ -17845,7 +17832,7 @@ is-plain-object@^2.0.3, is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" -is-potential-custom-element-name@^1.0.0: +is-potential-custom-element-name@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== @@ -18109,7 +18096,7 @@ istanbul-lib-hook@^3.0.0: dependencies: append-transform "^2.0.0" -istanbul-lib-instrument@^4.0.0, istanbul-lib-instrument@^4.0.3: +istanbul-lib-instrument@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz#873c6fff897450118222774696a3f28902d77c1d" integrity sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ== @@ -18119,7 +18106,7 @@ istanbul-lib-instrument@^4.0.0, istanbul-lib-instrument@^4.0.3: istanbul-lib-coverage "^3.0.0" semver "^6.3.0" -istanbul-lib-instrument@^5.0.4: +istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.1.0.tgz#7b49198b657b27a730b8e9cb601f1e1bff24c59a" integrity sha512-czwUz525rkOFDJxfKK6mYfIs9zBKILyrZQxjz3ABhjQXhbhFsSbo1HW/BFcsDnfJYJWA6thRR5/TUY2qs5W99Q== @@ -18161,10 +18148,10 @@ istanbul-lib-source-maps@^4.0.0: istanbul-lib-coverage "^3.0.0" source-map "^0.6.1" -istanbul-reports@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.0.2.tgz#d593210e5000683750cb09fc0644e4b6e27fd53b" - integrity sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw== +istanbul-reports@^3.0.2, istanbul-reports@^3.1.3: + version "3.1.4" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.4.tgz#1b6f068ecbc6c331040aab5741991273e609e40c" + integrity sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw== dependencies: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" @@ -18207,92 +18194,95 @@ jest-axe@^5.0.0: jest-matcher-utils "27.0.2" lodash.merge "4.6.2" -jest-canvas-mock@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/jest-canvas-mock/-/jest-canvas-mock-2.3.1.tgz#9535d14bc18ccf1493be36ac37dd349928387826" - integrity sha512-5FnSZPrX3Q2ZfsbYNE3wqKR3+XorN8qFzDzB5o0golWgt6EOX1+emBnpOc9IAQ+NXFj8Nzm3h7ZdE/9H0ylBcg== +jest-canvas-mock@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/jest-canvas-mock/-/jest-canvas-mock-2.4.0.tgz#947b71442d7719f8e055decaecdb334809465341" + integrity sha512-mmMpZzpmLzn5vepIaHk5HoH3Ka4WykbSoLuG/EKoJd0x0ID/t+INo1l8ByfcUJuDM+RIsL4QDg/gDnBbrj2/IQ== dependencies: cssfontparser "^1.2.1" moo-color "^1.0.2" -jest-changed-files@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-26.6.2.tgz#f6198479e1cc66f22f9ae1e22acaa0b429c042d0" - integrity sha512-fDS7szLcY9sCtIip8Fjry9oGf3I2ht/QT21bAHm5Dmf0mD4X3ReNUf17y+bO6fR8WgbIZTlbyG1ak/53cbRzKQ== +jest-changed-files@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-27.5.1.tgz#a348aed00ec9bf671cc58a66fcbe7c3dfd6a68f5" + integrity sha512-buBLMiByfWGCoMsLLzGUUSpAmIAGnbR2KJoMN10ziLhOLvP4e0SlypHnAel8iqQXTrcbmfEY9sSqae5sgUsTvw== dependencies: - "@jest/types" "^26.6.2" - execa "^4.0.0" - throat "^5.0.0" + "@jest/types" "^27.5.1" + execa "^5.0.0" + throat "^6.0.1" -jest-circus@^26.6.3: - version "26.6.3" - resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-26.6.3.tgz#3cc7ef2a6a3787e5d7bfbe2c72d83262154053e7" - integrity sha512-ACrpWZGcQMpbv13XbzRzpytEJlilP/Su0JtNCi5r/xLpOUhnaIJr8leYYpLEMgPFURZISEHrnnpmB54Q/UziPw== +jest-circus@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-27.5.1.tgz#37a5a4459b7bf4406e53d637b49d22c65d125ecc" + integrity sha512-D95R7x5UtlMA5iBYsOHFFbMD/GVA4R/Kdq15f7xYWUfWHBto9NYRsOvnSauTgdF+ogCpJ4tyKOXhUifxS65gdw== dependencies: - "@babel/traverse" "^7.1.0" - "@jest/environment" "^26.6.2" - "@jest/test-result" "^26.6.2" - "@jest/types" "^26.6.2" - "@types/babel__traverse" "^7.0.4" + "@jest/environment" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/types" "^27.5.1" "@types/node" "*" chalk "^4.0.0" co "^4.6.0" dedent "^0.7.0" - expect "^26.6.2" + expect "^27.5.1" is-generator-fn "^2.0.0" - jest-each "^26.6.2" - jest-matcher-utils "^26.6.2" - jest-message-util "^26.6.2" - jest-runner "^26.6.3" - jest-runtime "^26.6.3" - jest-snapshot "^26.6.2" - jest-util "^26.6.2" - pretty-format "^26.6.2" - stack-utils "^2.0.2" - throat "^5.0.0" + jest-each "^27.5.1" + jest-matcher-utils "^27.5.1" + jest-message-util "^27.5.1" + jest-runtime "^27.5.1" + jest-snapshot "^27.5.1" + jest-util "^27.5.1" + pretty-format "^27.5.1" + slash "^3.0.0" + stack-utils "^2.0.3" + throat "^6.0.1" -jest-cli@^26.6.3: - version "26.6.3" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-26.6.3.tgz#43117cfef24bc4cd691a174a8796a532e135e92a" - integrity sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg== +jest-cli@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-27.5.1.tgz#278794a6e6458ea8029547e6c6cbf673bd30b145" + integrity sha512-Hc6HOOwYq4/74/c62dEE3r5elx8wjYqxY0r0G/nFrLDPMFRu6RA/u8qINOIkvhxG7mMQ5EJsOGfRpI8L6eFUVw== dependencies: - "@jest/core" "^26.6.3" - "@jest/test-result" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/core" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/types" "^27.5.1" chalk "^4.0.0" exit "^0.1.2" - graceful-fs "^4.2.4" + graceful-fs "^4.2.9" import-local "^3.0.2" - is-ci "^2.0.0" - jest-config "^26.6.3" - jest-util "^26.6.2" - jest-validate "^26.6.2" + jest-config "^27.5.1" + jest-util "^27.5.1" + jest-validate "^27.5.1" prompts "^2.0.1" - yargs "^15.4.1" + yargs "^16.2.0" -jest-config@^26, jest-config@^26.6.3: - version "26.6.3" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-26.6.3.tgz#64f41444eef9eb03dc51d5c53b75c8c71f645349" - integrity sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg== +jest-config@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-27.5.1.tgz#5c387de33dca3f99ad6357ddeccd91bf3a0e4a41" + integrity sha512-5sAsjm6tGdsVbW9ahcChPAFCk4IlkQUknH5AvKjuLTSlcO/wCZKyFdn7Rg0EkC+OGgWODEy2hDpWB1PgzH0JNA== dependencies: - "@babel/core" "^7.1.0" - "@jest/test-sequencer" "^26.6.3" - "@jest/types" "^26.6.2" - babel-jest "^26.6.3" + "@babel/core" "^7.8.0" + "@jest/test-sequencer" "^27.5.1" + "@jest/types" "^27.5.1" + babel-jest "^27.5.1" chalk "^4.0.0" + ci-info "^3.2.0" deepmerge "^4.2.2" glob "^7.1.1" - graceful-fs "^4.2.4" - jest-environment-jsdom "^26.6.2" - jest-environment-node "^26.6.2" - jest-get-type "^26.3.0" - jest-jasmine2 "^26.6.3" - jest-regex-util "^26.0.0" - jest-resolve "^26.6.2" - jest-util "^26.6.2" - jest-validate "^26.6.2" - micromatch "^4.0.2" - pretty-format "^26.6.2" + graceful-fs "^4.2.9" + jest-circus "^27.5.1" + jest-environment-jsdom "^27.5.1" + jest-environment-node "^27.5.1" + jest-get-type "^27.5.1" + jest-jasmine2 "^27.5.1" + jest-regex-util "^27.5.1" + jest-resolve "^27.5.1" + jest-runner "^27.5.1" + jest-util "^27.5.1" + jest-validate "^27.5.1" + micromatch "^4.0.4" + parse-json "^5.2.0" + pretty-format "^27.5.1" + slash "^3.0.0" + strip-json-comments "^3.1.1" jest-diff@^26.0.0, jest-diff@^26.6.2: version "26.6.2" @@ -18304,7 +18294,7 @@ jest-diff@^26.0.0, jest-diff@^26.6.2: jest-get-type "^26.3.0" pretty-format "^26.6.2" -jest-diff@^27.0.2, jest-diff@^27.2.0: +jest-diff@^27.0.2, jest-diff@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-27.5.1.tgz#a07f5011ac9e6643cf8a95a462b7b1ecf6680def" integrity sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw== @@ -18324,55 +18314,55 @@ jest-diff@^28.1.1: jest-get-type "^28.0.2" pretty-format "^28.1.1" -jest-docblock@^26.0.0: - version "26.0.0" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-26.0.0.tgz#3e2fa20899fc928cb13bd0ff68bd3711a36889b5" - integrity sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w== +jest-docblock@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-27.5.1.tgz#14092f364a42c6108d42c33c8cf30e058e25f6c0" + integrity sha512-rl7hlABeTsRYxKiUfpHrQrG4e2obOiTQWfMEH3PxPjOtdsfLQO4ReWSZaQ7DETm4xu07rl4q/h4zcKXyU0/OzQ== dependencies: detect-newline "^3.0.0" -jest-each@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-26.6.2.tgz#02526438a77a67401c8a6382dfe5999952c167cb" - integrity sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A== +jest-each@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-27.5.1.tgz#5bc87016f45ed9507fed6e4702a5b468a5b2c44e" + integrity sha512-1Ff6p+FbhT/bXQnEouYy00bkNSY7OUpfIcmdl8vZ31A1UUaurOLPA8a8BbJOF2RDUElwJhmeaV7LnagI+5UwNQ== dependencies: - "@jest/types" "^26.6.2" + "@jest/types" "^27.5.1" chalk "^4.0.0" - jest-get-type "^26.3.0" - jest-util "^26.6.2" - pretty-format "^26.6.2" + jest-get-type "^27.5.1" + jest-util "^27.5.1" + pretty-format "^27.5.1" -jest-environment-jsdom@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz#78d09fe9cf019a357009b9b7e1f101d23bd1da3e" - integrity sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q== +jest-environment-jsdom@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-27.5.1.tgz#ea9ccd1fc610209655a77898f86b2b559516a546" + integrity sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw== dependencies: - "@jest/environment" "^26.6.2" - "@jest/fake-timers" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/environment" "^27.5.1" + "@jest/fake-timers" "^27.5.1" + "@jest/types" "^27.5.1" "@types/node" "*" - jest-mock "^26.6.2" - jest-util "^26.6.2" - jsdom "^16.4.0" + jest-mock "^27.5.1" + jest-util "^27.5.1" + jsdom "^16.6.0" -jest-environment-node@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-26.6.2.tgz#824e4c7fb4944646356f11ac75b229b0035f2b0c" - integrity sha512-zhtMio3Exty18dy8ee8eJ9kjnRyZC1N4C1Nt/VShN1apyXc8rWGtJ9lI7vqiWcyyXS4BVSEn9lxAM2D+07/Tag== +jest-environment-node@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-27.5.1.tgz#dedc2cfe52fab6b8f5714b4808aefa85357a365e" + integrity sha512-Jt4ZUnxdOsTGwSRAfKEnE6BcwsSPNOijjwifq5sDFSA2kesnXTvNqKHYgM0hDq3549Uf/KzdXNYn4wMZJPlFLw== dependencies: - "@jest/environment" "^26.6.2" - "@jest/fake-timers" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/environment" "^27.5.1" + "@jest/fake-timers" "^27.5.1" + "@jest/types" "^27.5.1" "@types/node" "*" - jest-mock "^26.6.2" - jest-util "^26.6.2" + jest-mock "^27.5.1" + jest-util "^27.5.1" jest-get-type@^26.3.0: version "26.3.0" resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-26.3.0.tgz#e97dc3c3f53c2b406ca7afaed4493b1d099199e0" integrity sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig== -jest-get-type@^27.0.1, jest-get-type@^27.0.6, jest-get-type@^27.5.1: +jest-get-type@^27.0.1, jest-get-type@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.5.1.tgz#3cd613c507b0f7ace013df407a1c1cd578bcb4f1" integrity sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw== @@ -18403,37 +18393,56 @@ jest-haste-map@^26.6.2: optionalDependencies: fsevents "^2.1.2" -jest-jasmine2@^26.6.3: - version "26.6.3" - resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz#adc3cf915deacb5212c93b9f3547cd12958f2edd" - integrity sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg== +jest-haste-map@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-27.5.1.tgz#9fd8bd7e7b4fa502d9c6164c5640512b4e811e7f" + integrity sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng== dependencies: - "@babel/traverse" "^7.1.0" - "@jest/environment" "^26.6.2" - "@jest/source-map" "^26.6.2" - "@jest/test-result" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/types" "^27.5.1" + "@types/graceful-fs" "^4.1.2" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^27.5.1" + jest-serializer "^27.5.1" + jest-util "^27.5.1" + jest-worker "^27.5.1" + micromatch "^4.0.4" + walker "^1.0.7" + optionalDependencies: + fsevents "^2.3.2" + +jest-jasmine2@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-27.5.1.tgz#a037b0034ef49a9f3d71c4375a796f3b230d1ac4" + integrity sha512-jtq7VVyG8SqAorDpApwiJJImd0V2wv1xzdheGHRGyuT7gZm6gG47QEskOlzsN1PG/6WNaCo5pmwMHDf3AkG2pQ== + dependencies: + "@jest/environment" "^27.5.1" + "@jest/source-map" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/types" "^27.5.1" "@types/node" "*" chalk "^4.0.0" co "^4.6.0" - expect "^26.6.2" + expect "^27.5.1" is-generator-fn "^2.0.0" - jest-each "^26.6.2" - jest-matcher-utils "^26.6.2" - jest-message-util "^26.6.2" - jest-runtime "^26.6.3" - jest-snapshot "^26.6.2" - jest-util "^26.6.2" - pretty-format "^26.6.2" - throat "^5.0.0" + jest-each "^27.5.1" + jest-matcher-utils "^27.5.1" + jest-message-util "^27.5.1" + jest-runtime "^27.5.1" + jest-snapshot "^27.5.1" + jest-util "^27.5.1" + pretty-format "^27.5.1" + throat "^6.0.1" -jest-leak-detector@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz#7717cf118b92238f2eba65054c8a0c9c653a91af" - integrity sha512-i4xlXpsVSMeKvg2cEKdfhh0H39qlJlP5Ex1yQxwF9ubahboQYMgTtz5oML35AVA3B4Eu+YsmwaiKVev9KCvLxg== +jest-leak-detector@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-27.5.1.tgz#6ec9d54c3579dd6e3e66d70e3498adf80fde3fb8" + integrity sha512-POXfWAMvfU6WMUXftV4HolnJfnPOGEu10fscNCA76KBpRRhcMN2c8d3iT2pxQS3HLbA+5X4sOUPzYO2NUyIlHQ== dependencies: - jest-get-type "^26.3.0" - pretty-format "^26.6.2" + jest-get-type "^27.5.1" + pretty-format "^27.5.1" jest-matcher-utils@27.0.2: version "27.0.2" @@ -18455,15 +18464,15 @@ jest-matcher-utils@^26.6.2: jest-get-type "^26.3.0" pretty-format "^26.6.2" -jest-matcher-utils@^27.2.0: - version "27.2.0" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-27.2.0.tgz#b4d224ab88655d5fab64b96b989ac349e2f5da43" - integrity sha512-F+LG3iTwJ0gPjxBX6HCyrARFXq6jjiqhwBQeskkJQgSLeF1j6ui1RTV08SR7O51XTUhtc8zqpDj8iCG4RGmdKw== +jest-matcher-utils@^27.0.0, jest-matcher-utils@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz#9c0cdbda8245bc22d2331729d1091308b40cf8ab" + integrity sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw== dependencies: chalk "^4.0.0" - jest-diff "^27.2.0" - jest-get-type "^27.0.6" - pretty-format "^27.2.0" + jest-diff "^27.5.1" + jest-get-type "^27.5.1" + pretty-format "^27.5.1" jest-matcher-utils@^28.1.1: version "28.1.1" @@ -18490,18 +18499,18 @@ jest-message-util@^26.6.2: slash "^3.0.0" stack-utils "^2.0.2" -jest-message-util@^27.2.0: - version "27.2.0" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-27.2.0.tgz#2f65c71df55267208686b1d7514e18106c91ceaf" - integrity sha512-y+sfT/94CiP8rKXgwCOzO1mUazIEdEhrLjuiu+RKmCP+8O/TJTSne9dqQRbFIHBtlR2+q7cddJlWGir8UATu5w== +jest-message-util@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-27.5.1.tgz#bdda72806da10d9ed6425e12afff38cd1458b6cf" + integrity sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g== dependencies: "@babel/code-frame" "^7.12.13" - "@jest/types" "^27.1.1" + "@jest/types" "^27.5.1" "@types/stack-utils" "^2.0.0" chalk "^4.0.0" - graceful-fs "^4.2.4" + graceful-fs "^4.2.9" micromatch "^4.0.4" - pretty-format "^27.2.0" + pretty-format "^27.5.1" slash "^3.0.0" stack-utils "^2.0.3" @@ -18520,12 +18529,12 @@ jest-message-util@^28.1.1: slash "^3.0.0" stack-utils "^2.0.3" -jest-mock@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-26.6.2.tgz#d6cb712b041ed47fe0d9b6fc3474bc6543feb302" - integrity sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew== +jest-mock@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-27.5.1.tgz#19948336d49ef4d9c52021d34ac7b5f36ff967d6" + integrity sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og== dependencies: - "@jest/types" "^26.6.2" + "@jest/types" "^27.5.1" "@types/node" "*" jest-pnp-resolver@^1.2.2: @@ -18543,19 +18552,19 @@ jest-regex-util@^26.0.0: resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-26.0.0.tgz#d25e7184b36e39fd466c3bc41be0971e821fee28" integrity sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A== -jest-regex-util@^27.0.6: - version "27.0.6" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-27.0.6.tgz#02e112082935ae949ce5d13b2675db3d8c87d9c5" - integrity sha512-SUhPzBsGa1IKm8hx2F4NfTGGp+r7BXJ4CulsZ1k2kI+mGLG+lxGrs76veN2LF/aUdGosJBzKgXmNCw+BzFqBDQ== +jest-regex-util@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-27.5.1.tgz#4da143f7e9fd1e542d4aa69617b38e4a78365b95" + integrity sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg== -jest-resolve-dependencies@^26.6.3: - version "26.6.3" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-26.6.3.tgz#6680859ee5d22ee5dcd961fe4871f59f4c784fb6" - integrity sha512-pVwUjJkxbhe4RY8QEWzN3vns2kqyuldKpxlxJlzEYfKSvY6/bMvxoFrYYzUO1Gx28yKWN37qyV7rIoIp2h8fTg== +jest-resolve-dependencies@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-27.5.1.tgz#d811ecc8305e731cc86dd79741ee98fed06f1da8" + integrity sha512-QQOOdY4PE39iawDn5rzbIePNigfe5B9Z91GDD1ae/xNDlu9kaat8QQ5EKnNmVWPV54hUdxCVwwj6YMgR2O7IOg== dependencies: - "@jest/types" "^26.6.2" - jest-regex-util "^26.0.0" - jest-snapshot "^26.6.2" + "@jest/types" "^27.5.1" + jest-regex-util "^27.5.1" + jest-snapshot "^27.5.1" jest-resolve@^26.6.2: version "26.6.2" @@ -18571,64 +18580,76 @@ jest-resolve@^26.6.2: resolve "^1.18.1" slash "^3.0.0" -jest-runner@^26.6.3: - version "26.6.3" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-26.6.3.tgz#2d1fed3d46e10f233fd1dbd3bfaa3fe8924be159" - integrity sha512-atgKpRHnaA2OvByG/HpGA4g6CSPS/1LK0jK3gATJAoptC1ojltpmVlYC3TYgdmGp+GLuhzpH30Gvs36szSL2JQ== +jest-resolve@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-27.5.1.tgz#a2f1c5a0796ec18fe9eb1536ac3814c23617b384" + integrity sha512-FFDy8/9E6CV83IMbDpcjOhumAQPDyETnU2KZ1O98DwTnz8AOBsW/Xv3GySr1mOZdItLR+zDZ7I/UdTFbgSOVCw== dependencies: - "@jest/console" "^26.6.2" - "@jest/environment" "^26.6.2" - "@jest/test-result" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/types" "^27.5.1" + chalk "^4.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^27.5.1" + jest-pnp-resolver "^1.2.2" + jest-util "^27.5.1" + jest-validate "^27.5.1" + resolve "^1.20.0" + resolve.exports "^1.1.0" + slash "^3.0.0" + +jest-runner@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-27.5.1.tgz#071b27c1fa30d90540805c5645a0ec167c7b62e5" + integrity sha512-g4NPsM4mFCOwFKXO4p/H/kWGdJp9V8kURY2lX8Me2drgXqG7rrZAx5kv+5H7wtt/cdFIjhqYx1HrlqWHaOvDaQ== + dependencies: + "@jest/console" "^27.5.1" + "@jest/environment" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" "@types/node" "*" chalk "^4.0.0" - emittery "^0.7.1" - exit "^0.1.2" - graceful-fs "^4.2.4" - jest-config "^26.6.3" - jest-docblock "^26.0.0" - jest-haste-map "^26.6.2" - jest-leak-detector "^26.6.2" - jest-message-util "^26.6.2" - jest-resolve "^26.6.2" - jest-runtime "^26.6.3" - jest-util "^26.6.2" - jest-worker "^26.6.2" + emittery "^0.8.1" + graceful-fs "^4.2.9" + jest-docblock "^27.5.1" + jest-environment-jsdom "^27.5.1" + jest-environment-node "^27.5.1" + jest-haste-map "^27.5.1" + jest-leak-detector "^27.5.1" + jest-message-util "^27.5.1" + jest-resolve "^27.5.1" + jest-runtime "^27.5.1" + jest-util "^27.5.1" + jest-worker "^27.5.1" source-map-support "^0.5.6" - throat "^5.0.0" - -jest-runtime@^26, jest-runtime@^26.6.3: - version "26.6.3" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-26.6.3.tgz#4f64efbcfac398331b74b4b3c82d27d401b8fa2b" - integrity sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw== - dependencies: - "@jest/console" "^26.6.2" - "@jest/environment" "^26.6.2" - "@jest/fake-timers" "^26.6.2" - "@jest/globals" "^26.6.2" - "@jest/source-map" "^26.6.2" - "@jest/test-result" "^26.6.2" - "@jest/transform" "^26.6.2" - "@jest/types" "^26.6.2" - "@types/yargs" "^15.0.0" + throat "^6.0.1" + +jest-runtime@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-27.5.1.tgz#4896003d7a334f7e8e4a53ba93fb9bcd3db0a1af" + integrity sha512-o7gxw3Gf+H2IGt8fv0RiyE1+r83FJBRruoA+FXrlHw6xEyBsU8ugA6IPfTdVyA0w8HClpbK+DGJxH59UrNMx8A== + dependencies: + "@jest/environment" "^27.5.1" + "@jest/fake-timers" "^27.5.1" + "@jest/globals" "^27.5.1" + "@jest/source-map" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" chalk "^4.0.0" - cjs-module-lexer "^0.6.0" + cjs-module-lexer "^1.0.0" collect-v8-coverage "^1.0.0" - exit "^0.1.2" + execa "^5.0.0" glob "^7.1.3" - graceful-fs "^4.2.4" - jest-config "^26.6.3" - jest-haste-map "^26.6.2" - jest-message-util "^26.6.2" - jest-mock "^26.6.2" - jest-regex-util "^26.0.0" - jest-resolve "^26.6.2" - jest-snapshot "^26.6.2" - jest-util "^26.6.2" - jest-validate "^26.6.2" + graceful-fs "^4.2.9" + jest-haste-map "^27.5.1" + jest-message-util "^27.5.1" + jest-mock "^27.5.1" + jest-regex-util "^27.5.1" + jest-resolve "^27.5.1" + jest-snapshot "^27.5.1" + jest-util "^27.5.1" slash "^3.0.0" strip-bom "^4.0.0" - yargs "^15.4.1" jest-serializer@^26.6.2: version "26.6.2" @@ -18638,6 +18659,14 @@ jest-serializer@^26.6.2: "@types/node" "*" graceful-fs "^4.2.4" +jest-serializer@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-27.5.1.tgz#81438410a30ea66fd57ff730835123dea1fb1f64" + integrity sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w== + dependencies: + "@types/node" "*" + graceful-fs "^4.2.9" + jest-silent-reporter@^0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/jest-silent-reporter/-/jest-silent-reporter-0.5.0.tgz#5fd8ccd61665227e3bf19d908b7350719d06ff38" @@ -18646,7 +18675,7 @@ jest-silent-reporter@^0.5.0: chalk "^4.0.0" jest-util "^26.0.0" -jest-snapshot@^26.3.0, jest-snapshot@^26.6.2: +jest-snapshot@^26.3.0: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-26.6.2.tgz#f3b0af1acb223316850bd14e1beea9837fb39c84" integrity sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og== @@ -18668,6 +18697,34 @@ jest-snapshot@^26.3.0, jest-snapshot@^26.6.2: pretty-format "^26.6.2" semver "^7.3.2" +jest-snapshot@^27.0.2, jest-snapshot@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-27.5.1.tgz#b668d50d23d38054a51b42c4039cab59ae6eb6a1" + integrity sha512-yYykXI5a0I31xX67mgeLw1DZ0bJB+gpq5IpSuCAoyDi0+BhgU/RIrL+RTzDmkNTchvDFWKP8lp+w/42Z3us5sA== + dependencies: + "@babel/core" "^7.7.2" + "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/traverse" "^7.7.2" + "@babel/types" "^7.0.0" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/babel__traverse" "^7.0.4" + "@types/prettier" "^2.1.5" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^27.5.1" + graceful-fs "^4.2.9" + jest-diff "^27.5.1" + jest-get-type "^27.5.1" + jest-haste-map "^27.5.1" + jest-matcher-utils "^27.5.1" + jest-message-util "^27.5.1" + jest-util "^27.5.1" + natural-compare "^1.4.0" + pretty-format "^27.5.1" + semver "^7.3.2" + jest-specific-snapshot@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/jest-specific-snapshot/-/jest-specific-snapshot-4.0.0.tgz#a52a2e223e7576e610dbeaf341207c557ac20554" @@ -18675,7 +18732,14 @@ jest-specific-snapshot@^4.0.0: dependencies: jest-snapshot "^26.3.0" -jest-styled-components@^7.0.3: +jest-specific-snapshot@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/jest-specific-snapshot/-/jest-specific-snapshot-5.0.0.tgz#48f72d5613af7f3e30df75b6b3534db6bab32ea0" + integrity sha512-V65vuPxZQExD3tGbv+Du5tbG1E3H3Dq/HFbsCEkPJP27w5vr/nATQJl61Dx5doBfu54OrJak0JaeYVSeZubDKg== + dependencies: + jest-snapshot "^27.0.2" + +jest-styled-components@7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/jest-styled-components/-/jest-styled-components-7.0.3.tgz#cc0b031f910484e68f175568682f3969ff774b2c" integrity sha512-jj9sWyshehUnB0P9WFUaq9Bkh6RKYO8aD8lf3gUrXRwg/MRddTFk7U9D9pC4IAI3v9fbz4vmrMxwaecTpG8NKA== @@ -18694,6 +18758,18 @@ jest-util@^26.0.0, jest-util@^26.6.2: is-ci "^2.0.0" micromatch "^4.0.2" +jest-util@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-27.5.1.tgz#3ba9771e8e31a0b85da48fe0b0891fb86c01c2f9" + integrity sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw== + dependencies: + "@jest/types" "^27.5.1" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + jest-util@^28.1.1: version "28.1.1" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-28.1.1.tgz#ff39e436a1aca397c0ab998db5a51ae2b7080d05" @@ -18706,29 +18782,29 @@ jest-util@^28.1.1: graceful-fs "^4.2.9" picomatch "^2.2.3" -jest-validate@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-26.6.2.tgz#23d380971587150467342911c3d7b4ac57ab20ec" - integrity sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ== +jest-validate@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-27.5.1.tgz#9197d54dc0bdb52260b8db40b46ae668e04df067" + integrity sha512-thkNli0LYTmOI1tDB3FI1S1RTp/Bqyd9pTarJwL87OIBFuqEb5Apv5EaApEudYg4g86e3CT6kM0RowkhtEnCBQ== dependencies: - "@jest/types" "^26.6.2" - camelcase "^6.0.0" + "@jest/types" "^27.5.1" + camelcase "^6.2.0" chalk "^4.0.0" - jest-get-type "^26.3.0" + jest-get-type "^27.5.1" leven "^3.1.0" - pretty-format "^26.6.2" + pretty-format "^27.5.1" -jest-watcher@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-26.6.2.tgz#a5b683b8f9d68dbcb1d7dae32172d2cca0592975" - integrity sha512-WKJob0P/Em2csiVthsI68p6aGKTIcsfjH9Gsx1f0A3Italz43e3ho0geSAVsmj09RWOELP1AZ/DXyJgOgDKxXQ== +jest-watcher@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-27.5.1.tgz#71bd85fb9bde3a2c2ec4dc353437971c43c642a2" + integrity sha512-z676SuD6Z8o8qbmEGhoEUFOM1+jfEiL3DXHK/xgEiG2EyNYfFG60jluWcupY6dATjfEsKQuibReS1djInQnoVw== dependencies: - "@jest/test-result" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/test-result" "^27.5.1" + "@jest/types" "^27.5.1" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" - jest-util "^26.6.2" + jest-util "^27.5.1" string-length "^4.0.1" jest-worker@^26.5.0, jest-worker@^26.6.2: @@ -18740,7 +18816,7 @@ jest-worker@^26.5.0, jest-worker@^26.6.2: merge-stream "^2.0.0" supports-color "^7.0.0" -jest-worker@^27.4.5: +jest-worker@^27.4.5, jest-worker@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== @@ -18749,14 +18825,14 @@ jest-worker@^27.4.5: merge-stream "^2.0.0" supports-color "^8.0.0" -jest@^26.6.3: - version "26.6.3" - resolved "https://registry.yarnpkg.com/jest/-/jest-26.6.3.tgz#40e8fdbe48f00dfa1f0ce8121ca74b88ac9148ef" - integrity sha512-lGS5PXGAzR4RF7V5+XObhqz2KZIDUA1yD0DG6pBVmy10eh0ZIXQImRuzocsI/N2XZ1GrLFwTS27In2i2jlpq1Q== +jest@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest/-/jest-27.5.1.tgz#dadf33ba70a779be7a6fc33015843b51494f63fc" + integrity sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ== dependencies: - "@jest/core" "^26.6.3" + "@jest/core" "^27.5.1" import-local "^3.0.2" - jest-cli "^26.6.3" + jest-cli "^27.5.1" joi@*, joi@^17.3.0, joi@^17.4.0: version "17.4.0" @@ -18855,36 +18931,37 @@ jsbn@~0.1.0: resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= -jsdom@^16.4.0: - version "16.4.0" - resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.4.0.tgz#36005bde2d136f73eee1a830c6d45e55408edddb" - integrity sha512-lYMm3wYdgPhrl7pDcRmvzPhhrGVBeVhPIqeHjzeiHN3DFmD1RBpbExbi8vU7BJdH8VAZYovR8DMt0PNNDM7k8w== +jsdom@^16.4.0, jsdom@^16.6.0: + version "16.7.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710" + integrity sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw== dependencies: - abab "^2.0.3" - acorn "^7.1.1" + abab "^2.0.5" + acorn "^8.2.4" acorn-globals "^6.0.0" cssom "^0.4.4" - cssstyle "^2.2.0" + cssstyle "^2.3.0" data-urls "^2.0.0" - decimal.js "^10.2.0" + decimal.js "^10.2.1" domexception "^2.0.1" - escodegen "^1.14.1" + escodegen "^2.0.0" + form-data "^3.0.0" html-encoding-sniffer "^2.0.1" - is-potential-custom-element-name "^1.0.0" + http-proxy-agent "^4.0.1" + https-proxy-agent "^5.0.0" + is-potential-custom-element-name "^1.0.1" nwsapi "^2.2.0" - parse5 "5.1.1" - request "^2.88.2" - request-promise-native "^1.0.8" - saxes "^5.0.0" + parse5 "6.0.1" + saxes "^5.0.1" symbol-tree "^3.2.4" - tough-cookie "^3.0.1" + tough-cookie "^4.0.0" w3c-hr-time "^1.0.2" w3c-xmlserializer "^2.0.0" webidl-conversions "^6.1.0" whatwg-encoding "^1.0.5" whatwg-mimetype "^2.3.0" - whatwg-url "^8.0.0" - ws "^7.2.3" + whatwg-url "^8.5.0" + ws "^7.4.6" xml-name-validator "^3.0.0" jsesc@^2.5.1: @@ -21130,18 +21207,6 @@ node-libs-browser@^2.2.1: util "^0.11.0" vm-browserify "^1.0.1" -node-notifier@^8.0.0: - version "8.0.1" - resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-8.0.1.tgz#f86e89bbc925f2b068784b31f382afdc6ca56be1" - integrity sha512-BvEXF+UmsnAfYfoapKM9nGxnP+Wn7P91YfXmrKnfcYCx6VBeoN5Ez5Ogck6I8Bi5k4RlpqRYaw75pAwzX9OphA== - dependencies: - growly "^1.3.0" - is-wsl "^2.2.0" - semver "^7.3.2" - shellwords "^0.1.1" - uuid "^8.3.0" - which "^2.0.2" - node-preload@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/node-preload/-/node-preload-0.2.1.tgz#c03043bb327f417a18fee7ab7ee57b408a144301" @@ -21775,11 +21840,6 @@ p-cancelable@^2.0.0: resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.0.0.tgz#4a3740f5bdaf5ed5d7c3e34882c6fb5d6b266a6e" integrity sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg== -p-each-series@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.1.0.tgz#961c8dd3f195ea96c747e636b262b800a6b1af48" - integrity sha512-ZuRs1miPT4HrjFa+9fRfOFXxGJfORgelKV9f9nNOWw2gl6gVsRaVDOQP0+MI0G0wGKns1Yacsu0GjOFbTK0JFQ== - p-event@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/p-event/-/p-event-4.1.0.tgz#e92bb866d7e8e5b732293b1c8269d38e9982bf8e" @@ -22009,7 +22069,7 @@ parse-json@^4.0.0: error-ex "^1.3.1" json-parse-better-errors "^1.0.1" -parse-json@^5.0.0: +parse-json@^5.0.0, parse-json@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== @@ -22036,12 +22096,7 @@ parse5-htmlparser2-tree-adapter@^6.0.1: dependencies: parse5 "^6.0.1" -parse5@5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178" - integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug== - -parse5@^6.0.0, parse5@^6.0.1: +parse5@6.0.1, parse5@^6.0.0, parse5@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== @@ -22274,7 +22329,7 @@ pino@^6.11.2: quick-format-unescaped "^4.0.3" sonic-boom "^1.0.2" -pirates@^4.0.1, pirates@^4.0.5: +pirates@^4.0.1, pirates@^4.0.4, pirates@^4.0.5: version "4.0.5" resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b" integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ== @@ -22872,7 +22927,7 @@ pretty-format@^26.0.0, pretty-format@^26.6.2: ansi-styles "^4.0.0" react-is "^17.0.1" -pretty-format@^27.0.2, pretty-format@^27.2.0, pretty-format@^27.5.1: +pretty-format@^27.0.0, pretty-format@^27.0.2, pretty-format@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== @@ -23590,10 +23645,10 @@ react-grid-layout@^1.3.4: react-draggable "^4.0.0" react-resizable "^3.0.4" -react-hook-form@^7.37.0: - version "7.37.0" - resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.37.0.tgz#4d1738f092d3d8a3ade34ee892d97350b1032b19" - integrity sha512-6NFTxsnw+EXSpNNvLr5nFMjPdYKRryQcelTHg7zwBB6vAzfPIcZq4AExP4heVlwdzntepQgwiOQW4z7Mr99Lsg== +react-hook-form@^7.38.0: + version "7.38.0" + resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.38.0.tgz#53d6a68df587ce4ce88352f63e0ecc7fc8779320" + integrity sha512-gxWW1kMeru9xR1GoR+Iw4hA+JBOM3SHfr4DWCUKY0xc7Vv1MLsF109oHtBeWl9shcyPFx67KHru44DheN0XY5A== react-input-autosize@^3.0.0: version "3.0.0" @@ -24620,23 +24675,7 @@ request-progress@^3.0.0: dependencies: throttleit "^1.0.0" -request-promise-core@1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.4.tgz#3eedd4223208d419867b78ce815167d10593a22f" - integrity sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw== - dependencies: - lodash "^4.17.19" - -request-promise-native@^1.0.8: - version "1.0.9" - resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.9.tgz#e407120526a5efdc9a39b28a5679bf47b9d9dc28" - integrity sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g== - dependencies: - request-promise-core "1.1.4" - stealthy-require "^1.1.1" - tough-cookie "^2.3.3" - -request@^2.44.0, request@^2.88.0, request@^2.88.2: +request@^2.44.0, request@^2.88.0: version "2.88.2" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== @@ -24672,7 +24711,7 @@ require-from-string@^2.0.2: resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== -require-in-the-middle@^5.0.3, require-in-the-middle@^5.2.0: +require-in-the-middle@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/require-in-the-middle/-/require-in-the-middle-5.2.0.tgz#4b71e3cc7f59977100af9beb76bf2d056a5a6de2" integrity sha512-efCx3b+0Z69/LGJmm9Yvi4cqEdxnoGnxYxGxBghkkTTFeXRtTCmmhO0AnAfHz59k957uTSuy8WaHqOs8wbYUWg== @@ -24752,6 +24791,11 @@ resolve-url@^0.2.1: resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= +resolve.exports@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.0.tgz#5ce842b94b05146c0e03076985d1d0e7e48c90c9" + integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ== + resolve@1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" @@ -25081,7 +25125,7 @@ sax@>=0.6.0, sax@^1.2.1: resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== -saxes@^5.0.0: +saxes@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d" integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw== @@ -25438,11 +25482,6 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -shellwords@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" - integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== - side-channel@^1.0.2, side-channel@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" @@ -25703,14 +25742,6 @@ source-map-resolve@^0.5.0, source-map-resolve@^0.5.2: source-map-url "^0.4.0" urix "^0.1.0" -source-map-resolve@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.6.0.tgz#3d9df87e236b53f16d01e58150fc7711138e5ed2" - integrity sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w== - dependencies: - atob "^2.1.2" - decode-uri-component "^0.2.0" - source-map-support@0.5.9: version "0.5.9" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.9.tgz#41bc953b2534267ea2d605bccfa7bfa3111ced5f" @@ -26054,11 +26085,6 @@ stdout-stream@^1.4.0: dependencies: readable-stream "^2.0.1" -stealthy-require@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" - integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks= - store2@^2.12.0: version "2.12.0" resolved "https://registry.yarnpkg.com/store2/-/store2-2.12.0.tgz#e1f1b7e1a59b6083b2596a8d067f6ee88fd4d3cf" @@ -26821,10 +26847,10 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= -throat@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b" - integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA== +throat@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/throat/-/throat-6.0.1.tgz#d514fedad95740c12c2d7fc70ea863eb51ade375" + integrity sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w== throttle-debounce@^2.1.0: version "2.1.0" @@ -27098,24 +27124,7 @@ touch@^3.1.0: dependencies: nopt "~1.0.10" -tough-cookie@^2.3.3, tough-cookie@~2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" - integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== - dependencies: - psl "^1.1.28" - punycode "^2.1.1" - -tough-cookie@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-3.0.1.tgz#9df4f57e739c26930a018184887f4adb7dca73b2" - integrity sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg== - dependencies: - ip-regex "^2.1.0" - psl "^1.1.28" - punycode "^2.1.1" - -tough-cookie@^4.1.2: +tough-cookie@^4.0.0, tough-cookie@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.2.tgz#e53e84b85f24e0b65dd526f46628db6c85f6b874" integrity sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ== @@ -27125,6 +27134,14 @@ tough-cookie@^4.1.2: universalify "^0.2.0" url-parse "^1.5.3" +tough-cookie@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== + dependencies: + psl "^1.1.28" + punycode "^2.1.1" + tr46@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" @@ -28001,10 +28018,10 @@ v8-compile-cache@^2.0.3, v8-compile-cache@^2.3.0: resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== -v8-to-istanbul@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-7.0.0.tgz#b4fe00e35649ef7785a9b7fcebcea05f37c332fc" - integrity sha512-fLL2rFuQpMtm9r8hrAV2apXX/WqHJ6+IC4/eQVdMDGBUgH/YMV4Gv3duk3kjmyg6uiQWBAA9nJwue4iJUOkHeA== +v8-to-istanbul@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-8.1.0.tgz#0aeb763894f1a0a1676adf8a8b7612a38902446c" + integrity sha512-/PRhfd8aTNp9Ggr62HPzXg2XasNFGy5PBt0Rp04du7/8GNNSgxFL6WBTkgMKSL9bFjH+8kKEG3f37FmxiTqUUA== dependencies: "@types/istanbul-lib-coverage" "^2.0.1" convert-source-map "^1.6.0" @@ -28885,7 +28902,7 @@ whatwg-url@^7.0.0: tr46 "^1.0.1" webidl-conversions "^4.0.2" -whatwg-url@^8.0.0: +whatwg-url@^8.0.0, whatwg-url@^8.5.0: version "8.7.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.7.0.tgz#656a78e510ff8f3937bc0bcbe9f5c0ac35941b77" integrity sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg== @@ -29112,17 +29129,12 @@ write-file-atomic@^4.0.1: imurmurhash "^0.1.4" signal-exit "^3.0.7" -ws@8.9.0: +ws@8.9.0, ws@>=8.7.0, ws@^8.2.3, ws@^8.4.2: version "8.9.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.9.0.tgz#2a994bb67144be1b53fe2d23c53c028adeb7f45e" integrity sha512-Ja7nszREasGaYUYCI2k4lCKIRTt+y7XuqVoHR44YpI49TtryyqbqvDMn5eqfW7e6HzTukDRIsXqzVHScqRcafg== -ws@>=8.7.0, ws@^8.2.3, ws@^8.4.2: - version "8.8.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.8.0.tgz#8e71c75e2f6348dbf8d78005107297056cb77769" - integrity sha512-JDAgSYQ1ksuwqfChJusw1LSJ8BizJ2e/vVu5Lxjq3YvNJNlROv1ui4i+c/kUUrPheBvQl4c5UbERhTwKa6QBJQ== - -ws@^7.2.3, ws@^7.3.1, ws@^7.4.6: +ws@^7.3.1, ws@^7.4.6: version "7.5.6" resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.6.tgz#e59fc509fb15ddfb65487ee9765c5a51dec5fe7b" integrity sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA== @@ -29217,10 +29229,10 @@ xtend@~2.1.1: dependencies: object-keys "~0.4.0" -xterm@^4.18.0: - version "4.18.0" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.18.0.tgz#a1f6ab2c330c3918fb094ae5f4c2562987398ea1" - integrity sha512-JQoc1S0dti6SQfI0bK1AZvGnAxH4MVw45ZPFSO6FHTInAiau3Ix77fSxNx3mX4eh9OL4AYa8+4C8f5UvnSfppQ== +xterm@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.0.0.tgz#0af50509b33d0dc62fde7a4ec17750b8e453cc5c" + integrity sha512-tmVsKzZovAYNDIaUinfz+VDclraQpPUnAME+JawosgWRMphInDded/PuY0xmU5dOhyeYZsI0nz5yd8dPYsdLTA== y18n@^3.2.0: version "3.2.2"