Merge upstream/main into branch

This commit is contained in:
Quynh Nguyen 2021-09-15 10:13:57 -05:00
commit 45da099839
774 changed files with 11076 additions and 10500 deletions

View file

@ -21,7 +21,7 @@ snapshots.js
# plugin overrides
/src/core/lib/kbn_internal_native_observable
/src/plugins/data/common/es_query/kuery/ast/_generated_/**
/src/plugins/vis_type_timelion/common/_generated_/**
/src/plugins/vis_types/timelion/common/_generated_/**
/x-pack/plugins/apm/e2e/tmp/*
/x-pack/plugins/canvas/canvas_plugin
/x-pack/plugins/canvas/shareable_runtime/build

4
.github/CODEOWNERS vendored
View file

@ -27,9 +27,9 @@
/src/plugins/kibana_legacy/ @elastic/kibana-vis-editors
/src/plugins/vis_default_editor/ @elastic/kibana-vis-editors
/src/plugins/vis_types/metric/ @elastic/kibana-vis-editors
/src/plugins/vis_type_table/ @elastic/kibana-vis-editors
/src/plugins/vis_types/table/ @elastic/kibana-vis-editors
/src/plugins/vis_types/tagcloud/ @elastic/kibana-vis-editors
/src/plugins/vis_type_timelion/ @elastic/kibana-vis-editors
/src/plugins/vis_types/timelion/ @elastic/kibana-vis-editors
/src/plugins/vis_type_timeseries/ @elastic/kibana-vis-editors
/src/plugins/vis_types/vega/ @elastic/kibana-vis-editors
/src/plugins/vis_types/vislib/ @elastic/kibana-vis-editors

View file

@ -56,12 +56,12 @@
"server": "src/legacy/server",
"statusPage": "src/legacy/core_plugins/status_page",
"telemetry": ["src/plugins/telemetry", "src/plugins/telemetry_management_section"],
"timelion": ["src/plugins/vis_type_timelion"],
"timelion": ["src/plugins/vis_types/timelion"],
"uiActions": "src/plugins/ui_actions",
"visDefaultEditor": "src/plugins/vis_default_editor",
"visTypeMarkdown": "src/plugins/vis_type_markdown",
"visTypeMetric": "src/plugins/vis_types/metric",
"visTypeTable": "src/plugins/vis_type_table",
"visTypeTable": "src/plugins/vis_types/table",
"visTypeTagCloud": "src/plugins/vis_types/tagcloud",
"visTypeTimeseries": "src/plugins/vis_type_timeseries",
"visTypeVega": "src/plugins/vis_types/vega",

View file

@ -753,19 +753,19 @@
"references": [
{
"plugin": "visTypeTable",
"path": "src/plugins/vis_type_table/public/plugin.ts"
"path": "src/plugins/vis_types/table/public/plugin.ts"
},
{
"plugin": "visTypeTable",
"path": "src/plugins/vis_type_table/public/plugin.ts"
"path": "src/plugins/vis_types/table/public/plugin.ts"
},
{
"plugin": "visTypeTable",
"path": "src/plugins/vis_type_table/target/types/public/plugin.d.ts"
"path": "src/plugins/vis_types/table/target/types/public/plugin.d.ts"
},
{
"plugin": "visTypeTable",
"path": "src/plugins/vis_type_table/target/types/public/plugin.d.ts"
"path": "src/plugins/vis_types/table/target/types/public/plugin.d.ts"
}
],
"children": [

View file

@ -5004,11 +5004,11 @@
},
{
"plugin": "visTypeTable",
"path": "src/plugins/vis_type_table/public/legacy/table_vis_controller.test.ts"
"path": "src/plugins/vis_types/table/public/legacy/table_vis_controller.test.ts"
},
{
"plugin": "visTypeTable",
"path": "src/plugins/vis_type_table/public/legacy/table_vis_controller.test.ts"
"path": "src/plugins/vis_types/table/public/legacy/table_vis_controller.test.ts"
},
{
"plugin": "visTypeVega",
@ -16631,11 +16631,11 @@
},
{
"plugin": "visTypeTimelion",
"path": "src/plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts"
"path": "src/plugins/vis_types/timelion/public/helpers/timelion_request_handler.ts"
},
{
"plugin": "visTypeTimelion",
"path": "src/plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts"
"path": "src/plugins/vis_types/timelion/public/helpers/timelion_request_handler.ts"
},
{
"plugin": "visualize",
@ -16671,11 +16671,11 @@
},
{
"plugin": "visTypeTimelion",
"path": "src/plugins/vis_type_timelion/public/timelion_vis_fn.ts"
"path": "src/plugins/vis_types/timelion/public/timelion_vis_fn.ts"
},
{
"plugin": "visTypeTimelion",
"path": "src/plugins/vis_type_timelion/public/timelion_vis_fn.ts"
"path": "src/plugins/vis_types/timelion/public/timelion_vis_fn.ts"
},
{
"plugin": "dashboard",
@ -18119,11 +18119,11 @@
},
{
"plugin": "visTypeTimelion",
"path": "src/plugins/vis_type_timelion/public/helpers/plugin_services.ts"
"path": "src/plugins/vis_types/timelion/public/helpers/plugin_services.ts"
},
{
"plugin": "visTypeTimelion",
"path": "src/plugins/vis_type_timelion/public/helpers/plugin_services.ts"
"path": "src/plugins/vis_types/timelion/public/helpers/plugin_services.ts"
},
{
"plugin": "indexPatternManagement",
@ -18135,11 +18135,11 @@
},
{
"plugin": "visTypeTimelion",
"path": "src/plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.test.ts"
"path": "src/plugins/vis_types/timelion/public/components/timelion_expression_input_helpers.test.ts"
},
{
"plugin": "visTypeTimelion",
"path": "src/plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.test.ts"
"path": "src/plugins/vis_types/timelion/public/components/timelion_expression_input_helpers.test.ts"
},
{
"plugin": "discover",
@ -19008,27 +19008,27 @@
"references": [
{
"plugin": "visTypeTimelion",
"path": "src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx"
"path": "src/plugins/vis_types/timelion/public/components/timelion_vis_component.tsx"
},
{
"plugin": "visTypeTimelion",
"path": "src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx"
"path": "src/plugins/vis_types/timelion/public/components/timelion_vis_component.tsx"
},
{
"plugin": "visTypeTimelion",
"path": "src/plugins/vis_type_timelion/public/timelion_vis_renderer.tsx"
"path": "src/plugins/vis_types/timelion/public/timelion_vis_renderer.tsx"
},
{
"plugin": "visTypeTimelion",
"path": "src/plugins/vis_type_timelion/public/timelion_vis_renderer.tsx"
"path": "src/plugins/vis_types/timelion/public/timelion_vis_renderer.tsx"
},
{
"plugin": "visTypeTimelion",
"path": "src/plugins/vis_type_timelion/public/legacy/timelion_vis_component.tsx"
"path": "src/plugins/vis_types/timelion/public/legacy/timelion_vis_component.tsx"
},
{
"plugin": "visTypeTimelion",
"path": "src/plugins/vis_type_timelion/public/legacy/timelion_vis_component.tsx"
"path": "src/plugins/vis_types/timelion/public/legacy/timelion_vis_component.tsx"
}
],
"initialIsOpen": false
@ -21706,15 +21706,15 @@
},
{
"plugin": "visTypeTimelion",
"path": "src/plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts"
"path": "src/plugins/vis_types/timelion/public/helpers/timelion_request_handler.ts"
},
{
"plugin": "visTypeTimelion",
"path": "src/plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts"
"path": "src/plugins/vis_types/timelion/public/helpers/timelion_request_handler.ts"
},
{
"plugin": "visTypeTimelion",
"path": "src/plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts"
"path": "src/plugins/vis_types/timelion/public/helpers/timelion_request_handler.ts"
},
{
"plugin": "visTypeVega",
@ -24261,7 +24261,7 @@
},
{
"plugin": "visTypeTimelion",
"path": "src/plugins/vis_type_timelion/public/plugin.ts"
"path": "src/plugins/vis_types/timelion/public/plugin.ts"
},
{
"plugin": "visTypeTimeseries",
@ -24479,7 +24479,7 @@
},
{
"plugin": "visTypeTable",
"path": "src/plugins/vis_type_table/public/plugin.ts"
"path": "src/plugins/vis_types/table/public/plugin.ts"
},
{
"plugin": "visTypeTimeseries",
@ -27306,11 +27306,11 @@
},
{
"plugin": "visTypeTable",
"path": "src/plugins/vis_type_table/public/legacy/table_vis_controller.test.ts"
"path": "src/plugins/vis_types/table/public/legacy/table_vis_controller.test.ts"
},
{
"plugin": "visTypeTable",
"path": "src/plugins/vis_type_table/public/legacy/table_vis_controller.test.ts"
"path": "src/plugins/vis_types/table/public/legacy/table_vis_controller.test.ts"
},
{
"plugin": "visTypeVega",
@ -32208,11 +32208,11 @@
},
{
"plugin": "visTypeTimelion",
"path": "src/plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts"
"path": "src/plugins/vis_types/timelion/public/helpers/timelion_request_handler.ts"
},
{
"plugin": "visTypeTimelion",
"path": "src/plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts"
"path": "src/plugins/vis_types/timelion/public/helpers/timelion_request_handler.ts"
},
{
"plugin": "visualize",
@ -32248,11 +32248,11 @@
},
{
"plugin": "visTypeTimelion",
"path": "src/plugins/vis_type_timelion/public/timelion_vis_fn.ts"
"path": "src/plugins/vis_types/timelion/public/timelion_vis_fn.ts"
},
{
"plugin": "visTypeTimelion",
"path": "src/plugins/vis_type_timelion/public/timelion_vis_fn.ts"
"path": "src/plugins/vis_types/timelion/public/timelion_vis_fn.ts"
},
{
"plugin": "dashboard",
@ -38924,11 +38924,11 @@
},
{
"plugin": "visTypeTimelion",
"path": "src/plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts"
"path": "src/plugins/vis_types/timelion/public/helpers/timelion_request_handler.ts"
},
{
"plugin": "visTypeTimelion",
"path": "src/plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts"
"path": "src/plugins/vis_types/timelion/public/helpers/timelion_request_handler.ts"
},
{
"plugin": "visualize",
@ -38964,11 +38964,11 @@
},
{
"plugin": "visTypeTimelion",
"path": "src/plugins/vis_type_timelion/public/timelion_vis_fn.ts"
"path": "src/plugins/vis_types/timelion/public/timelion_vis_fn.ts"
},
{
"plugin": "visTypeTimelion",
"path": "src/plugins/vis_type_timelion/public/timelion_vis_fn.ts"
"path": "src/plugins/vis_types/timelion/public/timelion_vis_fn.ts"
},
{
"plugin": "dashboard",
@ -39892,27 +39892,27 @@
"references": [
{
"plugin": "visTypeTimelion",
"path": "src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx"
"path": "src/plugins/vis_types/timelion/public/components/timelion_vis_component.tsx"
},
{
"plugin": "visTypeTimelion",
"path": "src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx"
"path": "src/plugins/vis_types/timelion/public/components/timelion_vis_component.tsx"
},
{
"plugin": "visTypeTimelion",
"path": "src/plugins/vis_type_timelion/public/timelion_vis_renderer.tsx"
"path": "src/plugins/vis_types/timelion/public/timelion_vis_renderer.tsx"
},
{
"plugin": "visTypeTimelion",
"path": "src/plugins/vis_type_timelion/public/timelion_vis_renderer.tsx"
"path": "src/plugins/vis_types/timelion/public/timelion_vis_renderer.tsx"
},
{
"plugin": "visTypeTimelion",
"path": "src/plugins/vis_type_timelion/public/legacy/timelion_vis_component.tsx"
"path": "src/plugins/vis_types/timelion/public/legacy/timelion_vis_component.tsx"
},
{
"plugin": "visTypeTimelion",
"path": "src/plugins/vis_type_timelion/public/legacy/timelion_vis_component.tsx"
"path": "src/plugins/vis_types/timelion/public/legacy/timelion_vis_component.tsx"
}
],
"initialIsOpen": false

View file

@ -5927,11 +5927,11 @@
},
{
"plugin": "visTypeTable",
"path": "src/plugins/vis_type_table/public/legacy/table_vis_controller.test.ts"
"path": "src/plugins/vis_types/table/public/legacy/table_vis_controller.test.ts"
},
{
"plugin": "visTypeTable",
"path": "src/plugins/vis_type_table/public/legacy/table_vis_controller.test.ts"
"path": "src/plugins/vis_types/table/public/legacy/table_vis_controller.test.ts"
},
{
"plugin": "visTypeVega",
@ -13270,11 +13270,11 @@
},
{
"plugin": "visTypeTimelion",
"path": "src/plugins/vis_type_timelion/public/helpers/plugin_services.ts"
"path": "src/plugins/vis_types/timelion/public/helpers/plugin_services.ts"
},
{
"plugin": "visTypeTimelion",
"path": "src/plugins/vis_type_timelion/public/helpers/plugin_services.ts"
"path": "src/plugins/vis_types/timelion/public/helpers/plugin_services.ts"
},
{
"plugin": "indexPatternManagement",
@ -13286,11 +13286,11 @@
},
{
"plugin": "visTypeTimelion",
"path": "src/plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.test.ts"
"path": "src/plugins/vis_types/timelion/public/components/timelion_expression_input_helpers.test.ts"
},
{
"plugin": "visTypeTimelion",
"path": "src/plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.test.ts"
"path": "src/plugins/vis_types/timelion/public/components/timelion_expression_input_helpers.test.ts"
},
{
"plugin": "discover",

View file

@ -898,11 +898,11 @@ warning: This document is auto-generated and is meant to be viewed inside our ex
| Deprecated API | Reference location(s) | Remove By |
| ---------------|-----------|-----------|
| <DocLink id="kibDataPluginApi" section="def-public.IndexPattern" text="IndexPattern"/> | [table_vis_controller.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_type_table/public/legacy/table_vis_controller.test.ts#:~:text=IndexPattern), [table_vis_controller.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_type_table/public/legacy/table_vis_controller.test.ts#:~:text=IndexPattern) | - |
| <DocLink id="kibDataPluginApi" section="def-public.DataPublicPluginStart.fieldFormats" text="fieldFormats"/> | [plugin.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_type_table/public/plugin.ts#:~:text=fieldFormats) | - |
| <DocLink id="kibDataPluginApi" section="def-common.IndexPattern" text="IndexPattern"/> | [table_vis_controller.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_type_table/public/legacy/table_vis_controller.test.ts#:~:text=IndexPattern), [table_vis_controller.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_type_table/public/legacy/table_vis_controller.test.ts#:~:text=IndexPattern) | - |
| <DocLink id="kibDataPluginApi" section="def-server.IndexPattern" text="IndexPattern"/> | [table_vis_controller.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_type_table/public/legacy/table_vis_controller.test.ts#:~:text=IndexPattern), [table_vis_controller.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_type_table/public/legacy/table_vis_controller.test.ts#:~:text=IndexPattern) | - |
| <DocLink id="kibCorePluginApi" section="def-public.AsyncPlugin" text="AsyncPlugin"/> | [plugin.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_type_table/public/plugin.ts#:~:text=AsyncPlugin), [plugin.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_type_table/public/plugin.ts#:~:text=AsyncPlugin), [plugin.d.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_type_table/target/types/public/plugin.d.ts#:~:text=AsyncPlugin), [plugin.d.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_type_table/target/types/public/plugin.d.ts#:~:text=AsyncPlugin) | - |
| <DocLink id="kibDataPluginApi" section="def-public.IndexPattern" text="IndexPattern"/> | [table_vis_controller.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/table/public/legacy/table_vis_controller.test.ts#:~:text=IndexPattern), [table_vis_controller.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/table/public/legacy/table_vis_controller.test.ts#:~:text=IndexPattern) | - |
| <DocLink id="kibDataPluginApi" section="def-public.DataPublicPluginStart.fieldFormats" text="fieldFormats"/> | [plugin.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/table/public/plugin.ts#:~:text=fieldFormats) | - |
| <DocLink id="kibDataPluginApi" section="def-common.IndexPattern" text="IndexPattern"/> | [table_vis_controller.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/table/public/legacy/table_vis_controller.test.ts#:~:text=IndexPattern), [table_vis_controller.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/table/public/legacy/table_vis_controller.test.ts#:~:text=IndexPattern) | - |
| <DocLink id="kibDataPluginApi" section="def-server.IndexPattern" text="IndexPattern"/> | [table_vis_controller.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/table/public/legacy/table_vis_controller.test.ts#:~:text=IndexPattern), [table_vis_controller.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/table/public/legacy/table_vis_controller.test.ts#:~:text=IndexPattern) | - |
| <DocLink id="kibCorePluginApi" section="def-public.AsyncPlugin" text="AsyncPlugin"/> | [plugin.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/table/public/plugin.ts#:~:text=AsyncPlugin), [plugin.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/table/public/plugin.ts#:~:text=AsyncPlugin), [plugin.d.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/table/target/types/public/plugin.d.ts#:~:text=AsyncPlugin), [plugin.d.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/table/target/types/public/plugin.d.ts#:~:text=AsyncPlugin) | - |
@ -910,15 +910,15 @@ warning: This document is auto-generated and is meant to be viewed inside our ex
| Deprecated API | Reference location(s) | Remove By |
| ---------------|-----------|-----------|
| <DocLink id="kibDataPluginApi" section="def-public.IndexPatternsContract" text="IndexPatternsContract"/> | [plugin_services.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_type_timelion/public/helpers/plugin_services.ts#:~:text=IndexPatternsContract), [plugin_services.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_type_timelion/public/helpers/plugin_services.ts#:~:text=IndexPatternsContract), [timelion_expression_input_helpers.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.test.ts#:~:text=IndexPatternsContract), [timelion_expression_input_helpers.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.test.ts#:~:text=IndexPatternsContract) | - |
| <DocLink id="kibDataPluginApi" section="def-public.DataPublicPluginStart.indexPatterns" text="indexPatterns"/> | [plugin.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_type_timelion/public/plugin.ts#:~:text=indexPatterns) | - |
| <DocLink id="kibDataPluginApi" section="def-public.RangeFilterParams" text="RangeFilterParams"/> | [timelion_vis_component.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx#:~:text=RangeFilterParams), [timelion_vis_component.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx#:~:text=RangeFilterParams), [timelion_vis_renderer.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/vis_type_timelion/public/timelion_vis_renderer.tsx#:~:text=RangeFilterParams), [timelion_vis_renderer.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/vis_type_timelion/public/timelion_vis_renderer.tsx#:~:text=RangeFilterParams), [timelion_vis_component.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/vis_type_timelion/public/legacy/timelion_vis_component.tsx#:~:text=RangeFilterParams), [timelion_vis_component.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/vis_type_timelion/public/legacy/timelion_vis_component.tsx#:~:text=RangeFilterParams) | 8.1 |
| <DocLink id="kibDataPluginApi" section="def-public.esQuery" text="esQuery"/> | [timelion_request_handler.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts#:~:text=esQuery), [timelion_request_handler.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts#:~:text=esQuery), [timelion_request_handler.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts#:~:text=esQuery) | 8.1 |
| <DocLink id="kibDataPluginApi" section="def-public.Filter" text="Filter"/> | [timelion_request_handler.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts#:~:text=Filter), [timelion_request_handler.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts#:~:text=Filter), [timelion_vis_fn.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_type_timelion/public/timelion_vis_fn.ts#:~:text=Filter), [timelion_vis_fn.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_type_timelion/public/timelion_vis_fn.ts#:~:text=Filter) | 8.1 |
| <DocLink id="kibDataPluginApi" section="def-common.Filter" text="Filter"/> | [timelion_request_handler.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts#:~:text=Filter), [timelion_request_handler.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts#:~:text=Filter), [timelion_vis_fn.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_type_timelion/public/timelion_vis_fn.ts#:~:text=Filter), [timelion_vis_fn.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_type_timelion/public/timelion_vis_fn.ts#:~:text=Filter) | 8.1 |
| <DocLink id="kibDataPluginApi" section="def-common.RangeFilterParams" text="RangeFilterParams"/> | [timelion_vis_component.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx#:~:text=RangeFilterParams), [timelion_vis_component.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx#:~:text=RangeFilterParams), [timelion_vis_renderer.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/vis_type_timelion/public/timelion_vis_renderer.tsx#:~:text=RangeFilterParams), [timelion_vis_renderer.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/vis_type_timelion/public/timelion_vis_renderer.tsx#:~:text=RangeFilterParams), [timelion_vis_component.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/vis_type_timelion/public/legacy/timelion_vis_component.tsx#:~:text=RangeFilterParams), [timelion_vis_component.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/vis_type_timelion/public/legacy/timelion_vis_component.tsx#:~:text=RangeFilterParams) | 8.1 |
| <DocLink id="kibDataPluginApi" section="def-common.IndexPatternsContract" text="IndexPatternsContract"/> | [plugin_services.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_type_timelion/public/helpers/plugin_services.ts#:~:text=IndexPatternsContract), [plugin_services.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_type_timelion/public/helpers/plugin_services.ts#:~:text=IndexPatternsContract), [timelion_expression_input_helpers.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.test.ts#:~:text=IndexPatternsContract), [timelion_expression_input_helpers.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.test.ts#:~:text=IndexPatternsContract) | - |
| <DocLink id="kibDataPluginApi" section="def-server.Filter" text="Filter"/> | [timelion_request_handler.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts#:~:text=Filter), [timelion_request_handler.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts#:~:text=Filter), [timelion_vis_fn.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_type_timelion/public/timelion_vis_fn.ts#:~:text=Filter), [timelion_vis_fn.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_type_timelion/public/timelion_vis_fn.ts#:~:text=Filter) | 8.1 |
| <DocLink id="kibDataPluginApi" section="def-public.IndexPatternsContract" text="IndexPatternsContract"/> | [plugin_services.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/timelion/public/helpers/plugin_services.ts#:~:text=IndexPatternsContract), [plugin_services.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/timelion/public/helpers/plugin_services.ts#:~:text=IndexPatternsContract), [timelion_expression_input_helpers.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/timelion/public/components/timelion_expression_input_helpers.test.ts#:~:text=IndexPatternsContract), [timelion_expression_input_helpers.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/timelion/public/components/timelion_expression_input_helpers.test.ts#:~:text=IndexPatternsContract) | - |
| <DocLink id="kibDataPluginApi" section="def-public.DataPublicPluginStart.indexPatterns" text="indexPatterns"/> | [plugin.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/timelion/public/plugin.ts#:~:text=indexPatterns) | - |
| <DocLink id="kibDataPluginApi" section="def-public.RangeFilterParams" text="RangeFilterParams"/> | [timelion_vis_component.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/timelion/public/components/timelion_vis_component.tsx#:~:text=RangeFilterParams), [timelion_vis_component.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/timelion/public/components/timelion_vis_component.tsx#:~:text=RangeFilterParams), [timelion_vis_renderer.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/timelion/public/timelion_vis_renderer.tsx#:~:text=RangeFilterParams), [timelion_vis_renderer.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/timelion/public/timelion_vis_renderer.tsx#:~:text=RangeFilterParams), [timelion_vis_component.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/timelion/public/legacy/timelion_vis_component.tsx#:~:text=RangeFilterParams), [timelion_vis_component.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/timelion/public/legacy/timelion_vis_component.tsx#:~:text=RangeFilterParams) | 8.1 |
| <DocLink id="kibDataPluginApi" section="def-public.esQuery" text="esQuery"/> | [timelion_request_handler.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/timelion/public/helpers/timelion_request_handler.ts#:~:text=esQuery), [timelion_request_handler.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/timelion/public/helpers/timelion_request_handler.ts#:~:text=esQuery), [timelion_request_handler.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/timelion/public/helpers/timelion_request_handler.ts#:~:text=esQuery) | 8.1 |
| <DocLink id="kibDataPluginApi" section="def-public.Filter" text="Filter"/> | [timelion_request_handler.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/timelion/public/helpers/timelion_request_handler.ts#:~:text=Filter), [timelion_request_handler.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/timelion/public/helpers/timelion_request_handler.ts#:~:text=Filter), [timelion_vis_fn.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/timelion/public/timelion_vis_fn.ts#:~:text=Filter), [timelion_vis_fn.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/timelion/public/timelion_vis_fn.ts#:~:text=Filter) | 8.1 |
| <DocLink id="kibDataPluginApi" section="def-common.Filter" text="Filter"/> | [timelion_request_handler.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/timelion/public/helpers/timelion_request_handler.ts#:~:text=Filter), [timelion_request_handler.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/timelion/public/helpers/timelion_request_handler.ts#:~:text=Filter), [timelion_vis_fn.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/timelion/public/timelion_vis_fn.ts#:~:text=Filter), [timelion_vis_fn.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/timelion/public/timelion_vis_fn.ts#:~:text=Filter) | 8.1 |
| <DocLink id="kibDataPluginApi" section="def-common.RangeFilterParams" text="RangeFilterParams"/> | [timelion_vis_component.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/timelion/public/components/timelion_vis_component.tsx#:~:text=RangeFilterParams), [timelion_vis_component.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/timelion/public/components/timelion_vis_component.tsx#:~:text=RangeFilterParams), [timelion_vis_renderer.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/timelion/public/timelion_vis_renderer.tsx#:~:text=RangeFilterParams), [timelion_vis_renderer.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/timelion/public/timelion_vis_renderer.tsx#:~:text=RangeFilterParams), [timelion_vis_component.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/timelion/public/legacy/timelion_vis_component.tsx#:~:text=RangeFilterParams), [timelion_vis_component.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/timelion/public/legacy/timelion_vis_component.tsx#:~:text=RangeFilterParams) | 8.1 |
| <DocLink id="kibDataPluginApi" section="def-common.IndexPatternsContract" text="IndexPatternsContract"/> | [plugin_services.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/timelion/public/helpers/plugin_services.ts#:~:text=IndexPatternsContract), [plugin_services.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/timelion/public/helpers/plugin_services.ts#:~:text=IndexPatternsContract), [timelion_expression_input_helpers.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/timelion/public/components/timelion_expression_input_helpers.test.ts#:~:text=IndexPatternsContract), [timelion_expression_input_helpers.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/timelion/public/components/timelion_expression_input_helpers.test.ts#:~:text=IndexPatternsContract) | - |
| <DocLink id="kibDataPluginApi" section="def-server.Filter" text="Filter"/> | [timelion_request_handler.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/timelion/public/helpers/timelion_request_handler.ts#:~:text=Filter), [timelion_request_handler.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/timelion/public/helpers/timelion_request_handler.ts#:~:text=Filter), [timelion_vis_fn.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/timelion/public/timelion_vis_fn.ts#:~:text=Filter), [timelion_vis_fn.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/timelion/public/timelion_vis_fn.ts#:~:text=Filter) | 8.1 |

View file

@ -27,7 +27,7 @@
"tags": [],
"label": "TableVisParams",
"description": [],
"path": "src/plugins/vis_type_table/common/types.ts",
"path": "src/plugins/vis_types/table/common/types.ts",
"deprecated": false,
"children": [
{
@ -40,7 +40,7 @@
"signature": [
"number | \"\""
],
"path": "src/plugins/vis_type_table/common/types.ts",
"path": "src/plugins/vis_types/table/common/types.ts",
"deprecated": false
},
{
@ -50,7 +50,7 @@
"tags": [],
"label": "showPartialRows",
"description": [],
"path": "src/plugins/vis_type_table/common/types.ts",
"path": "src/plugins/vis_types/table/common/types.ts",
"deprecated": false
},
{
@ -60,7 +60,7 @@
"tags": [],
"label": "showMetricsAtAllLevels",
"description": [],
"path": "src/plugins/vis_type_table/common/types.ts",
"path": "src/plugins/vis_types/table/common/types.ts",
"deprecated": false
},
{
@ -70,7 +70,7 @@
"tags": [],
"label": "showToolbar",
"description": [],
"path": "src/plugins/vis_type_table/common/types.ts",
"path": "src/plugins/vis_types/table/common/types.ts",
"deprecated": false
},
{
@ -80,7 +80,7 @@
"tags": [],
"label": "showTotal",
"description": [],
"path": "src/plugins/vis_type_table/common/types.ts",
"path": "src/plugins/vis_types/table/common/types.ts",
"deprecated": false
},
{
@ -99,7 +99,7 @@
"text": "AggTypes"
}
],
"path": "src/plugins/vis_type_table/common/types.ts",
"path": "src/plugins/vis_types/table/common/types.ts",
"deprecated": false
},
{
@ -109,7 +109,7 @@
"tags": [],
"label": "percentageCol",
"description": [],
"path": "src/plugins/vis_type_table/common/types.ts",
"path": "src/plugins/vis_types/table/common/types.ts",
"deprecated": false
},
{
@ -122,7 +122,7 @@
"signature": [
"boolean | undefined"
],
"path": "src/plugins/vis_type_table/common/types.ts",
"path": "src/plugins/vis_types/table/common/types.ts",
"deprecated": false
}
],
@ -137,7 +137,7 @@
"tags": [],
"label": "AggTypes",
"description": [],
"path": "src/plugins/vis_type_table/common/types.ts",
"path": "src/plugins/vis_types/table/common/types.ts",
"deprecated": false,
"initialIsOpen": false
}
@ -153,7 +153,7 @@
"signature": [
"\"table\""
],
"path": "src/plugins/vis_type_table/common/types.ts",
"path": "src/plugins/vis_types/table/common/types.ts",
"deprecated": false,
"initialIsOpen": false
}

View file

@ -14,7 +14,7 @@
"tags": [],
"label": "_LEGACY_",
"description": [],
"path": "src/plugins/vis_type_timelion/public/index.ts",
"path": "src/plugins/vis_types/timelion/public/index.ts",
"deprecated": false,
"children": [
{
@ -24,7 +24,7 @@
"tags": [],
"label": "DEFAULT_TIME_FORMAT",
"description": [],
"path": "src/plugins/vis_type_timelion/public/index.ts",
"path": "src/plugins/vis_types/timelion/public/index.ts",
"deprecated": false
},
{
@ -37,7 +37,7 @@
"signature": [
"(from: number, to: number, size: number, interval: string, min: string) => string"
],
"path": "src/plugins/vis_type_timelion/public/index.ts",
"path": "src/plugins/vis_types/timelion/public/index.ts",
"deprecated": false,
"returnComment": [],
"children": [
@ -48,7 +48,7 @@
"tags": [],
"label": "from",
"description": [],
"path": "src/plugins/vis_type_timelion/common/lib/calculate_interval.ts",
"path": "src/plugins/vis_types/timelion/common/lib/calculate_interval.ts",
"deprecated": false
},
{
@ -58,7 +58,7 @@
"tags": [],
"label": "to",
"description": [],
"path": "src/plugins/vis_type_timelion/common/lib/calculate_interval.ts",
"path": "src/plugins/vis_types/timelion/common/lib/calculate_interval.ts",
"deprecated": false
},
{
@ -68,7 +68,7 @@
"tags": [],
"label": "size",
"description": [],
"path": "src/plugins/vis_type_timelion/common/lib/calculate_interval.ts",
"path": "src/plugins/vis_types/timelion/common/lib/calculate_interval.ts",
"deprecated": false
},
{
@ -78,7 +78,7 @@
"tags": [],
"label": "interval",
"description": [],
"path": "src/plugins/vis_type_timelion/common/lib/calculate_interval.ts",
"path": "src/plugins/vis_types/timelion/common/lib/calculate_interval.ts",
"deprecated": false
},
{
@ -88,7 +88,7 @@
"tags": [],
"label": "min",
"description": [],
"path": "src/plugins/vis_type_timelion/common/lib/calculate_interval.ts",
"path": "src/plugins/vis_types/timelion/common/lib/calculate_interval.ts",
"deprecated": false
}
]
@ -105,7 +105,7 @@
"ParsedExpression",
">"
],
"path": "src/plugins/vis_type_timelion/public/index.ts",
"path": "src/plugins/vis_types/timelion/public/index.ts",
"deprecated": false,
"returnComment": [],
"children": [
@ -116,7 +116,7 @@
"tags": [],
"label": "input",
"description": [],
"path": "src/plugins/vis_type_timelion/common/parser_async.ts",
"path": "src/plugins/vis_types/timelion/common/parser_async.ts",
"deprecated": false
}
]
@ -137,7 +137,7 @@
"LegacyAxis",
"): string; }"
],
"path": "src/plugins/vis_type_timelion/public/index.ts",
"path": "src/plugins/vis_types/timelion/public/index.ts",
"deprecated": false,
"returnComment": [],
"children": []
@ -160,7 +160,7 @@
},
") => string"
],
"path": "src/plugins/vis_type_timelion/public/index.ts",
"path": "src/plugins/vis_types/timelion/public/index.ts",
"deprecated": false,
"returnComment": [],
"children": [
@ -180,7 +180,7 @@
"text": "IUiSettingsClient"
}
],
"path": "src/plugins/vis_type_timelion/public/helpers/get_timezone.ts",
"path": "src/plugins/vis_types/timelion/public/helpers/get_timezone.ts",
"deprecated": false
}
]
@ -203,7 +203,7 @@
},
") => (esInterval: any) => any"
],
"path": "src/plugins/vis_type_timelion/public/index.ts",
"path": "src/plugins/vis_types/timelion/public/index.ts",
"deprecated": false,
"returnComment": [],
"children": [
@ -223,7 +223,7 @@
"text": "IUiSettingsClient"
}
],
"path": "src/plugins/vis_type_timelion/public/helpers/xaxis_formatter.ts",
"path": "src/plugins/vis_types/timelion/public/helpers/xaxis_formatter.ts",
"deprecated": false
}
]
@ -240,7 +240,7 @@
"IAxis",
") => number[]"
],
"path": "src/plugins/vis_type_timelion/public/index.ts",
"path": "src/plugins/vis_types/timelion/public/index.ts",
"deprecated": false,
"returnComment": [],
"children": []
@ -256,7 +256,7 @@
"tags": [],
"label": "VisTypeTimelionPluginStart",
"description": [],
"path": "src/plugins/vis_type_timelion/public/plugin.ts",
"path": "src/plugins/vis_types/timelion/public/plugin.ts",
"deprecated": false,
"children": [
{
@ -275,7 +275,7 @@
"TimelionFunctionArgsSuggestion",
"[]; }"
],
"path": "src/plugins/vis_type_timelion/public/plugin.ts",
"path": "src/plugins/vis_types/timelion/public/plugin.ts",
"deprecated": false,
"returnComment": [],
"children": []
@ -291,7 +291,7 @@
"tags": [],
"label": "VisTypeTimelionPluginSetup",
"description": [],
"path": "src/plugins/vis_type_timelion/public/plugin.ts",
"path": "src/plugins/vis_types/timelion/public/plugin.ts",
"deprecated": false,
"children": [
{
@ -301,7 +301,7 @@
"tags": [],
"label": "isUiEnabled",
"description": [],
"path": "src/plugins/vis_type_timelion/public/plugin.ts",
"path": "src/plugins/vis_types/timelion/public/plugin.ts",
"deprecated": false
}
],
@ -322,7 +322,7 @@
"description": [
"\nDescribes public Timelion plugin contract returned at the `setup` stage."
],
"path": "src/plugins/vis_type_timelion/server/plugin.ts",
"path": "src/plugins/vis_types/timelion/server/plugin.ts",
"deprecated": false,
"children": [
{
@ -332,7 +332,7 @@
"tags": [],
"label": "uiEnabled",
"description": [],
"path": "src/plugins/vis_type_timelion/server/plugin.ts",
"path": "src/plugins/vis_types/timelion/server/plugin.ts",
"deprecated": false
}
],

View file

@ -285,7 +285,7 @@ The plugin exposes the static DefaultEditorController class to consume.
|WARNING: Missing README.
|{kib-repo}blob/{branch}/src/plugins/vis_type_table/README.md[visTypeTable]
|{kib-repo}blob/{branch}/src/plugins/vis_types/table/README.md[visTypeTable]
|Contains the data table visualization, that allows presenting data in a simple table format.
@ -293,7 +293,7 @@ The plugin exposes the static DefaultEditorController class to consume.
|WARNING: Missing README.
|{kib-repo}blob/{branch}/src/plugins/vis_type_timelion/README.md[visTypeTimelion]
|{kib-repo}blob/{branch}/src/plugins/vis_types/timelion/README.md[visTypeTimelion]
|Contains the timelion visualization and the timelion backend.
@ -391,7 +391,7 @@ security and spaces filtering as well as performing audit logging.
|{kib-repo}blob/{branch}/x-pack/plugins/enterprise_search/README.md[enterpriseSearch]
|This plugin provides beta Kibana user interfaces for managing the Enterprise Search solution and its products, App Search and Workplace Search.
|This plugin provides Kibana user interfaces for managing the Enterprise Search solution and its products, App Search and Workplace Search.
|{kib-repo}blob/{branch}/x-pack/plugins/event_log/README.md[eventLog]

View file

@ -25,8 +25,6 @@ import { i18n } from '@kbn/i18n';
async function getDeprecations({ esClient, savedObjectsClient }: GetDeprecationsContext): Promise<DeprecationsDetails[]> {
const deprecations: DeprecationsDetails[] = [];
// Example of an api correctiveAction
const count = await getFooCount(savedObjectsClient);
if (count > 0) {
deprecations.push({
@ -42,12 +40,12 @@ async function getDeprecations({ esClient, savedObjectsClient }: GetDeprecations
level: 'warning',
correctiveActions: {
manualSteps: [
i18n.translate('xpack.foo.deprecations.manualStepOneMessage', {
defaultMessage: 'Navigate to the Kibana Dashboard and click "Create dashboard".',
}),
i18n.translate('xpack.foo.deprecations.manualStepTwoMessage', {
defaultMessage: 'Select Foo from the "New Visualization" window.',
}),
i18n.translate('xpack.foo.deprecations.manualStepOneMessage', {
defaultMessage: 'Navigate to the Kibana Dashboard and click "Create dashboard".',
}),
i18n.translate('xpack.foo.deprecations.manualStepTwoMessage', {
defaultMessage: 'Select Foo from the "New Visualization" window.',
}),
],
api: {
path: '/internal/security/users/test_dashboard_user',
@ -68,7 +66,6 @@ async function getDeprecations({ esClient, savedObjectsClient }: GetDeprecations
},
});
}
return deprecations;
}

View file

@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-core-server](./kibana-plugin-core-server.md) &gt; [EventLoopDelaysMonitor](./kibana-plugin-core-server.eventloopdelaysmonitor.md) &gt; [(constructor)](./kibana-plugin-core-server.eventloopdelaysmonitor._constructor_.md)
## EventLoopDelaysMonitor.(constructor)
Creating a new instance from EventLoopDelaysMonitor will automatically start tracking event loop delays.
<b>Signature:</b>
```typescript
constructor();
```

View file

@ -0,0 +1,19 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-core-server](./kibana-plugin-core-server.md) &gt; [EventLoopDelaysMonitor](./kibana-plugin-core-server.eventloopdelaysmonitor.md) &gt; [collect](./kibana-plugin-core-server.eventloopdelaysmonitor.collect.md)
## EventLoopDelaysMonitor.collect() method
Collect gathers event loop delays metrics from nodejs perf\_hooks.monitorEventLoopDelay the histogram calculations start from the last time `reset` was called or this EventLoopDelaysMonitor instance was created.
<b>Signature:</b>
```typescript
collect(): IntervalHistogram;
```
<b>Returns:</b>
`IntervalHistogram`
{<!-- -->IntervalHistogram<!-- -->}

View file

@ -0,0 +1,26 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-core-server](./kibana-plugin-core-server.md) &gt; [EventLoopDelaysMonitor](./kibana-plugin-core-server.eventloopdelaysmonitor.md)
## EventLoopDelaysMonitor class
<b>Signature:</b>
```typescript
export declare class EventLoopDelaysMonitor
```
## Constructors
| Constructor | Modifiers | Description |
| --- | --- | --- |
| [(constructor)()](./kibana-plugin-core-server.eventloopdelaysmonitor._constructor_.md) | | Creating a new instance from EventLoopDelaysMonitor will automatically start tracking event loop delays. |
## Methods
| Method | Modifiers | Description |
| --- | --- | --- |
| [collect()](./kibana-plugin-core-server.eventloopdelaysmonitor.collect.md) | | Collect gathers event loop delays metrics from nodejs perf\_hooks.monitorEventLoopDelay the histogram calculations start from the last time <code>reset</code> was called or this EventLoopDelaysMonitor instance was created. |
| [reset()](./kibana-plugin-core-server.eventloopdelaysmonitor.reset.md) | | Resets the collected histogram data. |
| [stop()](./kibana-plugin-core-server.eventloopdelaysmonitor.stop.md) | | Disables updating the interval timer for collecting new data points. |

View file

@ -0,0 +1,17 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-core-server](./kibana-plugin-core-server.md) &gt; [EventLoopDelaysMonitor](./kibana-plugin-core-server.eventloopdelaysmonitor.md) &gt; [reset](./kibana-plugin-core-server.eventloopdelaysmonitor.reset.md)
## EventLoopDelaysMonitor.reset() method
Resets the collected histogram data.
<b>Signature:</b>
```typescript
reset(): void;
```
<b>Returns:</b>
`void`

View file

@ -0,0 +1,17 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-core-server](./kibana-plugin-core-server.md) &gt; [EventLoopDelaysMonitor](./kibana-plugin-core-server.eventloopdelaysmonitor.md) &gt; [stop](./kibana-plugin-core-server.eventloopdelaysmonitor.stop.md)
## EventLoopDelaysMonitor.stop() method
Disables updating the interval timer for collecting new data points.
<b>Signature:</b>
```typescript
stop(): void;
```
<b>Returns:</b>
`void`

View file

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-core-server](./kibana-plugin-core-server.md) &gt; [IntervalHistogram](./kibana-plugin-core-server.intervalhistogram.md) &gt; [exceeds](./kibana-plugin-core-server.intervalhistogram.exceeds.md)
## IntervalHistogram.exceeds property
<b>Signature:</b>
```typescript
exceeds: number;
```

View file

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-core-server](./kibana-plugin-core-server.md) &gt; [IntervalHistogram](./kibana-plugin-core-server.intervalhistogram.md) &gt; [fromTimestamp](./kibana-plugin-core-server.intervalhistogram.fromtimestamp.md)
## IntervalHistogram.fromTimestamp property
<b>Signature:</b>
```typescript
fromTimestamp: string;
```

View file

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-core-server](./kibana-plugin-core-server.md) &gt; [IntervalHistogram](./kibana-plugin-core-server.intervalhistogram.md) &gt; [lastUpdatedAt](./kibana-plugin-core-server.intervalhistogram.lastupdatedat.md)
## IntervalHistogram.lastUpdatedAt property
<b>Signature:</b>
```typescript
lastUpdatedAt: string;
```

View file

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-core-server](./kibana-plugin-core-server.md) &gt; [IntervalHistogram](./kibana-plugin-core-server.intervalhistogram.md) &gt; [max](./kibana-plugin-core-server.intervalhistogram.max.md)
## IntervalHistogram.max property
<b>Signature:</b>
```typescript
max: number;
```

View file

@ -0,0 +1,27 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-core-server](./kibana-plugin-core-server.md) &gt; [IntervalHistogram](./kibana-plugin-core-server.intervalhistogram.md)
## IntervalHistogram interface
an IntervalHistogram object that samples and reports the event loop delay over time. The delays will be reported in nanoseconds.
<b>Signature:</b>
```typescript
export interface IntervalHistogram
```
## Properties
| Property | Type | Description |
| --- | --- | --- |
| [exceeds](./kibana-plugin-core-server.intervalhistogram.exceeds.md) | <code>number</code> | |
| [fromTimestamp](./kibana-plugin-core-server.intervalhistogram.fromtimestamp.md) | <code>string</code> | |
| [lastUpdatedAt](./kibana-plugin-core-server.intervalhistogram.lastupdatedat.md) | <code>string</code> | |
| [max](./kibana-plugin-core-server.intervalhistogram.max.md) | <code>number</code> | |
| [mean](./kibana-plugin-core-server.intervalhistogram.mean.md) | <code>number</code> | |
| [min](./kibana-plugin-core-server.intervalhistogram.min.md) | <code>number</code> | |
| [percentiles](./kibana-plugin-core-server.intervalhistogram.percentiles.md) | <code>{</code><br/><code> 50: number;</code><br/><code> 75: number;</code><br/><code> 95: number;</code><br/><code> 99: number;</code><br/><code> }</code> | |
| [stddev](./kibana-plugin-core-server.intervalhistogram.stddev.md) | <code>number</code> | |

View file

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-core-server](./kibana-plugin-core-server.md) &gt; [IntervalHistogram](./kibana-plugin-core-server.intervalhistogram.md) &gt; [mean](./kibana-plugin-core-server.intervalhistogram.mean.md)
## IntervalHistogram.mean property
<b>Signature:</b>
```typescript
mean: number;
```

View file

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-core-server](./kibana-plugin-core-server.md) &gt; [IntervalHistogram](./kibana-plugin-core-server.intervalhistogram.md) &gt; [min](./kibana-plugin-core-server.intervalhistogram.min.md)
## IntervalHistogram.min property
<b>Signature:</b>
```typescript
min: number;
```

View file

@ -0,0 +1,16 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-core-server](./kibana-plugin-core-server.md) &gt; [IntervalHistogram](./kibana-plugin-core-server.intervalhistogram.md) &gt; [percentiles](./kibana-plugin-core-server.intervalhistogram.percentiles.md)
## IntervalHistogram.percentiles property
<b>Signature:</b>
```typescript
percentiles: {
50: number;
75: number;
95: number;
99: number;
};
```

View file

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-core-server](./kibana-plugin-core-server.md) &gt; [IntervalHistogram](./kibana-plugin-core-server.intervalhistogram.md) &gt; [stddev](./kibana-plugin-core-server.intervalhistogram.stddev.md)
## IntervalHistogram.stddev property
<b>Signature:</b>
```typescript
stddev: number;
```

View file

@ -19,6 +19,7 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
| [BasePath](./kibana-plugin-core-server.basepath.md) | Access or manipulate the Kibana base path |
| [CspConfig](./kibana-plugin-core-server.cspconfig.md) | CSP configuration for use in Kibana. |
| [ElasticsearchConfig](./kibana-plugin-core-server.elasticsearchconfig.md) | Wrapper of config schema. |
| [EventLoopDelaysMonitor](./kibana-plugin-core-server.eventloopdelaysmonitor.md) | |
| [KibanaRequest](./kibana-plugin-core-server.kibanarequest.md) | Kibana specific abstraction for an incoming request. |
| [RouteValidationError](./kibana-plugin-core-server.routevalidationerror.md) | Error to return when the validation is not successful. |
| [SavedObjectsClient](./kibana-plugin-core-server.savedobjectsclient.md) | |
@ -97,6 +98,7 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
| [IExternalUrlPolicy](./kibana-plugin-core-server.iexternalurlpolicy.md) | A policy describing whether access to an external destination is allowed. |
| [IKibanaResponse](./kibana-plugin-core-server.ikibanaresponse.md) | A response data object, expected to returned as a result of [RequestHandler](./kibana-plugin-core-server.requesthandler.md) execution |
| [IKibanaSocket](./kibana-plugin-core-server.ikibanasocket.md) | A tiny abstraction for TCP socket. |
| [IntervalHistogram](./kibana-plugin-core-server.intervalhistogram.md) | an IntervalHistogram object that samples and reports the event loop delay over time. The delays will be reported in nanoseconds. |
| [IRenderOptions](./kibana-plugin-core-server.irenderoptions.md) | |
| [IRouter](./kibana-plugin-core-server.irouter.md) | Registers route handlers for specified resource path and method. See [RouteConfig](./kibana-plugin-core-server.routeconfig.md) and [RequestHandler](./kibana-plugin-core-server.requesthandler.md) for more information about arguments to route registrations. |
| [ISavedObjectsPointInTimeFinder](./kibana-plugin-core-server.isavedobjectspointintimefinder.md) | |

View file

@ -19,7 +19,8 @@ export interface OpsMetrics
| [collected\_at](./kibana-plugin-core-server.opsmetrics.collected_at.md) | <code>Date</code> | Time metrics were recorded at. |
| [concurrent\_connections](./kibana-plugin-core-server.opsmetrics.concurrent_connections.md) | <code>OpsServerMetrics['concurrent_connections']</code> | number of current concurrent connections to the server |
| [os](./kibana-plugin-core-server.opsmetrics.os.md) | <code>OpsOsMetrics</code> | OS related metrics |
| [process](./kibana-plugin-core-server.opsmetrics.process.md) | <code>OpsProcessMetrics</code> | Process related metrics |
| [process](./kibana-plugin-core-server.opsmetrics.process.md) | <code>OpsProcessMetrics</code> | Process related metrics. Deprecated in favor of processes field. |
| [processes](./kibana-plugin-core-server.opsmetrics.processes.md) | <code>OpsProcessMetrics[]</code> | Process related metrics. Reports an array of objects for each kibana pid. |
| [requests](./kibana-plugin-core-server.opsmetrics.requests.md) | <code>OpsServerMetrics['requests']</code> | server requests stats |
| [response\_times](./kibana-plugin-core-server.opsmetrics.response_times.md) | <code>OpsServerMetrics['response_times']</code> | server response time stats |

View file

@ -4,7 +4,11 @@
## OpsMetrics.process property
Process related metrics
> Warning: This API is now obsolete.
>
>
Process related metrics. Deprecated in favor of processes field.
<b>Signature:</b>

View file

@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-core-server](./kibana-plugin-core-server.md) &gt; [OpsMetrics](./kibana-plugin-core-server.opsmetrics.md) &gt; [processes](./kibana-plugin-core-server.opsmetrics.processes.md)
## OpsMetrics.processes property
Process related metrics. Reports an array of objects for each kibana pid.
<b>Signature:</b>
```typescript
processes: OpsProcessMetrics[];
```

View file

@ -4,7 +4,7 @@
## OpsProcessMetrics.event\_loop\_delay property
node event loop delay
mean event loop delay since last collection
<b>Signature:</b>

View file

@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-core-server](./kibana-plugin-core-server.md) &gt; [OpsProcessMetrics](./kibana-plugin-core-server.opsprocessmetrics.md) &gt; [event\_loop\_delay\_histogram](./kibana-plugin-core-server.opsprocessmetrics.event_loop_delay_histogram.md)
## OpsProcessMetrics.event\_loop\_delay\_histogram property
node event loop delay histogram since last collection
<b>Signature:</b>
```typescript
event_loop_delay_histogram: IntervalHistogram;
```

View file

@ -16,7 +16,8 @@ export interface OpsProcessMetrics
| Property | Type | Description |
| --- | --- | --- |
| [event\_loop\_delay](./kibana-plugin-core-server.opsprocessmetrics.event_loop_delay.md) | <code>number</code> | node event loop delay |
| [event\_loop\_delay\_histogram](./kibana-plugin-core-server.opsprocessmetrics.event_loop_delay_histogram.md) | <code>IntervalHistogram</code> | node event loop delay histogram since last collection |
| [event\_loop\_delay](./kibana-plugin-core-server.opsprocessmetrics.event_loop_delay.md) | <code>number</code> | mean event loop delay since last collection |
| [memory](./kibana-plugin-core-server.opsprocessmetrics.memory.md) | <code>{</code><br/><code> heap: {</code><br/><code> total_in_bytes: number;</code><br/><code> used_in_bytes: number;</code><br/><code> size_limit: number;</code><br/><code> };</code><br/><code> resident_set_size_in_bytes: number;</code><br/><code> }</code> | process memory usage |
| [pid](./kibana-plugin-core-server.opsprocessmetrics.pid.md) | <code>number</code> | pid of the kibana process |
| [uptime\_in\_millis](./kibana-plugin-core-server.opsprocessmetrics.uptime_in_millis.md) | <code>number</code> | uptime of the kibana process |

View file

@ -512,10 +512,6 @@ Enables the legacy charts library for timelion charts in Visualize.
**This setting is deprecated and will not be supported as of 8.0.**
Maps values to specific colors in charts using the *Compatibility* palette.
[[visualization-dimmingopacity]]`visualization:dimmingOpacity`::
The opacity of the chart items that are dimmed when highlighting another element
of the chart. Use numbers between 0 and 1. The lower the number, the more the highlighted element stands out.
[[visualization-heatmap-maxbuckets]]`visualization:heatmap:maxBuckets`::
The maximum number of buckets a datasource can return. High numbers can have a negative impact on your browser rendering performance.

View file

@ -375,7 +375,7 @@
"safe-squel": "^5.12.5",
"seedrandom": "^3.0.5",
"semver": "^7.3.2",
"set-value": "^3.0.2",
"set-value": "^4.1.0",
"source-map-support": "^0.5.19",
"stats-lite": "^2.2.0",
"strip-ansi": "^6.0.0",

View file

@ -92,5 +92,6 @@ module.exports = {
],
'@kbn/eslint/no_async_promise_body': 'error',
'@kbn/eslint/no_async_foreach': 'error',
},
};

View file

@ -8,7 +8,7 @@
export interface ManagedConfigKey {
key: string;
value: Record<string, any>;
value: string | Record<string, any>;
}
/**
@ -37,4 +37,9 @@ export const MANAGED_CONFIG_KEYS: ManagedConfigKey[] = [
['**/packages/kbn-pm/dist/index.js']: true,
},
},
{
key: 'typescript.tsdk',
// we use a relative path here so that it works with remote vscode connections
value: './node_modules/typescript/lib',
},
];

View file

@ -22,6 +22,10 @@ const TEST_KEYS: ManagedConfigKey[] = [
world: [1, 2, 3],
},
},
{
key: 'stringKey',
value: 'foo',
},
];
const run = (json?: string) => updateVscodeConfig(TEST_KEYS, '', json);
@ -35,7 +39,9 @@ it('updates the passed JSON with the managed settings', () => {
"hello": true,
// @managed
"world": [1, 2, 3]
}
},
// @managed
"stringKey": "foo"
}
`);
@ -50,7 +56,9 @@ it('initialized empty or undefined json values', () => {
"hello": true,
// @managed
"world": [1, 2, 3]
}
},
// @managed
"stringKey": "foo"
}
`);
@ -63,14 +71,16 @@ it('initialized empty or undefined json values', () => {
"hello": true,
// @managed
"world": [1, 2, 3]
}
},
// @managed
"stringKey": "foo"
}
`);
});
it('replaces conflicting managed keys which do not have object values', () => {
expect(run(`{ "key": false }`)).toMatchInlineSnapshot(`
it('replaces conflicting managed keys which do not have matching value types', () => {
expect(run(`{ "key": false, "stringKey": { "a": "B" } }`)).toMatchInlineSnapshot(`
// @managed
{
"key": {
@ -78,7 +88,9 @@ it('replaces conflicting managed keys which do not have object values', () => {
"hello": true,
// @managed
"world": [1, 2, 3]
}
},
// @managed
"stringKey": "foo"
}
`);
@ -122,7 +134,9 @@ it('persists comments in the original file', () => {
"hello": true,
// @managed
"world": [1, 2, 3]
}
},
// @managed
"stringKey": "foo"
}
`);
@ -148,7 +162,9 @@ it('overrides old values for managed keys', () => {
"hello": true,
// @managed
"world": [1, 2, 3]
}
},
// @managed
"stringKey": "foo"
}
`);
@ -176,7 +192,9 @@ it('does not modify properties with leading `// self managed` comment', () => {
// self managed
"key": {
"world": [5]
}
},
// self managed
"stringKey": "--"
}
`);
@ -186,7 +204,9 @@ it('does not modify properties with leading `// self managed` comment', () => {
// self managed
"key": {
"world": [5]
}
},
// self managed
"stringKey": "--"
}
`);
@ -210,7 +230,9 @@ it('does not modify child properties with leading `// self managed` comment', ()
"world": [5],
// @managed
"hello": true
}
},
// @managed
"stringKey": "foo"
}
`);
@ -236,7 +258,9 @@ it('does not modify unknown child properties', () => {
"world": [5],
// @managed
"hello": true
}
},
// @managed
"stringKey": "foo"
}
`);
@ -262,7 +286,9 @@ it('removes managed properties which are no longer managed', () => {
"world": [5],
// @managed
"hello": true
}
},
// @managed
"stringKey": "foo"
}
`);
@ -286,7 +312,9 @@ it('wipes out child keys which conflict with newly managed child keys', () => {
"hello": true,
// @managed
"world": [1, 2, 3]
}
},
// @managed
"stringKey": "foo"
}
`);
@ -308,7 +336,9 @@ it('correctly formats info text when specified', () => {
"hello": true,
// @managed
"world": [1, 2, 3]
}
},
// @managed
"stringKey": "foo"
}
`);
@ -321,7 +351,10 @@ it('allows "// self managed" comments conflicting with "// @managed" comments to
// @managed
// self managed
"hello": ["world"]
}
},
// @managed
// self managed
"stringKey": 12345
}
`);
@ -333,7 +366,9 @@ it('allows "// self managed" comments conflicting with "// @managed" comments to
"hello": ["world"],
// @managed
"world": [1, 2, 3]
}
},
// self managed
"stringKey": 12345
}
`);

View file

@ -25,11 +25,20 @@ const isManaged = (node?: t.Node) =>
(c) => c.type === 'CommentLine' && c.value.trim().toLocaleLowerCase() === '@managed'
);
const isSelfManaged = (node?: t.Node) =>
!!node?.leadingComments?.some(
const isSelfManaged = (node?: t.Node) => {
const result = !!node?.leadingComments?.some(
(c) => c.type === 'CommentLine' && c.value.trim().toLocaleLowerCase() === 'self managed'
);
// if we find a node which is both managed and self managed remove the managed comment
if (result && node && isManaged(node)) {
node.leadingComments =
node.leadingComments?.filter((c) => c.value.trim() !== '@managed') ?? null;
}
return result;
};
const remove = <T>(arr: T[], value: T) => {
const index = arr.indexOf(value);
if (index > -1) {
@ -37,16 +46,16 @@ const remove = <T>(arr: T[], value: T) => {
}
};
const createManagedChildProp = (key: string, value: any) => {
const createManagedProp = (key: string, value: any) => {
const childProp = t.objectProperty(t.stringLiteral(key), parseExpression(JSON.stringify(value)));
t.addComment(childProp, 'leading', ' @managed', true);
return childProp;
};
const createManagedProp = (key: string, value: Record<string, any>) => {
const createObjectPropOfManagedValues = (key: string, value: Record<string, any>) => {
return t.objectProperty(
t.stringLiteral(key),
t.objectExpression(Object.entries(value).map(([k, v]) => createManagedChildProp(k, v)))
t.objectExpression(Object.entries(value).map(([k, v]) => createManagedProp(k, v)))
);
};
@ -57,8 +66,16 @@ const createManagedProp = (key: string, value: Record<string, any>) => {
* @param key the key name to add
* @param value managed value which should be set at `key`
*/
const addManagedProp = (ast: t.ObjectExpression, key: string, value: Record<string, any>) => {
ast.properties.push(createManagedProp(key, value));
const addManagedProp = (
ast: t.ObjectExpression,
key: string,
value: string | Record<string, any>
) => {
ast.properties.push(
typeof value === 'string'
? createManagedProp(key, value)
: createObjectPropOfManagedValues(key, value)
);
};
/**
@ -72,7 +89,7 @@ const addManagedProp = (ast: t.ObjectExpression, key: string, value: Record<stri
const replaceManagedProp = (
ast: t.ObjectExpression,
existing: BasicObjectProp,
value: Record<string, any>
value: string | Record<string, any>
) => {
remove(ast.properties, existing);
addManagedProp(ast, existing.key.value, value);
@ -98,15 +115,11 @@ const mergeManagedProperties = (
if (!existing) {
// add the new managed prop
properties.push(createManagedChildProp(key, value));
properties.push(createManagedProp(key, value));
continue;
}
if (isSelfManaged(existing)) {
// strip "// @managed" comment if conflicting with "// self managed"
existing.leadingComments = (existing.leadingComments ?? []).filter(
(c) => c.value.trim() !== '@managed'
);
continue;
}
@ -119,7 +132,7 @@ const mergeManagedProperties = (
// take over the unmanaged child prop by deleting the previous prop and replacing it
// with a brand new one
remove(properties, existing);
properties.push(createManagedChildProp(key, value));
properties.push(createManagedProp(key, value));
}
// iterate through the props to find "// @managed" props which are no longer in
@ -170,20 +183,29 @@ export function updateVscodeConfig(keys: ManagedConfigKey[], infoText: string, j
continue;
}
if (existingProp && existingProp.value.type === 'ObjectExpression') {
// setting exists and is an object so merge properties of `value` with it
mergeManagedProperties(existingProp.value.properties, value);
if (typeof value === 'object') {
if (existingProp && existingProp.value.type === 'ObjectExpression') {
// setting exists and is an object so merge properties of `value` with it
mergeManagedProperties(existingProp.value.properties, value);
continue;
}
if (existingProp) {
// setting exists but its value is not an object expression so replace it
replaceManagedProp(ast, existingProp, value);
continue;
}
// setting isn't in config file so create it
addManagedProp(ast, key, value);
continue;
}
if (existingProp) {
// setting exists but its value is not an object expression so replace it
replaceManagedProp(ast, existingProp, value);
continue;
} else {
addManagedProp(ast, key, value);
}
// setting isn't in config file so create it
addManagedProp(ast, key, value);
}
ast.leadingComments = [

View file

@ -14,5 +14,6 @@ module.exports = {
module_migration: require('./rules/module_migration'),
no_export_all: require('./rules/no_export_all'),
no_async_promise_body: require('./rules/no_async_promise_body'),
no_async_foreach: require('./rules/no_async_foreach'),
},
};

View file

@ -0,0 +1,62 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 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.
*/
const tsEstree = require('@typescript-eslint/typescript-estree');
const esTypes = tsEstree.AST_NODE_TYPES;
/** @typedef {import("eslint").Rule.RuleModule} Rule */
/** @typedef {import("@typescript-eslint/typescript-estree").TSESTree.Node} Node */
/** @typedef {import("@typescript-eslint/typescript-estree").TSESTree.CallExpression} CallExpression */
/** @typedef {import("@typescript-eslint/typescript-estree").TSESTree.FunctionExpression} FunctionExpression */
/** @typedef {import("@typescript-eslint/typescript-estree").TSESTree.ArrowFunctionExpression} ArrowFunctionExpression */
/** @typedef {import("eslint").Rule.RuleFixer} Fixer */
const ERROR_MSG =
'Passing an async function to .forEach() prevents promise rejections from being handled. Use asyncForEach() or similar helper from "@kbn/std" instead.';
/**
* @param {Node} node
* @returns {node is ArrowFunctionExpression | FunctionExpression}
*/
const isFunc = (node) =>
node.type === esTypes.ArrowFunctionExpression || node.type === esTypes.FunctionExpression;
/**
* @param {any} context
* @param {CallExpression} node
*/
const isAsyncForEachCall = (node) => {
return (
node.callee.type === esTypes.MemberExpression &&
node.callee.property.type === esTypes.Identifier &&
node.callee.property.name === 'forEach' &&
node.arguments.length >= 1 &&
isFunc(node.arguments[0]) &&
node.arguments[0].async
);
};
/** @type {Rule} */
module.exports = {
meta: {
fixable: 'code',
schema: [],
},
create: (context) => ({
CallExpression(_) {
const node = /** @type {CallExpression} */ (_);
if (isAsyncForEachCall(node)) {
context.report({
message: ERROR_MSG,
loc: node.arguments[0].loc,
});
}
},
}),
};

View file

@ -0,0 +1,72 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
const { RuleTester } = require('eslint');
const rule = require('./no_async_foreach');
const dedent = require('dedent');
const ruleTester = new RuleTester({
parser: require.resolve('@typescript-eslint/parser'),
parserOptions: {
sourceType: 'module',
ecmaVersion: 2018,
ecmaFeatures: {
jsx: true,
},
},
});
ruleTester.run('@kbn/eslint/no_async_foreach', rule, {
valid: [
{
code: dedent`
array.forEach((a) => {
b(a)
})
`,
},
{
code: dedent`
array.forEach(function (a) {
b(a)
})
`,
},
],
invalid: [
{
code: dedent`
array.forEach(async (a) => {
await b(a)
})
`,
errors: [
{
line: 1,
message:
'Passing an async function to .forEach() prevents promise rejections from being handled. Use asyncForEach() or similar helper from "@kbn/std" instead.',
},
],
},
{
code: dedent`
array.forEach(async function (a) {
await b(a)
})
`,
errors: [
{
line: 1,
message:
'Passing an async function to .forEach() prevents promise rejections from being handled. Use asyncForEach() or similar helper from "@kbn/std" instead.',
},
],
},
],
});

View file

@ -9,7 +9,10 @@ SOURCE_FILES = glob(
[
"src/**/*.ts",
],
exclude = ["**/*.test.*"],
exclude = [
"**/*.test.*",
"**/test_helpers.ts",
],
)
SRCS = SOURCE_FILES

View file

@ -18,3 +18,11 @@ export { unset } from './unset';
export { getFlattenedObject } from './get_flattened_object';
export { ensureNoUnsafeProperties } from './ensure_no_unsafe_properties';
export * from './rxjs_7';
export {
map$,
mapWithLimit$,
asyncMap,
asyncMapWithLimit,
asyncForEach,
asyncForEachWithLimit,
} from './iteration';

View file

@ -0,0 +1,81 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import * as Rx from 'rxjs';
import { asyncForEach, asyncForEachWithLimit } from './for_each';
import { list, sleep } from './test_helpers';
jest.mock('./observable');
const mockMapWithLimit$: jest.Mock = jest.requireMock('./observable').mapWithLimit$;
beforeEach(() => {
jest.clearAllMocks();
});
describe('asyncForEachWithLimit', () => {
it('calls mapWithLimit$ and resolves with undefined when it completes', async () => {
const iter = list(10);
const limit = 5;
const fn = jest.fn();
const result$ = new Rx.Subject();
mockMapWithLimit$.mockImplementation(() => result$);
const promise = asyncForEachWithLimit(iter, limit, fn);
let resolved = false;
promise.then(() => (resolved = true));
expect(mockMapWithLimit$).toHaveBeenCalledTimes(1);
expect(mockMapWithLimit$).toHaveBeenCalledWith(iter, limit, fn);
expect(resolved).toBe(false);
result$.next(1);
result$.next(2);
result$.next(3);
await sleep(10);
expect(resolved).toBe(false);
result$.complete();
await expect(promise).resolves.toBe(undefined);
});
it('resolves when iterator is empty', async () => {
mockMapWithLimit$.mockImplementation((x) => Rx.from(x));
await expect(asyncForEachWithLimit([], 100, async () => 'foo')).resolves.toBe(undefined);
});
});
describe('asyncForEach', () => {
it('calls mapWithLimit$ without limit and resolves with undefined when it completes', async () => {
const iter = list(10);
const fn = jest.fn();
const result$ = new Rx.Subject();
mockMapWithLimit$.mockImplementation(() => result$);
const promise = asyncForEach(iter, fn);
let resolved = false;
promise.then(() => (resolved = true));
expect(mockMapWithLimit$).toHaveBeenCalledTimes(1);
expect(mockMapWithLimit$).toHaveBeenCalledWith(iter, Infinity, fn);
expect(resolved).toBe(false);
result$.next(1);
result$.next(2);
result$.next(3);
await sleep(10);
expect(resolved).toBe(false);
result$.complete();
await expect(promise).resolves.toBe(undefined);
});
});

View file

@ -0,0 +1,44 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { defaultIfEmpty } from 'rxjs/operators';
import { lastValueFrom } from '../rxjs_7';
import { mapWithLimit$ } from './observable';
import { IterableInput, AsyncMapFn } from './types';
/**
* Creates a promise which resolves with `undefined` after calling `fn` for each
* item in `iterable`. `fn` can return either a Promise or Observable. If `fn`
* returns observables then they will properly abort if an error occurs.
*
* @param iterable Items to iterate
* @param fn Function to call for each item
*/
export async function asyncForEach<T>(iterable: IterableInput<T>, fn: AsyncMapFn<T, any>) {
await lastValueFrom(mapWithLimit$(iterable, Infinity, fn).pipe(defaultIfEmpty()));
}
/**
* Creates a promise which resolves with `undefined` after calling `fn` for each
* item in `iterable`. `fn` can return either a Promise or Observable. If `fn`
* returns observables then they will properly abort if an error occurs.
*
* The number of concurrent executions of `fn` is limited by `limit`.
*
* @param iterable Items to iterate
* @param limit Maximum number of operations to run in parallel
* @param fn Function to call for each item
*/
export async function asyncForEachWithLimit<T>(
iterable: IterableInput<T>,
limit: number,
fn: AsyncMapFn<T, any>
) {
await lastValueFrom(mapWithLimit$(iterable, limit, fn).pipe(defaultIfEmpty()));
}

View file

@ -6,7 +6,6 @@
* Side Public License, v 1.
*/
// TODO: https://github.com/elastic/kibana/issues/110891
/* eslint-disable @kbn/eslint/no_export_all */
export * from './types';
export * from './observable';
export * from './for_each';
export * from './map';

View file

@ -0,0 +1,82 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import * as Rx from 'rxjs';
import { mapTo } from 'rxjs/operators';
import { asyncMap, asyncMapWithLimit } from './map';
import { list } from './test_helpers';
jest.mock('./observable');
const mapWithLimit$: jest.Mock = jest.requireMock('./observable').mapWithLimit$;
mapWithLimit$.mockImplementation(jest.requireActual('./observable').mapWithLimit$);
beforeEach(() => {
jest.clearAllMocks();
});
describe('asyncMapWithLimit', () => {
it('calls mapWithLimit$ and resolves with properly sorted results', async () => {
const iter = list(10);
const limit = 5;
const fn = jest.fn((n) => (n % 2 ? Rx.timer(n) : Rx.timer(n * 4)).pipe(mapTo(n)));
const result = await asyncMapWithLimit(iter, limit, fn);
expect(result).toMatchInlineSnapshot(`
Array [
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
]
`);
expect(mapWithLimit$).toHaveBeenCalledTimes(1);
expect(mapWithLimit$).toHaveBeenCalledWith(iter, limit, expect.any(Function));
});
it.each([
[list(0), []] as const,
[list(1), ['foo']] as const,
[list(2), ['foo', 'foo']] as const,
])('resolves when iterator is %p', async (input, output) => {
await expect(asyncMapWithLimit(input, 100, async () => 'foo')).resolves.toEqual(output);
});
});
describe('asyncMap', () => {
it('calls mapWithLimit$ without limit and resolves with undefined when it completes', async () => {
const iter = list(10);
const fn = jest.fn((n) => (n % 2 ? Rx.timer(n) : Rx.timer(n * 4)).pipe(mapTo(n)));
const result = await asyncMap(iter, fn);
expect(result).toMatchInlineSnapshot(`
Array [
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
]
`);
expect(mapWithLimit$).toHaveBeenCalledTimes(1);
expect(mapWithLimit$).toHaveBeenCalledWith(iter, Infinity, expect.any(Function));
});
});

View file

@ -0,0 +1,63 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { from } from 'rxjs';
import { toArray } from 'rxjs/operators';
import { lastValueFrom } from '../rxjs_7';
import { IterableInput, AsyncMapFn, AsyncMapResult } from './types';
import { mapWithLimit$ } from './observable';
const getAllResults = <T>(input: AsyncMapResult<T>) => lastValueFrom(from(input).pipe(toArray()));
/**
* Creates a promise whose values is the array of results produced by calling `fn` for
* each item in `iterable`. `fn` can return either a Promise or Observable. If `fn`
* returns observables then they will properly abort if an error occurs.
*
* The result array follows the order of the input iterable, even though the calls
* to `fn` may not. (so avoid side effects)
*
* @param iterable Items to iterate
* @param fn Function to call for each item. Result is added/concatenated into the result array in place of the input value
*/
export async function asyncMap<T1, T2>(iterable: IterableInput<T1>, fn: AsyncMapFn<T1, T2>) {
return await asyncMapWithLimit(iterable, Infinity, fn);
}
/**
* Creates a promise whose values is the array of results produced by calling `fn` for
* each item in `iterable`. `fn` can return either a Promise or Observable. If `fn`
* returns observables then they will properly abort if an error occurs.
*
* The number of concurrent executions of `fn` is limited by `limit`.
*
* The result array follows the order of the input iterable, even though the calls
* to `fn` may not. (so avoid side effects)
*
* @param iterable Items to iterate
* @param limit Maximum number of operations to run in parallel
* @param fn Function to call for each item. Result is added/concatenated into the result array in place of the input value
*/
export async function asyncMapWithLimit<T1, T2>(
iterable: IterableInput<T1>,
limit: number,
fn: AsyncMapFn<T1, T2>
) {
const results$ = mapWithLimit$(
iterable,
limit,
async (item, i) => [i, await getAllResults(fn(item, i))] as const
);
const results = await getAllResults(results$);
return results
.sort(([a], [b]) => a - b)
.reduce((acc: T2[], [, result]) => acc.concat(result), []);
}

View file

@ -0,0 +1,81 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import * as Rx from 'rxjs';
import { toArray } from 'rxjs/operators';
import { lastValueFrom } from '../rxjs_7';
import { map$, mapWithLimit$ } from './observable';
import { list, sleep, generator } from './test_helpers';
beforeEach(() => {
jest.clearAllMocks();
});
describe('mapWithLimit$', () => {
it('calls the fn for each item and produced each item on the stream with limit 1', async () => {
let maxConcurrency = 0;
let active = 0;
const limit = Math.random() > 0.5 ? 20 : 40;
const results = await lastValueFrom(
mapWithLimit$(list(100), limit, async (n) => {
active += 1;
if (active > maxConcurrency) {
maxConcurrency = active;
}
await sleep(5);
active -= 1;
return n;
}).pipe(toArray())
);
expect(maxConcurrency).toBe(limit);
expect(results).toHaveLength(100);
for (const [n, i] of results.entries()) {
expect(n).toBe(i);
}
});
it.each([
['empty array', [], []] as const,
['empty generator', generator(0), []] as const,
['generator', generator(5), [0, 1, 2, 3, 4]] as const,
['set', new Set([5, 4, 3, 2, 1]), [5, 4, 3, 2, 1]] as const,
['observable', Rx.of(1, 2, 3, 4, 5), [1, 2, 3, 4, 5]] as const,
])('works with %p', async (_, iter, expected) => {
const mock = jest.fn(async (n) => n);
const results = await lastValueFrom(mapWithLimit$(iter, 1, mock).pipe(toArray()));
expect(results).toEqual(expected);
});
});
describe('map$', () => {
it('applies no limit to mapWithLimit$', async () => {
let maxConcurrency = 0;
let active = 0;
const results = await lastValueFrom(
map$(list(100), async (n) => {
active += 1;
if (active > maxConcurrency) {
maxConcurrency = active;
}
await sleep(5);
active -= 1;
return n;
}).pipe(toArray())
);
expect(maxConcurrency).toBe(100);
expect(results).toHaveLength(100);
for (const [n, i] of results.entries()) {
expect(n).toBe(i);
}
});
});

View file

@ -0,0 +1,49 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { from } from 'rxjs';
import { mergeMap } from 'rxjs/operators';
import { IterableInput, AsyncMapFn } from './types';
/**
* Creates an observable whose values are the result of calling `fn` for each
* item in `iterable`. `fn` can return either a Promise or an Observable. If
* `fn` returns observables then they will properly abort if an error occurs.
*
* Results are emitted as soon as they are available so their order is very
* likely to not match their order in the input `array`.
*
* @param iterable Items to iterate
* @param fn Function to call for each item. Result is added/concatenated into the result array in place of the input value
*/
export function map$<T1, T2>(iterable: IterableInput<T1>, fn: AsyncMapFn<T1, T2>) {
return from(iterable).pipe(mergeMap(fn));
}
/**
* Creates an observable whose values are the result of calling `fn` for each
* item in `iterable`. `fn` can return either a Promise or an Observable. If
* `fn` returns observables then they will properly abort if an error occurs.
*
* The number of concurrent executions of `fn` is limited by `limit`.
*
* Results are emitted as soon as they are available so their order is very
* likely to not match their order in the input `array`.
*
* @param iterable Items to iterate
* @param limit Maximum number of operations to run in parallel
* @param fn Function to call for each item. Result is added/concatenated into the result array in place of the input value
*/
export function mapWithLimit$<T1, T2>(
iterable: IterableInput<T1>,
limit: number,
fn: AsyncMapFn<T1, T2>
) {
return from(iterable).pipe(mergeMap(fn, limit));
}

View file

@ -0,0 +1,17 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export const list = (size: number) => Array.from({ length: size }, (_, i) => i);
export const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
export const generator = function* (size: number) {
for (const n of list(size)) {
yield n;
}
};

View file

@ -0,0 +1,13 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { Subscribable } from 'rxjs';
export type IterableInput<T> = Iterable<T> | Subscribable<T>;
export type AsyncMapResult<T> = Promise<T> | Subscribable<T>;
export type AsyncMapFn<T1, T2> = (item: T1, i: number) => AsyncMapResult<T2>;

View file

@ -85,8 +85,8 @@
'xml-crypto', '@types/xml-crypto'
],
reviewers: ['team:kibana-security'],
matchBaseBranches: ['master', '7.x'],
labels: ['Team:Security'],
matchBaseBranches: ['master'],
labels: ['Team:Security', 'release_note:skip', 'auto-backport'],
enabled: true,
},
],

View file

@ -13,14 +13,12 @@ const alwaysImportedTests = [
require.resolve('../test/ui_capabilities/newsfeed_err/config.ts'),
require.resolve('../test/new_visualize_flow/config.ts'),
require.resolve('../test/security_functional/config.ts'),
require.resolve('../test/functional/config.legacy.ts'),
];
// eslint-disable-next-line no-restricted-syntax
const onlyNotInCoverageTests = [
require.resolve('../test/api_integration/config.js'),
require.resolve('../test/interpreter_functional/config.ts'),
require.resolve('../test/examples/config.js'),
require.resolve('../test/functional_execution_context/config.ts'),
];
require('../src/setup_node_env');

View file

@ -6,4 +6,4 @@
* Side Public License, v 1.
*/
export { registerLegacyVis } from './register_legacy_vis';
require('../src/cli_verification_code/dev');

View file

@ -0,0 +1,39 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 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 { kibanaPackageJson, getDataPath } from '@kbn/utils';
import path from 'path';
import fs from 'fs';
import chalk from 'chalk';
import Command from '../cli/command';
const program = new Command('bin/kibana-verification-code');
program
.version(kibanaPackageJson.version)
.description('Tool to get Kibana verification code')
.action(() => {
const fpath = path.join(getDataPath(), 'verification_code');
try {
const code = fs.readFileSync(fpath).toString();
console.log(
`Your verification code is: ${chalk.black.bgCyanBright(
` ${code.substr(0, 3)} ${code.substr(3)} `
)}`
);
} catch (error) {
console.log(`Couldn't find verification code.
If Kibana hasn't been configured yet, restart Kibana to generate a new code.
Otherwise, you can safely ignore this message and start using Kibana.`);
}
});
program.parse(process.argv);

View file

@ -0,0 +1,10 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
require('../setup_node_env');
require('./cli_verification_code');

View file

@ -0,0 +1,10 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
require('../setup_node_env/dist');
require('./cli_verification_code');

View file

@ -9,6 +9,7 @@
import { StatusResponse } from '../../../../types/status';
import { httpServiceMock } from '../../../http/http_service.mock';
import { notificationServiceMock } from '../../../notifications/notifications_service.mock';
import { mocked } from '../../../../server/metrics/event_loop_delays/event_loop_delays_monitor.mocks';
import { loadStatus } from './load_status';
const mockedResponse: StatusResponse = {
@ -61,6 +62,7 @@ const mockedResponse: StatusResponse = {
},
},
process: {
pid: 1,
memory: {
heap: {
size_limit: 1000000,
@ -70,9 +72,25 @@ const mockedResponse: StatusResponse = {
resident_set_size_in_bytes: 1,
},
event_loop_delay: 1,
pid: 1,
event_loop_delay_histogram: mocked.createHistogram(),
uptime_in_millis: 1,
},
processes: [
{
pid: 1,
memory: {
heap: {
size_limit: 1000000,
used_in_bytes: 100,
total_in_bytes: 0,
},
resident_set_size_in_bytes: 1,
},
event_loop_delay: 1,
event_loop_delay_histogram: mocked.createHistogram(),
uptime_in_millis: 1,
},
],
response_times: {
avg_in_millis: 4000,
max_in_millis: 8000,

View file

@ -39,7 +39,6 @@ import { SavedObjectsClientContract } from '../saved_objects/types';
* const deprecations: DeprecationsDetails[] = [];
* const count = await getFooCount(savedObjectsClient);
* if (count > 0) {
* // Example of a manual correctiveAction
* deprecations.push({
* title: i18n.translate('xpack.foo.deprecations.title', {
* defaultMessage: `Foo's are deprecated`

View file

@ -378,7 +378,9 @@ export type {
OpsProcessMetrics,
MetricsServiceSetup,
MetricsServiceStart,
IntervalHistogram,
} from './metrics';
export { EventLoopDelaysMonitor } from './metrics';
export type { I18nServiceSetup } from './i18n';
export type {

View file

@ -65,8 +65,10 @@ export class FileAppender implements DisposableAppender {
return resolve();
}
this.outputStream.end(() => {
this.outputStream = undefined;
const outputStream = this.outputStream;
this.outputStream = undefined;
outputStream.end(() => {
resolve();
});
});

View file

@ -8,8 +8,10 @@
import { MetricsCollector } from './types';
const createCollector = (collectReturnValue: any = {}): jest.Mocked<MetricsCollector<any>> => {
const collector: jest.Mocked<MetricsCollector<any>> = {
const createCollector = <T = any>(
collectReturnValue: any = {}
): jest.Mocked<MetricsCollector<T>> => {
const collector: jest.Mocked<MetricsCollector<T>> = {
collect: jest.fn().mockResolvedValue(collectReturnValue),
reset: jest.fn(),
};

View file

@ -6,7 +6,8 @@
* Side Public License, v 1.
*/
import { MetricsCollector } from './types';
import type { MetricsCollector } from './types';
import { createMockOpsProcessMetrics } from './process.mocks';
const createMock = () => {
const mocked: jest.Mocked<MetricsCollector<any>> = {
@ -21,4 +22,5 @@ const createMock = () => {
export const collectorMock = {
create: createMock,
createOpsProcessMetrics: createMockOpsProcessMetrics,
};

View file

@ -0,0 +1,24 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { mocked } from '../event_loop_delays/event_loop_delays_monitor.mocks';
import type { OpsProcessMetrics } from './types';
export function createMockOpsProcessMetrics(): OpsProcessMetrics {
const histogram = mocked.createHistogram();
return {
memory: {
heap: { total_in_bytes: 1, used_in_bytes: 1, size_limit: 1 },
resident_set_size_in_bytes: 1,
},
event_loop_delay: 1,
event_loop_delay_histogram: histogram,
pid: 1,
uptime_in_millis: 1,
};
}

View file

@ -9,6 +9,7 @@
import v8, { HeapInfo } from 'v8';
import { ProcessMetricsCollector } from './process';
/* eslint-disable dot-notation */
describe('ProcessMetricsCollector', () => {
let collector: ProcessMetricsCollector;
@ -20,28 +21,34 @@ describe('ProcessMetricsCollector', () => {
jest.restoreAllMocks();
});
it('collects pid from the process', async () => {
const metrics = await collector.collect();
it('collects pid from the process', () => {
const metrics = collector.collect();
expect(metrics.pid).toEqual(process.pid);
expect(metrics).toHaveLength(1);
expect(metrics[0].pid).toEqual(process.pid);
});
it('collects event loop delay', async () => {
const metrics = await collector.collect();
expect(metrics.event_loop_delay).toBeGreaterThan(0);
it('collects event loop delay', () => {
const mockEventLoopDelayMonitor = { collect: jest.fn().mockReturnValue({ mean: 13 }) };
// @ts-expect-error-next-line readonly private method.
collector['eventLoopDelayMonitor'] = mockEventLoopDelayMonitor;
const metrics = collector.collect();
expect(metrics).toHaveLength(1);
expect(metrics[0].event_loop_delay).toBe(13);
expect(mockEventLoopDelayMonitor.collect).toBeCalledTimes(1);
});
it('collects uptime info from the process', async () => {
it('collects uptime info from the process', () => {
const uptime = 58986;
jest.spyOn(process, 'uptime').mockImplementation(() => uptime);
const metrics = await collector.collect();
const metrics = collector.collect();
expect(metrics.uptime_in_millis).toEqual(uptime * 1000);
expect(metrics).toHaveLength(1);
expect(metrics[0].uptime_in_millis).toEqual(uptime * 1000);
});
it('collects memory info from the process', async () => {
it('collects memory info from the process', () => {
const heapTotal = 58986;
const heapUsed = 4688;
const heapSizeLimit = 5788;
@ -61,11 +68,12 @@ describe('ProcessMetricsCollector', () => {
} as HeapInfo)
);
const metrics = await collector.collect();
const metrics = collector.collect();
expect(metrics.memory.heap.total_in_bytes).toEqual(heapTotal);
expect(metrics.memory.heap.used_in_bytes).toEqual(heapUsed);
expect(metrics.memory.heap.size_limit).toEqual(heapSizeLimit);
expect(metrics.memory.resident_set_size_in_bytes).toEqual(rss);
expect(metrics).toHaveLength(1);
expect(metrics[0].memory.heap.total_in_bytes).toEqual(heapTotal);
expect(metrics[0].memory.heap.used_in_bytes).toEqual(heapUsed);
expect(metrics[0].memory.heap.size_limit).toEqual(heapSizeLimit);
expect(metrics[0].memory.resident_set_size_in_bytes).toEqual(rss);
});
});

View file

@ -7,14 +7,26 @@
*/
import v8 from 'v8';
import { Bench } from '@hapi/hoek';
import { OpsProcessMetrics, MetricsCollector } from './types';
import { EventLoopDelaysMonitor } from '../event_loop_delays';
export class ProcessMetricsCollector implements MetricsCollector<OpsProcessMetrics> {
public async collect(): Promise<OpsProcessMetrics> {
export class ProcessMetricsCollector implements MetricsCollector<OpsProcessMetrics[]> {
static getMainThreadMetrics(processes: OpsProcessMetrics[]): undefined | OpsProcessMetrics {
/**
* Currently Kibana does not support multi-processes.
* Once we have multiple processes we can add a `name` field
* and filter on `name === 'server_worker'` to get the main thread.
*/
return processes[0];
}
private readonly eventLoopDelayMonitor = new EventLoopDelaysMonitor();
private getCurrentPidMetrics(): OpsProcessMetrics {
const eventLoopDelayHistogram = this.eventLoopDelayMonitor.collect();
const heapStats = v8.getHeapStatistics();
const memoryUsage = process.memoryUsage();
const [eventLoopDelay] = await Promise.all([getEventLoopDelay()]);
return {
memory: {
heap: {
@ -25,19 +37,17 @@ export class ProcessMetricsCollector implements MetricsCollector<OpsProcessMetri
resident_set_size_in_bytes: memoryUsage.rss,
},
pid: process.pid,
event_loop_delay: eventLoopDelay,
event_loop_delay: eventLoopDelayHistogram.mean,
event_loop_delay_histogram: eventLoopDelayHistogram,
uptime_in_millis: process.uptime() * 1000,
};
}
public reset() {}
}
public collect(): OpsProcessMetrics[] {
return [this.getCurrentPidMetrics()];
}
const getEventLoopDelay = (): Promise<number> => {
const bench = new Bench();
return new Promise((resolve) => {
setImmediate(() => {
return resolve(bench.elapsed());
});
});
};
public reset() {
this.eventLoopDelayMonitor.reset();
}
}

View file

@ -5,11 +5,13 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { MaybePromise } from '@kbn/utility-types';
import type { IntervalHistogram } from '../types';
/** Base interface for all metrics gatherers */
export interface MetricsCollector<T> {
/** collect the data currently gathered by the collector */
collect(): Promise<T>;
collect(): MaybePromise<T>;
/** reset the internal state of the collector */
reset(): void;
}
@ -19,6 +21,8 @@ export interface MetricsCollector<T> {
* @public
*/
export interface OpsProcessMetrics {
/** pid of the kibana process */
pid: number;
/** process memory usage */
memory: {
/** heap memory usage */
@ -33,10 +37,10 @@ export interface OpsProcessMetrics {
/** node rss */
resident_set_size_in_bytes: number;
};
/** node event loop delay */
/** mean event loop delay since last collection*/
event_loop_delay: number;
/** pid of the kibana process */
pid: number;
/** node event loop delay histogram since last collection */
event_loop_delay_histogram: IntervalHistogram;
/** uptime of the kibana process */
uptime_in_millis: number;
}

View file

@ -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 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 { mocked } from '../event_loop_delays_monitor.mocks';
export const monitorEventLoopDelay = jest.fn().mockImplementation(() => {
const mockedHistogram = mocked.createHistogram();
return {
...mockedHistogram,
enable: jest.fn(),
percentile: jest.fn().mockImplementation((percentile: number) => {
return (mockedHistogram.percentiles as Record<string, number | undefined>)[`${percentile}`];
}),
disable: jest.fn(),
reset: jest.fn(),
};
});

View file

@ -6,26 +6,11 @@
* Side Public License, v 1.
*/
import moment from 'moment';
import type { IntervalHistogram } from './event_loop_delays';
export const mockMonitorEnable = jest.fn();
export const mockMonitorPercentile = jest.fn();
export const mockMonitorReset = jest.fn();
export const mockMonitorDisable = jest.fn();
export const monitorEventLoopDelay = jest.fn().mockReturnValue({
enable: mockMonitorEnable,
percentile: mockMonitorPercentile,
disable: mockMonitorDisable,
reset: mockMonitorReset,
...createMockHistogram(),
});
jest.doMock('perf_hooks', () => ({
monitorEventLoopDelay,
}));
import type { EventLoopDelaysMonitor } from './event_loop_delays_monitor';
import type { IntervalHistogram } from '../types';
function createMockHistogram(overwrites: Partial<IntervalHistogram> = {}): IntervalHistogram {
const now = moment();
const now = Date.now();
return {
min: 9093120,
@ -33,8 +18,8 @@ function createMockHistogram(overwrites: Partial<IntervalHistogram> = {}): Inter
mean: 11993238.600747818,
exceeds: 0,
stddev: 1168191.9357543814,
fromTimestamp: now.startOf('day').toISOString(),
lastUpdatedAt: now.toISOString(),
fromTimestamp: moment(now).toISOString(),
lastUpdatedAt: moment(now).toISOString(),
percentiles: {
'50': 12607487,
'75': 12615679,
@ -45,6 +30,22 @@ function createMockHistogram(overwrites: Partial<IntervalHistogram> = {}): Inter
};
}
function createMockEventLoopDelaysMonitor() {
const mockCollect = jest.fn();
const MockEventLoopDelaysMonitor: jest.MockedClass<
typeof EventLoopDelaysMonitor
> = jest.fn().mockReturnValue({
collect: mockCollect,
reset: jest.fn(),
stop: jest.fn(),
});
mockCollect.mockReturnValue(createMockHistogram());
return new MockEventLoopDelaysMonitor();
}
export const mocked = {
createHistogram: createMockHistogram,
createEventLoopDelaysMonitor: createMockEventLoopDelaysMonitor,
};

View file

@ -0,0 +1,59 @@
/*
* Copyright 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.
*/
/* eslint-disable dot-notation */
jest.mock('perf_hooks');
import { monitorEventLoopDelay } from 'perf_hooks';
import { EventLoopDelaysMonitor } from './event_loop_delays_monitor';
import { mocked } from './event_loop_delays_monitor.mocks';
describe('EventLoopDelaysMonitor', () => {
beforeAll(() => {
jest.useFakeTimers('modern');
const mockNow = jest.getRealSystemTime();
jest.setSystemTime(mockNow);
});
afterEach(() => jest.clearAllMocks());
afterAll(() => jest.useRealTimers());
test('#constructor enables monitoring', () => {
const eventLoopDelaysMonitor = new EventLoopDelaysMonitor();
expect(monitorEventLoopDelay).toBeCalledTimes(1);
expect(eventLoopDelaysMonitor['loopMonitor'].enable).toBeCalledTimes(1);
});
test('#collect returns event loop delays histogram', () => {
const eventLoopDelaysMonitor = new EventLoopDelaysMonitor();
expect(eventLoopDelaysMonitor['loopMonitor'].disable).toBeCalledTimes(0);
expect(eventLoopDelaysMonitor['loopMonitor'].enable).toBeCalledTimes(1);
const histogramData = eventLoopDelaysMonitor.collect();
expect(eventLoopDelaysMonitor['loopMonitor'].disable).toBeCalledTimes(1);
expect(eventLoopDelaysMonitor['loopMonitor'].enable).toBeCalledTimes(2);
expect(eventLoopDelaysMonitor['loopMonitor'].percentile).toHaveBeenNthCalledWith(1, 50);
expect(eventLoopDelaysMonitor['loopMonitor'].percentile).toHaveBeenNthCalledWith(2, 75);
expect(eventLoopDelaysMonitor['loopMonitor'].percentile).toHaveBeenNthCalledWith(3, 95);
expect(eventLoopDelaysMonitor['loopMonitor'].percentile).toHaveBeenNthCalledWith(4, 99);
// mocked perf_hook returns `mocked.createHistogram()`.
// This ensures that the wiring of the `collect` function is correct.
const mockedHistogram = mocked.createHistogram();
expect(histogramData).toEqual(mockedHistogram);
});
test('#reset resets histogram data', () => {
const eventLoopDelaysMonitor = new EventLoopDelaysMonitor();
eventLoopDelaysMonitor.reset();
expect(eventLoopDelaysMonitor['loopMonitor'].reset).toBeCalledTimes(1);
});
test('#stop disables monitoring event loop delays', () => {
const eventLoopDelaysMonitor = new EventLoopDelaysMonitor();
expect(eventLoopDelaysMonitor['loopMonitor'].disable).toBeCalledTimes(0);
eventLoopDelaysMonitor.stop();
expect(eventLoopDelaysMonitor['loopMonitor'].disable).toBeCalledTimes(1);
});
});

View file

@ -8,48 +8,41 @@
import type { EventLoopDelayMonitor } from 'perf_hooks';
import { monitorEventLoopDelay } from 'perf_hooks';
import { MONITOR_EVENT_LOOP_DELAYS_RESOLUTION } from './constants';
import type { IntervalHistogram } from '../types';
export interface IntervalHistogram {
fromTimestamp: string;
lastUpdatedAt: string;
min: number;
max: number;
mean: number;
exceeds: number;
stddev: number;
percentiles: {
50: number;
75: number;
95: number;
99: number;
};
}
export class EventLoopDelaysCollector {
export class EventLoopDelaysMonitor {
private readonly loopMonitor: EventLoopDelayMonitor;
private fromTimestamp: Date;
/**
* Creating a new instance from EventLoopDelaysMonitor will
* automatically start tracking event loop delays.
*/
constructor() {
const monitor = monitorEventLoopDelay({
resolution: MONITOR_EVENT_LOOP_DELAYS_RESOLUTION,
});
const monitor = monitorEventLoopDelay();
monitor.enable();
this.fromTimestamp = new Date();
this.loopMonitor = monitor;
}
/**
* Collect gathers event loop delays metrics from nodejs perf_hooks.monitorEventLoopDelay
* the histogram calculations start from the last time `reset` was called or this
* EventLoopDelaysMonitor instance was created.
* @returns {IntervalHistogram}
*/
public collect(): IntervalHistogram {
const lastUpdated = new Date();
this.loopMonitor.disable();
const { min, max, mean, exceeds, stddev } = this.loopMonitor;
return {
const collectedData: IntervalHistogram = {
min,
max,
mean,
exceeds,
stddev,
fromTimestamp: this.fromTimestamp.toISOString(),
lastUpdatedAt: new Date().toISOString(),
lastUpdatedAt: lastUpdated.toISOString(),
percentiles: {
50: this.loopMonitor.percentile(50),
75: this.loopMonitor.percentile(75),
@ -57,13 +50,22 @@ export class EventLoopDelaysCollector {
99: this.loopMonitor.percentile(99),
},
};
this.loopMonitor.enable();
return collectedData;
}
/**
* Resets the collected histogram data.
*/
public reset() {
this.loopMonitor.reset();
this.fromTimestamp = new Date();
}
/**
* Disables updating the interval timer for collecting new data points.
*/
public stop() {
this.loopMonitor.disable();
}

View file

@ -0,0 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export { EventLoopDelaysMonitor } from './event_loop_delays_monitor';

View file

@ -12,8 +12,10 @@ export type {
MetricsServiceSetup,
MetricsServiceStart,
OpsMetrics,
IntervalHistogram,
} from './types';
export type { OpsProcessMetrics, OpsServerMetrics, OpsOsMetrics } from './collectors';
export { MetricsService } from './metrics_service';
export { opsConfig } from './ops_config';
export type { OpsConfigType } from './ops_config';
export { EventLoopDelaysMonitor } from './event_loop_delays';

View file

@ -8,19 +8,15 @@
import { OpsMetrics } from '..';
import { getEcsOpsMetricsLog } from './get_ops_metrics_log';
import { collectorMock } from '../collectors/mocks';
function createBaseOpsMetrics(): OpsMetrics {
const mockProcess = collectorMock.createOpsProcessMetrics();
return {
collected_at: new Date('2020-01-01 01:00:00'),
process: {
memory: {
heap: { total_in_bytes: 1, used_in_bytes: 1, size_limit: 1 },
resident_set_size_in_bytes: 1,
},
event_loop_delay: 1,
pid: 1,
uptime_in_millis: 1,
},
process: mockProcess,
processes: [mockProcess],
os: {
platform: 'darwin' as const,
platformRelease: 'test',

View file

@ -8,8 +8,9 @@
import { BehaviorSubject } from 'rxjs';
import type { PublicMethodsOf } from '@kbn/utility-types';
import type { MetricsService } from './metrics_service';
import { collectorMock } from './collectors/mocks';
import { mocked as eventLoopDelaysMonitorMock } from './event_loop_delays/event_loop_delays_monitor.mocks';
import {
InternalMetricsServiceSetup,
InternalMetricsServiceStart,
@ -22,18 +23,14 @@ const createInternalSetupContractMock = () => {
collectionInterval: 30000,
getOpsMetrics$: jest.fn(),
};
const processMock = collectorMock.createOpsProcessMetrics();
setupContract.getOpsMetrics$.mockReturnValue(
new BehaviorSubject({
collected_at: new Date('2020-01-01 01:00:00'),
process: {
memory: {
heap: { total_in_bytes: 1, used_in_bytes: 1, size_limit: 1 },
resident_set_size_in_bytes: 1,
},
event_loop_delay: 1,
pid: 1,
uptime_in_millis: 1,
},
process: processMock,
processes: [processMock],
os: {
platform: 'darwin' as const,
platformRelease: 'test',
@ -81,4 +78,5 @@ export const metricsServiceMock = {
createStartContract: createStartContractMock,
createInternalSetupContract: createInternalSetupContractMock,
createInternalStartContract: createInternalStartContractMock,
createEventLoopDelaysMonitor: eventLoopDelaysMonitorMock.createEventLoopDelaysMonitor,
};

View file

@ -28,7 +28,7 @@ describe('OpsMetricsCollector', () => {
describe('#collect', () => {
it('gathers metrics from the underlying collectors', async () => {
mockOsCollector.collect.mockResolvedValue('osMetrics');
mockProcessCollector.collect.mockResolvedValue('processMetrics');
mockProcessCollector.collect.mockResolvedValue(['processMetrics']);
mockServerCollector.collect.mockResolvedValue({
requests: 'serverRequestsMetrics',
response_times: 'serverTimingMetrics',
@ -43,6 +43,7 @@ describe('OpsMetricsCollector', () => {
expect(metrics).toEqual({
collected_at: expect.any(Date),
process: 'processMetrics',
processes: ['processMetrics'],
os: 'osMetrics',
requests: 'serverRequestsMetrics',
response_times: 'serverTimingMetrics',

View file

@ -28,14 +28,21 @@ export class OpsMetricsCollector implements MetricsCollector<OpsMetrics> {
}
public async collect(): Promise<OpsMetrics> {
const [process, os, server] = await Promise.all([
const [processes, os, server] = await Promise.all([
this.processCollector.collect(),
this.osCollector.collect(),
this.serverCollector.collect(),
]);
return {
collected_at: new Date(),
process,
/**
* Kibana does not yet support multi-process nodes.
* `processes` is just an Array(1) only returning the current process's data
* which is why we can just use processes[0] for `process`
*/
process: processes[0],
processes,
os,
...server,
};

View file

@ -7,7 +7,7 @@
*/
import { Observable } from 'rxjs';
import { OpsProcessMetrics, OpsOsMetrics, OpsServerMetrics } from './collectors';
import type { OpsProcessMetrics, OpsOsMetrics, OpsServerMetrics } from './collectors';
/**
* APIs to retrieves metrics gathered and exposed by the core platform.
@ -51,8 +51,13 @@ export type InternalMetricsServiceStart = MetricsServiceStart;
export interface OpsMetrics {
/** Time metrics were recorded at. */
collected_at: Date;
/** Process related metrics */
/**
* Process related metrics.
* @deprecated use the processes field instead.
*/
process: OpsProcessMetrics;
/** Process related metrics. Reports an array of objects for each kibana pid.*/
processes: OpsProcessMetrics[];
/** OS related metrics */
os: OpsOsMetrics;
/** server response time stats */
@ -62,3 +67,37 @@ export interface OpsMetrics {
/** number of current concurrent connections to the server */
concurrent_connections: OpsServerMetrics['concurrent_connections'];
}
/**
* an IntervalHistogram object that samples and reports the event loop delay over time.
* The delays will be reported in nanoseconds.
*
* @public
*/
export interface IntervalHistogram {
// The first timestamp the interval timer kicked in for collecting data points.
fromTimestamp: string;
// Last timestamp the interval timer kicked in for collecting data points.
lastUpdatedAt: string;
// The minimum recorded event loop delay.
min: number;
// The maximum recorded event loop delay.
max: number;
// The mean of the recorded event loop delays.
mean: number;
// The number of times the event loop delay exceeded the maximum 1 hour event loop delay threshold.
exceeds: number;
// The standard deviation of the recorded event loop delays.
stddev: number;
// An object detailing the accumulated percentile distribution.
percentiles: {
// 50th percentile of delays of the collected data points.
50: number;
// 75th percentile of delays of the collected data points.
75: number;
// 95th percentile of delays of the collected data points.
95: number;
// 99th percentile of delays of the collected data points.
99: number;
};
}

View file

@ -0,0 +1,35 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import type { RegisterDeprecationsConfig } from '../../deprecations';
import type { ISavedObjectTypeRegistry } from '../saved_objects_type_registry';
import type { SavedObjectConfig } from '../saved_objects_config';
import type { KibanaConfigType } from '../../kibana_config';
import { getUnknownTypesDeprecations } from './unknown_object_types';
interface GetDeprecationProviderOptions {
typeRegistry: ISavedObjectTypeRegistry;
savedObjectsConfig: SavedObjectConfig;
kibanaConfig: KibanaConfigType;
kibanaVersion: string;
}
export const getSavedObjectsDeprecationsProvider = (
config: GetDeprecationProviderOptions
): RegisterDeprecationsConfig => {
return {
getDeprecations: async (context) => {
return [
...(await getUnknownTypesDeprecations({
...config,
esClient: context.esClient,
})),
];
},
};
};

View file

@ -0,0 +1,10 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export { getSavedObjectsDeprecationsProvider } from './deprecation_factory';
export { deleteUnknownTypeObjects } from './unknown_object_types';

View file

@ -6,9 +6,8 @@
* Side Public License, v 1.
*/
import { PluginInitializerContext } from 'kibana/public';
import { TableVisPlugin as Plugin } from './plugin';
export const getIndexForTypeMock = jest.fn();
export function plugin(initializerContext: PluginInitializerContext) {
return new Plugin(initializerContext);
}
jest.doMock('../service/lib/get_index_for_type', () => ({
getIndexForType: getIndexForTypeMock,
}));

View file

@ -0,0 +1,165 @@
/*
* Copyright 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 { getIndexForTypeMock } from './unknown_object_types.test.mocks';
import { estypes } from '@elastic/elasticsearch';
import { deleteUnknownTypeObjects, getUnknownTypesDeprecations } from './unknown_object_types';
import { typeRegistryMock } from '../saved_objects_type_registry.mock';
import { elasticsearchClientMock } from '../../elasticsearch/client/mocks';
import type { KibanaConfigType } from '../../kibana_config';
import type { SavedObjectConfig } from '../saved_objects_config';
import { SavedObjectsType } from 'kibana/server';
const createSearchResponse = (count: number): estypes.SearchResponse => {
return {
hits: {
total: count,
max_score: 0,
hits: new Array(count).fill({}),
},
} as estypes.SearchResponse;
};
describe('unknown saved object types deprecation', () => {
const kibanaVersion = '8.0.0';
let typeRegistry: ReturnType<typeof typeRegistryMock.create>;
let esClient: ReturnType<typeof elasticsearchClientMock.createScopedClusterClient>;
let kibanaConfig: KibanaConfigType;
let savedObjectsConfig: SavedObjectConfig;
beforeEach(() => {
typeRegistry = typeRegistryMock.create();
esClient = elasticsearchClientMock.createScopedClusterClient();
typeRegistry.getAllTypes.mockReturnValue([
{ name: 'foo' },
{ name: 'bar' },
] as SavedObjectsType[]);
getIndexForTypeMock.mockImplementation(({ type }: { type: string }) => `${type}-index`);
kibanaConfig = {
index: '.kibana',
enabled: true,
};
savedObjectsConfig = {
migration: {
enableV2: true,
},
} as SavedObjectConfig;
});
afterEach(() => {
getIndexForTypeMock.mockReset();
});
describe('getUnknownTypesDeprecations', () => {
beforeEach(() => {
esClient.asInternalUser.search.mockReturnValue(
elasticsearchClientMock.createSuccessTransportRequestPromise(createSearchResponse(0))
);
});
it('calls `esClient.asInternalUser.search` with the correct parameters', async () => {
await getUnknownTypesDeprecations({
savedObjectsConfig,
esClient,
typeRegistry,
kibanaConfig,
kibanaVersion,
});
expect(esClient.asInternalUser.search).toHaveBeenCalledTimes(1);
expect(esClient.asInternalUser.search).toHaveBeenCalledWith({
index: ['foo-index', 'bar-index'],
body: {
size: 10000,
query: {
bool: {
must_not: [{ term: { type: 'foo' } }, { term: { type: 'bar' } }],
},
},
},
});
});
it('returns no deprecation if no unknown type docs are found', async () => {
esClient.asInternalUser.search.mockReturnValue(
elasticsearchClientMock.createSuccessTransportRequestPromise(createSearchResponse(0))
);
const deprecations = await getUnknownTypesDeprecations({
savedObjectsConfig,
esClient,
typeRegistry,
kibanaConfig,
kibanaVersion,
});
expect(deprecations.length).toEqual(0);
});
it('returns a deprecation if any unknown type docs are found', async () => {
esClient.asInternalUser.search.mockReturnValue(
elasticsearchClientMock.createSuccessTransportRequestPromise(createSearchResponse(1))
);
const deprecations = await getUnknownTypesDeprecations({
savedObjectsConfig,
esClient,
typeRegistry,
kibanaConfig,
kibanaVersion,
});
expect(deprecations.length).toEqual(1);
expect(deprecations[0]).toEqual({
title: expect.any(String),
message: expect.any(String),
level: 'critical',
requireRestart: false,
deprecationType: undefined,
correctiveActions: {
manualSteps: expect.any(Array),
api: {
path: '/internal/saved_objects/deprecations/_delete_unknown_types',
method: 'POST',
body: {},
},
},
});
});
});
describe('deleteUnknownTypeObjects', () => {
it('calls `esClient.asInternalUser.search` with the correct parameters', async () => {
await deleteUnknownTypeObjects({
savedObjectsConfig,
esClient,
typeRegistry,
kibanaConfig,
kibanaVersion,
});
expect(esClient.asInternalUser.deleteByQuery).toHaveBeenCalledTimes(1);
expect(esClient.asInternalUser.deleteByQuery).toHaveBeenCalledWith({
index: ['foo-index', 'bar-index'],
wait_for_completion: false,
body: {
query: {
bool: {
must_not: [{ term: { type: 'foo' } }, { term: { type: 'bar' } }],
},
},
},
});
});
});
});

View file

@ -0,0 +1,172 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { estypes } from '@elastic/elasticsearch';
import { i18n } from '@kbn/i18n';
import type { DeprecationsDetails } from '../../deprecations';
import { IScopedClusterClient } from '../../elasticsearch';
import { ISavedObjectTypeRegistry } from '../saved_objects_type_registry';
import { SavedObjectsRawDocSource } from '../serialization';
import type { KibanaConfigType } from '../../kibana_config';
import type { SavedObjectConfig } from '../saved_objects_config';
import { getIndexForType } from '../service/lib';
interface UnknownTypesDeprecationOptions {
typeRegistry: ISavedObjectTypeRegistry;
esClient: IScopedClusterClient;
kibanaConfig: KibanaConfigType;
savedObjectsConfig: SavedObjectConfig;
kibanaVersion: string;
}
const getKnownTypes = (typeRegistry: ISavedObjectTypeRegistry) =>
typeRegistry.getAllTypes().map((type) => type.name);
const getTargetIndices = ({
types,
typeRegistry,
kibanaVersion,
kibanaConfig,
savedObjectsConfig,
}: {
types: string[];
typeRegistry: ISavedObjectTypeRegistry;
savedObjectsConfig: SavedObjectConfig;
kibanaConfig: KibanaConfigType;
kibanaVersion: string;
}) => {
return [
...new Set(
types.map((type) =>
getIndexForType({
type,
typeRegistry,
migV2Enabled: savedObjectsConfig.migration.enableV2,
kibanaVersion,
defaultIndex: kibanaConfig.index,
})
)
),
];
};
const getUnknownTypesQuery = (knownTypes: string[]): estypes.QueryDslQueryContainer => {
return {
bool: {
must_not: knownTypes.map((type) => ({
term: { type },
})),
},
};
};
const getUnknownSavedObjects = async ({
typeRegistry,
esClient,
kibanaConfig,
savedObjectsConfig,
kibanaVersion,
}: UnknownTypesDeprecationOptions) => {
const knownTypes = getKnownTypes(typeRegistry);
const targetIndices = getTargetIndices({
types: knownTypes,
typeRegistry,
kibanaConfig,
kibanaVersion,
savedObjectsConfig,
});
const query = getUnknownTypesQuery(knownTypes);
const { body } = await esClient.asInternalUser.search<SavedObjectsRawDocSource>({
index: targetIndices,
body: {
size: 10000,
query,
},
});
const { hits: unknownDocs } = body.hits;
return unknownDocs.map((doc) => ({ id: doc._id, type: doc._source?.type ?? 'unknown' }));
};
export const getUnknownTypesDeprecations = async (
options: UnknownTypesDeprecationOptions
): Promise<DeprecationsDetails[]> => {
const deprecations: DeprecationsDetails[] = [];
const unknownDocs = await getUnknownSavedObjects(options);
if (unknownDocs.length) {
deprecations.push({
title: i18n.translate('core.savedObjects.deprecations.unknownTypes.title', {
defaultMessage: 'Saved objects with unknown types are present in Kibana system indices',
}),
message: i18n.translate('core.savedObjects.deprecations.unknownTypes.message', {
defaultMessage:
'{objectCount, plural, one {# object} other {# objects}} with unknown types {objectCount, plural, one {was} other {were}} found in Kibana system indices. ' +
'Upgrading with unknown savedObject types is no longer supported. ' +
`To ensure that upgrades will succeed in the future, either re-enable plugins or delete these documents from the Kibana indices`,
values: {
objectCount: unknownDocs.length,
},
}),
level: 'critical',
requireRestart: false,
deprecationType: undefined, // not config nor feature...
correctiveActions: {
manualSteps: [
i18n.translate('core.savedObjects.deprecations.unknownTypes.manualSteps.1', {
defaultMessage: 'Enable disabled plugins then restart Kibana.',
}),
i18n.translate('core.savedObjects.deprecations.unknownTypes.manualSteps.2', {
defaultMessage:
'If no plugins are disabled, or if enabling them does not fix the issue, delete the documents.',
}),
],
api: {
path: '/internal/saved_objects/deprecations/_delete_unknown_types',
method: 'POST',
body: {},
},
},
});
}
return deprecations;
};
interface DeleteUnknownTypesOptions {
typeRegistry: ISavedObjectTypeRegistry;
esClient: IScopedClusterClient;
kibanaConfig: KibanaConfigType;
savedObjectsConfig: SavedObjectConfig;
kibanaVersion: string;
}
export const deleteUnknownTypeObjects = async ({
esClient,
typeRegistry,
kibanaConfig,
savedObjectsConfig,
kibanaVersion,
}: DeleteUnknownTypesOptions) => {
const knownTypes = getKnownTypes(typeRegistry);
const targetIndices = getTargetIndices({
types: knownTypes,
typeRegistry,
kibanaConfig,
kibanaVersion,
savedObjectsConfig,
});
const query = getUnknownTypesQuery(knownTypes);
await esClient.asInternalUser.deleteByQuery({
index: targetIndices,
wait_for_completion: false,
body: {
query,
},
});
};

View file

@ -0,0 +1,937 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`migrationsStateActionMachine logs state transitions, messages in state.logs and action responses when reaching DONE 1`] = `
Object {
"debug": Array [
Array [
"[.my-so-index] INIT RESPONSE",
Object {
"_tag": "Right",
"right": "response",
},
],
Array [
"[.my-so-index] INIT -> LEGACY_REINDEX. took: 0ms.",
Object {
"kibana": Object {
"migrations": Object {
"duration": 0,
"state": Object {
"batchSize": 1000,
"controlState": "LEGACY_REINDEX",
"currentAlias": ".my-so-index",
"excludeFromUpgradeFilterHooks": Object {},
"indexPrefix": ".my-so-index",
"kibanaVersion": "7.11.0",
"knownTypes": Array [],
"legacyIndex": ".my-so-index",
"logs": Array [
Object {
"level": "info",
"message": "Log from LEGACY_REINDEX control state",
},
],
"maxBatchSizeBytes": 100000000,
"outdatedDocuments": Array [],
"outdatedDocumentsQuery": Object {
"bool": Object {
"should": Array [],
},
},
"preMigrationScript": Object {
"_tag": "None",
},
"retryAttempts": 5,
"retryCount": 0,
"retryDelay": 0,
"targetIndexMappings": Object {
"properties": Object {},
},
"tempIndex": ".my-so-index_7.11.0_reindex_temp",
"tempIndexMappings": Object {
"dynamic": false,
"properties": Object {
"migrationVersion": Object {
"dynamic": "true",
"type": "object",
},
"type": Object {
"type": "keyword",
},
},
},
"transformedDocBatches": Array [],
"unusedTypesQuery": Object {
"bool": Object {
"must_not": Array [
Object {
"term": Object {
"type": "apm-services-telemetry",
},
},
Object {
"term": Object {
"type": "background-session",
},
},
Object {
"term": Object {
"type": "cases-sub-case",
},
},
Object {
"term": Object {
"type": "file-upload-telemetry",
},
},
Object {
"term": Object {
"type": "fleet-agent-events",
},
},
Object {
"term": Object {
"type": "ml-telemetry",
},
},
Object {
"term": Object {
"type": "osquery-usage-metric",
},
},
Object {
"term": Object {
"type": "server",
},
},
Object {
"term": Object {
"type": "timelion-sheet",
},
},
Object {
"term": Object {
"type": "tsvb-validation-telemetry",
},
},
Object {
"bool": Object {
"must": Array [
Object {
"match": Object {
"type": "search-session",
},
},
Object {
"match": Object {
"search-session.persisted": false,
},
},
],
},
},
],
},
},
"versionAlias": ".my-so-index_7.11.0",
"versionIndex": ".my-so-index_7.11.0_001",
},
},
},
},
],
Array [
"[.my-so-index] LEGACY_REINDEX RESPONSE",
Object {
"_tag": "Right",
"right": "response",
},
],
Array [
"[.my-so-index] LEGACY_REINDEX -> LEGACY_DELETE. took: 0ms.",
Object {
"kibana": Object {
"migrations": Object {
"duration": 0,
"state": Object {
"batchSize": 1000,
"controlState": "LEGACY_DELETE",
"currentAlias": ".my-so-index",
"excludeFromUpgradeFilterHooks": Object {},
"indexPrefix": ".my-so-index",
"kibanaVersion": "7.11.0",
"knownTypes": Array [],
"legacyIndex": ".my-so-index",
"logs": Array [
Object {
"level": "info",
"message": "Log from LEGACY_REINDEX control state",
},
Object {
"level": "info",
"message": "Log from LEGACY_DELETE control state",
},
],
"maxBatchSizeBytes": 100000000,
"outdatedDocuments": Array [],
"outdatedDocumentsQuery": Object {
"bool": Object {
"should": Array [],
},
},
"preMigrationScript": Object {
"_tag": "None",
},
"retryAttempts": 5,
"retryCount": 0,
"retryDelay": 0,
"targetIndexMappings": Object {
"properties": Object {},
},
"tempIndex": ".my-so-index_7.11.0_reindex_temp",
"tempIndexMappings": Object {
"dynamic": false,
"properties": Object {
"migrationVersion": Object {
"dynamic": "true",
"type": "object",
},
"type": Object {
"type": "keyword",
},
},
},
"transformedDocBatches": Array [],
"unusedTypesQuery": Object {
"bool": Object {
"must_not": Array [
Object {
"term": Object {
"type": "apm-services-telemetry",
},
},
Object {
"term": Object {
"type": "background-session",
},
},
Object {
"term": Object {
"type": "cases-sub-case",
},
},
Object {
"term": Object {
"type": "file-upload-telemetry",
},
},
Object {
"term": Object {
"type": "fleet-agent-events",
},
},
Object {
"term": Object {
"type": "ml-telemetry",
},
},
Object {
"term": Object {
"type": "osquery-usage-metric",
},
},
Object {
"term": Object {
"type": "server",
},
},
Object {
"term": Object {
"type": "timelion-sheet",
},
},
Object {
"term": Object {
"type": "tsvb-validation-telemetry",
},
},
Object {
"bool": Object {
"must": Array [
Object {
"match": Object {
"type": "search-session",
},
},
Object {
"match": Object {
"search-session.persisted": false,
},
},
],
},
},
],
},
},
"versionAlias": ".my-so-index_7.11.0",
"versionIndex": ".my-so-index_7.11.0_001",
},
},
},
},
],
Array [
"[.my-so-index] LEGACY_DELETE RESPONSE",
Object {
"_tag": "Right",
"right": "response",
},
],
Array [
"[.my-so-index] LEGACY_DELETE -> LEGACY_DELETE. took: 0ms.",
Object {
"kibana": Object {
"migrations": Object {
"duration": 0,
"state": Object {
"batchSize": 1000,
"controlState": "LEGACY_DELETE",
"currentAlias": ".my-so-index",
"excludeFromUpgradeFilterHooks": Object {},
"indexPrefix": ".my-so-index",
"kibanaVersion": "7.11.0",
"knownTypes": Array [],
"legacyIndex": ".my-so-index",
"logs": Array [
Object {
"level": "info",
"message": "Log from LEGACY_REINDEX control state",
},
Object {
"level": "info",
"message": "Log from LEGACY_DELETE control state",
},
Object {
"level": "info",
"message": "Log from LEGACY_DELETE control state",
},
],
"maxBatchSizeBytes": 100000000,
"outdatedDocuments": Array [],
"outdatedDocumentsQuery": Object {
"bool": Object {
"should": Array [],
},
},
"preMigrationScript": Object {
"_tag": "None",
},
"retryAttempts": 5,
"retryCount": 0,
"retryDelay": 0,
"targetIndexMappings": Object {
"properties": Object {},
},
"tempIndex": ".my-so-index_7.11.0_reindex_temp",
"tempIndexMappings": Object {
"dynamic": false,
"properties": Object {
"migrationVersion": Object {
"dynamic": "true",
"type": "object",
},
"type": Object {
"type": "keyword",
},
},
},
"transformedDocBatches": Array [],
"unusedTypesQuery": Object {
"bool": Object {
"must_not": Array [
Object {
"term": Object {
"type": "apm-services-telemetry",
},
},
Object {
"term": Object {
"type": "background-session",
},
},
Object {
"term": Object {
"type": "cases-sub-case",
},
},
Object {
"term": Object {
"type": "file-upload-telemetry",
},
},
Object {
"term": Object {
"type": "fleet-agent-events",
},
},
Object {
"term": Object {
"type": "ml-telemetry",
},
},
Object {
"term": Object {
"type": "osquery-usage-metric",
},
},
Object {
"term": Object {
"type": "server",
},
},
Object {
"term": Object {
"type": "timelion-sheet",
},
},
Object {
"term": Object {
"type": "tsvb-validation-telemetry",
},
},
Object {
"bool": Object {
"must": Array [
Object {
"match": Object {
"type": "search-session",
},
},
Object {
"match": Object {
"search-session.persisted": false,
},
},
],
},
},
],
},
},
"versionAlias": ".my-so-index_7.11.0",
"versionIndex": ".my-so-index_7.11.0_001",
},
},
},
},
],
Array [
"[.my-so-index] LEGACY_DELETE RESPONSE",
Object {
"_tag": "Right",
"right": "response",
},
],
Array [
"[.my-so-index] LEGACY_DELETE -> DONE. took: 0ms.",
Object {
"kibana": Object {
"migrations": Object {
"duration": 0,
"state": Object {
"batchSize": 1000,
"controlState": "DONE",
"currentAlias": ".my-so-index",
"excludeFromUpgradeFilterHooks": Object {},
"indexPrefix": ".my-so-index",
"kibanaVersion": "7.11.0",
"knownTypes": Array [],
"legacyIndex": ".my-so-index",
"logs": Array [
Object {
"level": "info",
"message": "Log from LEGACY_REINDEX control state",
},
Object {
"level": "info",
"message": "Log from LEGACY_DELETE control state",
},
Object {
"level": "info",
"message": "Log from LEGACY_DELETE control state",
},
Object {
"level": "info",
"message": "Log from DONE control state",
},
],
"maxBatchSizeBytes": 100000000,
"outdatedDocuments": Array [],
"outdatedDocumentsQuery": Object {
"bool": Object {
"should": Array [],
},
},
"preMigrationScript": Object {
"_tag": "None",
},
"retryAttempts": 5,
"retryCount": 0,
"retryDelay": 0,
"targetIndexMappings": Object {
"properties": Object {},
},
"tempIndex": ".my-so-index_7.11.0_reindex_temp",
"tempIndexMappings": Object {
"dynamic": false,
"properties": Object {
"migrationVersion": Object {
"dynamic": "true",
"type": "object",
},
"type": Object {
"type": "keyword",
},
},
},
"transformedDocBatches": Array [],
"unusedTypesQuery": Object {
"bool": Object {
"must_not": Array [
Object {
"term": Object {
"type": "apm-services-telemetry",
},
},
Object {
"term": Object {
"type": "background-session",
},
},
Object {
"term": Object {
"type": "cases-sub-case",
},
},
Object {
"term": Object {
"type": "file-upload-telemetry",
},
},
Object {
"term": Object {
"type": "fleet-agent-events",
},
},
Object {
"term": Object {
"type": "ml-telemetry",
},
},
Object {
"term": Object {
"type": "osquery-usage-metric",
},
},
Object {
"term": Object {
"type": "server",
},
},
Object {
"term": Object {
"type": "timelion-sheet",
},
},
Object {
"term": Object {
"type": "tsvb-validation-telemetry",
},
},
Object {
"bool": Object {
"must": Array [
Object {
"match": Object {
"type": "search-session",
},
},
Object {
"match": Object {
"search-session.persisted": false,
},
},
],
},
},
],
},
},
"versionAlias": ".my-so-index_7.11.0",
"versionIndex": ".my-so-index_7.11.0_001",
},
},
},
},
],
],
"error": Array [],
"fatal": Array [],
"info": Array [
Array [
"[.my-so-index] Log from LEGACY_REINDEX control state",
],
Array [
"[.my-so-index] INIT -> LEGACY_REINDEX. took: 0ms.",
],
Array [
"[.my-so-index] Log from LEGACY_DELETE control state",
],
Array [
"[.my-so-index] LEGACY_REINDEX -> LEGACY_DELETE. took: 0ms.",
],
Array [
"[.my-so-index] Log from LEGACY_DELETE control state",
],
Array [
"[.my-so-index] LEGACY_DELETE -> LEGACY_DELETE. took: 0ms.",
],
Array [
"[.my-so-index] Log from DONE control state",
],
Array [
"[.my-so-index] LEGACY_DELETE -> DONE. took: 0ms.",
],
],
"log": Array [],
"trace": Array [],
"warn": Array [],
}
`;
exports[`migrationsStateActionMachine logs state transitions, messages in state.logs and action responses when reaching FATAL 1`] = `
Object {
"debug": Array [
Array [
"[.my-so-index] INIT RESPONSE",
Object {
"_tag": "Right",
"right": "response",
},
],
Array [
"[.my-so-index] INIT -> LEGACY_DELETE. took: 0ms.",
Object {
"kibana": Object {
"migrations": Object {
"duration": 0,
"state": Object {
"batchSize": 1000,
"controlState": "LEGACY_DELETE",
"currentAlias": ".my-so-index",
"excludeFromUpgradeFilterHooks": Object {},
"indexPrefix": ".my-so-index",
"kibanaVersion": "7.11.0",
"knownTypes": Array [],
"legacyIndex": ".my-so-index",
"logs": Array [
Object {
"level": "info",
"message": "Log from LEGACY_DELETE control state",
},
],
"maxBatchSizeBytes": 100000000,
"outdatedDocuments": Array [
Object {
"_id": "1234",
},
],
"outdatedDocumentsQuery": Object {
"bool": Object {
"should": Array [],
},
},
"preMigrationScript": Object {
"_tag": "None",
},
"reason": "the fatal reason",
"retryAttempts": 5,
"retryCount": 0,
"retryDelay": 0,
"targetIndexMappings": Object {
"properties": Object {},
},
"tempIndex": ".my-so-index_7.11.0_reindex_temp",
"tempIndexMappings": Object {
"dynamic": false,
"properties": Object {
"migrationVersion": Object {
"dynamic": "true",
"type": "object",
},
"type": Object {
"type": "keyword",
},
},
},
"transformedDocBatches": Array [
Array [
Object {
"_id": "1234",
},
],
],
"unusedTypesQuery": Object {
"bool": Object {
"must_not": Array [
Object {
"term": Object {
"type": "apm-services-telemetry",
},
},
Object {
"term": Object {
"type": "background-session",
},
},
Object {
"term": Object {
"type": "cases-sub-case",
},
},
Object {
"term": Object {
"type": "file-upload-telemetry",
},
},
Object {
"term": Object {
"type": "fleet-agent-events",
},
},
Object {
"term": Object {
"type": "ml-telemetry",
},
},
Object {
"term": Object {
"type": "osquery-usage-metric",
},
},
Object {
"term": Object {
"type": "server",
},
},
Object {
"term": Object {
"type": "timelion-sheet",
},
},
Object {
"term": Object {
"type": "tsvb-validation-telemetry",
},
},
Object {
"bool": Object {
"must": Array [
Object {
"match": Object {
"type": "search-session",
},
},
Object {
"match": Object {
"search-session.persisted": false,
},
},
],
},
},
],
},
},
"versionAlias": ".my-so-index_7.11.0",
"versionIndex": ".my-so-index_7.11.0_001",
},
},
},
},
],
Array [
"[.my-so-index] LEGACY_DELETE RESPONSE",
Object {
"_tag": "Right",
"right": "response",
},
],
Array [
"[.my-so-index] LEGACY_DELETE -> FATAL. took: 0ms.",
Object {
"kibana": Object {
"migrations": Object {
"duration": 0,
"state": Object {
"batchSize": 1000,
"controlState": "FATAL",
"currentAlias": ".my-so-index",
"excludeFromUpgradeFilterHooks": Object {},
"indexPrefix": ".my-so-index",
"kibanaVersion": "7.11.0",
"knownTypes": Array [],
"legacyIndex": ".my-so-index",
"logs": Array [
Object {
"level": "info",
"message": "Log from LEGACY_DELETE control state",
},
Object {
"level": "info",
"message": "Log from FATAL control state",
},
],
"maxBatchSizeBytes": 100000000,
"outdatedDocuments": Array [
Object {
"_id": "1234",
},
],
"outdatedDocumentsQuery": Object {
"bool": Object {
"should": Array [],
},
},
"preMigrationScript": Object {
"_tag": "None",
},
"reason": "the fatal reason",
"retryAttempts": 5,
"retryCount": 0,
"retryDelay": 0,
"targetIndexMappings": Object {
"properties": Object {},
},
"tempIndex": ".my-so-index_7.11.0_reindex_temp",
"tempIndexMappings": Object {
"dynamic": false,
"properties": Object {
"migrationVersion": Object {
"dynamic": "true",
"type": "object",
},
"type": Object {
"type": "keyword",
},
},
},
"transformedDocBatches": Array [
Array [
Object {
"_id": "1234",
},
],
],
"unusedTypesQuery": Object {
"bool": Object {
"must_not": Array [
Object {
"term": Object {
"type": "apm-services-telemetry",
},
},
Object {
"term": Object {
"type": "background-session",
},
},
Object {
"term": Object {
"type": "cases-sub-case",
},
},
Object {
"term": Object {
"type": "file-upload-telemetry",
},
},
Object {
"term": Object {
"type": "fleet-agent-events",
},
},
Object {
"term": Object {
"type": "ml-telemetry",
},
},
Object {
"term": Object {
"type": "osquery-usage-metric",
},
},
Object {
"term": Object {
"type": "server",
},
},
Object {
"term": Object {
"type": "timelion-sheet",
},
},
Object {
"term": Object {
"type": "tsvb-validation-telemetry",
},
},
Object {
"bool": Object {
"must": Array [
Object {
"match": Object {
"type": "search-session",
},
},
Object {
"match": Object {
"search-session.persisted": false,
},
},
],
},
},
],
},
},
"versionAlias": ".my-so-index_7.11.0",
"versionIndex": ".my-so-index_7.11.0_001",
},
},
},
},
],
],
"error": Array [],
"fatal": Array [],
"info": Array [
Array [
"[.my-so-index] Log from LEGACY_DELETE control state",
],
Array [
"[.my-so-index] INIT -> LEGACY_DELETE. took: 0ms.",
],
Array [
"[.my-so-index] Log from FATAL control state",
],
Array [
"[.my-so-index] LEGACY_DELETE -> FATAL. took: 0ms.",
],
],
"log": Array [],
"trace": Array [],
"warn": Array [],
}
`;

View file

@ -44,6 +44,7 @@ function createRoot() {
{
name: 'root',
appenders: ['file'],
level: 'debug', // DEBUG logs are required to retrieve the PIT _id from the action response logs
},
],
},

View file

@ -7,9 +7,7 @@
*/
import Path from 'path';
import Fs from 'fs';
import Util from 'util';
import glob from 'glob';
import del from 'del';
import { kibanaServerTestUser } from '@kbn/test';
import { kibanaPackageJson as pkg } from '@kbn/utils';
import * as kbnTestServer from '../../../../test_helpers/kbn_server';
@ -18,15 +16,8 @@ import { Root } from '../../../root';
const LOG_FILE_PREFIX = 'migration_test_multiple_es_nodes';
const asyncUnlink = Util.promisify(Fs.unlink);
async function removeLogFile() {
glob(Path.join(__dirname, `${LOG_FILE_PREFIX}_*.log`), (err, files) => {
files.forEach(async (file) => {
// ignore errors if it doesn't exist
await asyncUnlink(file).catch(() => void 0);
});
});
await del([Path.join(__dirname, `${LOG_FILE_PREFIX}_*.log`)], { force: true });
}
function extractSortNumberFromId(id: string): number {

View file

@ -7,9 +7,7 @@
*/
import Path from 'path';
import Fs from 'fs';
import Util from 'util';
import glob from 'glob';
import del from 'del';
import { esTestConfig, kibanaServerTestUser } from '@kbn/test';
import { kibanaPackageJson as pkg } from '@kbn/utils';
import * as kbnTestServer from '../../../../test_helpers/kbn_server';
@ -19,15 +17,8 @@ import type { Root } from '../../../root';
const LOG_FILE_PREFIX = 'migration_test_multiple_kibana_nodes';
const asyncUnlink = Util.promisify(Fs.unlink);
async function removeLogFiles() {
glob(Path.join(__dirname, `${LOG_FILE_PREFIX}_*.log`), (err, files) => {
files.forEach(async (file) => {
// ignore errors if it doesn't exist
await asyncUnlink(file).catch(() => void 0);
});
});
await del([Path.join(__dirname, `${LOG_FILE_PREFIX}_*.log`)], { force: true });
}
function extractSortNumberFromId(id: string): number {

View file

@ -77,7 +77,7 @@ describe('migrationsStateActionMachine', () => {
};
};
it('logs state transitions, messages in state.logs and action responses', async () => {
it('logs state transitions, messages in state.logs and action responses when reaching DONE', async () => {
await migrationStateActionMachine({
initialState,
logger: mockLogger.get(),
@ -88,71 +88,23 @@ describe('migrationsStateActionMachine', () => {
const logs = loggingSystemMock.collect(mockLogger);
const doneLog = logs.info.splice(8, 1)[0][0];
expect(doneLog).toMatch(/\[.my-so-index\] Migration completed after \d+ms/);
expect(logs).toMatchInlineSnapshot(`
Object {
"debug": Array [
Array [
"[.my-so-index] INIT RESPONSE",
Object {
"_tag": "Right",
"right": "response",
},
],
Array [
"[.my-so-index] LEGACY_REINDEX RESPONSE",
Object {
"_tag": "Right",
"right": "response",
},
],
Array [
"[.my-so-index] LEGACY_DELETE RESPONSE",
Object {
"_tag": "Right",
"right": "response",
},
],
Array [
"[.my-so-index] LEGACY_DELETE RESPONSE",
Object {
"_tag": "Right",
"right": "response",
},
],
],
"error": Array [],
"fatal": Array [],
"info": Array [
Array [
"[.my-so-index] Log from LEGACY_REINDEX control state",
],
Array [
"[.my-so-index] INIT -> LEGACY_REINDEX. took: 0ms.",
],
Array [
"[.my-so-index] Log from LEGACY_DELETE control state",
],
Array [
"[.my-so-index] LEGACY_REINDEX -> LEGACY_DELETE. took: 0ms.",
],
Array [
"[.my-so-index] Log from LEGACY_DELETE control state",
],
Array [
"[.my-so-index] LEGACY_DELETE -> LEGACY_DELETE. took: 0ms.",
],
Array [
"[.my-so-index] Log from DONE control state",
],
Array [
"[.my-so-index] LEGACY_DELETE -> DONE. took: 0ms.",
],
],
"log": Array [],
"trace": Array [],
"warn": Array [],
}
`);
expect(logs).toMatchSnapshot();
});
it('logs state transitions, messages in state.logs and action responses when reaching FATAL', async () => {
await migrationStateActionMachine({
initialState: {
...initialState,
reason: 'the fatal reason',
outdatedDocuments: [{ _id: '1234', password: 'sensitive password' }],
transformedDocBatches: [[{ _id: '1234', password: 'sensitive transformed password' }]],
} as State,
logger: mockLogger.get(),
model: transitionModel(['LEGACY_DELETE', 'FATAL']),
next,
client: esClient,
}).catch((err) => err);
expect(loggingSystemMock.collect(mockLogger)).toMatchSnapshot();
});
// see https://github.com/elastic/kibana/issues/98406
@ -196,6 +148,7 @@ describe('migrationsStateActionMachine', () => {
})
).resolves.toEqual(expect.anything());
});
it('resolves with migrated status if some sourceIndex in the DONE state', async () => {
await expect(
migrationStateActionMachine({
@ -207,6 +160,7 @@ describe('migrationsStateActionMachine', () => {
})
).resolves.toEqual(expect.objectContaining({ status: 'migrated' }));
});
it('resolves with patched status if none sourceIndex in the DONE state', async () => {
await expect(
migrationStateActionMachine({
@ -218,6 +172,7 @@ describe('migrationsStateActionMachine', () => {
})
).resolves.toEqual(expect.objectContaining({ status: 'patched' }));
});
it('rejects with error message when reaching the FATAL state', async () => {
await expect(
migrationStateActionMachine({
@ -231,127 +186,8 @@ describe('migrationsStateActionMachine', () => {
`[Error: Unable to complete saved object migrations for the [.my-so-index] index: the fatal reason]`
);
});
it('logs all state transitions and action responses when reaching the FATAL state', async () => {
await migrationStateActionMachine({
initialState: {
...initialState,
reason: 'the fatal reason',
outdatedDocuments: [{ _id: '1234', password: 'sensitive password' }],
transformedDocBatches: [[{ _id: '1234', password: 'sensitive transformed password' }]],
} as State,
logger: mockLogger.get(),
model: transitionModel(['LEGACY_DELETE', 'FATAL']),
next,
client: esClient,
}).catch((err) => err);
// Ignore the first 4 log entries that come from our model
const executionLogLogs = loggingSystemMock.collect(mockLogger).info.slice(4);
expect(executionLogLogs).toEqual([
[
'[.my-so-index] INIT RESPONSE',
{
_tag: 'Right',
right: 'response',
},
],
[
'[.my-so-index] INIT -> LEGACY_DELETE',
{
kibana: {
migrationState: {
batchSize: 1000,
maxBatchSizeBytes: 1e8,
controlState: 'LEGACY_DELETE',
currentAlias: '.my-so-index',
excludeFromUpgradeFilterHooks: {},
indexPrefix: '.my-so-index',
kibanaVersion: '7.11.0',
knownTypes: [],
legacyIndex: '.my-so-index',
logs: [
{
level: 'info',
message: 'Log from LEGACY_DELETE control state',
},
],
outdatedDocuments: [{ _id: '1234' }],
outdatedDocumentsQuery: expect.any(Object),
preMigrationScript: {
_tag: 'None',
},
reason: 'the fatal reason',
retryAttempts: 5,
retryCount: 0,
retryDelay: 0,
targetIndexMappings: {
properties: {},
},
tempIndex: '.my-so-index_7.11.0_reindex_temp',
tempIndexMappings: expect.any(Object),
transformedDocBatches: [[{ _id: '1234' }]],
unusedTypesQuery: expect.any(Object),
versionAlias: '.my-so-index_7.11.0',
versionIndex: '.my-so-index_7.11.0_001',
},
},
},
],
[
'[.my-so-index] LEGACY_DELETE RESPONSE',
{
_tag: 'Right',
right: 'response',
},
],
[
'[.my-so-index] LEGACY_DELETE -> FATAL',
{
kibana: {
migrationState: {
batchSize: 1000,
maxBatchSizeBytes: 1e8,
controlState: 'FATAL',
currentAlias: '.my-so-index',
excludeFromUpgradeFilterHooks: {},
indexPrefix: '.my-so-index',
kibanaVersion: '7.11.0',
knownTypes: [],
legacyIndex: '.my-so-index',
logs: [
{
level: 'info',
message: 'Log from LEGACY_DELETE control state',
},
{
level: 'info',
message: 'Log from FATAL control state',
},
],
outdatedDocuments: [{ _id: '1234' }],
outdatedDocumentsQuery: expect.any(Object),
preMigrationScript: {
_tag: 'None',
},
reason: 'the fatal reason',
retryAttempts: 5,
retryCount: 0,
retryDelay: 0,
targetIndexMappings: {
properties: {},
},
tempIndex: '.my-so-index_7.11.0_reindex_temp',
tempIndexMappings: expect.any(Object),
transformedDocBatches: [[{ _id: '1234' }]],
unusedTypesQuery: expect.any(Object),
versionAlias: '.my-so-index_7.11.0',
versionIndex: '.my-so-index_7.11.0_001',
},
},
},
],
]);
});
it('rejects and logs the error when an action throws with an ResponseError', async () => {
it('rejects and logs the error when an action throws with a ResponseError', async () => {
await expect(
migrationStateActionMachine({
initialState: { ...initialState, reason: 'the fatal reason' } as State,
@ -384,9 +220,6 @@ describe('migrationsStateActionMachine', () => {
Array [
"[.my-so-index] Unexpected Elasticsearch ResponseError: statusCode: 200, method: POST, url: /mock error: [snapshot_in_progress_exception]: Cannot delete indices that are being snapshotted,",
],
Array [
"[.my-so-index] migration failed, dumping execution log:",
],
],
"fatal": Array [],
"info": Array [],
@ -417,9 +250,6 @@ describe('migrationsStateActionMachine', () => {
Array [
[Error: this action throws],
],
Array [
"[.my-so-index] migration failed, dumping execution log:",
],
],
"fatal": Array [],
"info": Array [],
@ -429,116 +259,6 @@ describe('migrationsStateActionMachine', () => {
}
`);
});
it('logs all state transitions and action responses when an action throws', async () => {
try {
await migrationStateActionMachine({
initialState: { ...initialState, reason: 'the fatal reason' } as State,
logger: mockLogger.get(),
model: transitionModel(['LEGACY_REINDEX', 'LEGACY_DELETE', 'FATAL']),
next: (state) => {
if (state.controlState === 'LEGACY_DELETE') throw new Error('this action throws');
return () => Promise.resolve('hello');
},
client: esClient,
});
} catch (e) {
/** ignore */
}
// Ignore the first 4 log entries that come from our model
const executionLogLogs = loggingSystemMock.collect(mockLogger).info.slice(4);
expect(executionLogLogs).toEqual([
['[.my-so-index] INIT RESPONSE', 'hello'],
[
'[.my-so-index] INIT -> LEGACY_REINDEX',
{
kibana: {
migrationState: {
batchSize: 1000,
maxBatchSizeBytes: 1e8,
controlState: 'LEGACY_REINDEX',
currentAlias: '.my-so-index',
excludeFromUpgradeFilterHooks: {},
indexPrefix: '.my-so-index',
kibanaVersion: '7.11.0',
knownTypes: [],
legacyIndex: '.my-so-index',
logs: [
{
level: 'info',
message: 'Log from LEGACY_REINDEX control state',
},
],
outdatedDocuments: [],
outdatedDocumentsQuery: expect.any(Object),
preMigrationScript: {
_tag: 'None',
},
reason: 'the fatal reason',
retryAttempts: 5,
retryCount: 0,
retryDelay: 0,
targetIndexMappings: {
properties: {},
},
tempIndex: '.my-so-index_7.11.0_reindex_temp',
tempIndexMappings: expect.any(Object),
transformedDocBatches: [],
unusedTypesQuery: expect.any(Object),
versionAlias: '.my-so-index_7.11.0',
versionIndex: '.my-so-index_7.11.0_001',
},
},
},
],
['[.my-so-index] LEGACY_REINDEX RESPONSE', 'hello'],
[
'[.my-so-index] LEGACY_REINDEX -> LEGACY_DELETE',
{
kibana: {
migrationState: {
batchSize: 1000,
maxBatchSizeBytes: 1e8,
controlState: 'LEGACY_DELETE',
currentAlias: '.my-so-index',
excludeFromUpgradeFilterHooks: {},
indexPrefix: '.my-so-index',
kibanaVersion: '7.11.0',
knownTypes: [],
legacyIndex: '.my-so-index',
logs: [
{
level: 'info',
message: 'Log from LEGACY_REINDEX control state',
},
{
level: 'info',
message: 'Log from LEGACY_DELETE control state',
},
],
outdatedDocuments: [],
outdatedDocumentsQuery: expect.any(Object),
preMigrationScript: {
_tag: 'None',
},
reason: 'the fatal reason',
retryAttempts: 5,
retryCount: 0,
retryDelay: 0,
targetIndexMappings: {
properties: {},
},
tempIndex: '.my-so-index_7.11.0_reindex_temp',
tempIndexMappings: expect.any(Object),
transformedDocBatches: [],
unusedTypesQuery: expect.any(Object),
versionAlias: '.my-so-index_7.11.0',
versionIndex: '.my-so-index_7.11.0_001',
},
},
},
],
]);
});
describe('cleanup', () => {
beforeEach(() => {
cleanupMock.mockClear();

View file

@ -16,41 +16,24 @@ import { cleanup } from './migrations_state_machine_cleanup';
import { ReindexSourceToTempIndex, ReindexSourceToTempIndexBulk, State } from './types';
import { SavedObjectsRawDoc } from '../serialization';
interface StateLogMeta extends LogMeta {
interface StateTransitionLogMeta extends LogMeta {
kibana: {
migrationState: State;
migrations: {
state: State;
duration: number;
};
};
}
/** @internal */
export type ExecutionLog = Array<
| {
type: 'transition';
prevControlState: State['controlState'];
controlState: State['controlState'];
state: State;
}
| {
type: 'response';
controlState: State['controlState'];
res: unknown;
}
| {
type: 'cleanup';
state: State;
message: string;
}
>;
const logStateTransition = (
logger: Logger,
logMessagePrefix: string,
oldState: State,
newState: State,
prevState: State,
currState: State,
tookMs: number
) => {
if (newState.logs.length > oldState.logs.length) {
newState.logs.slice(oldState.logs.length).forEach(({ message, level }) => {
if (currState.logs.length > prevState.logs.length) {
currState.logs.slice(prevState.logs.length).forEach(({ message, level }) => {
switch (level) {
case 'error':
return logger.error(logMessagePrefix + message);
@ -65,7 +48,18 @@ const logStateTransition = (
}
logger.info(
logMessagePrefix + `${oldState.controlState} -> ${newState.controlState}. took: ${tookMs}ms.`
logMessagePrefix + `${prevState.controlState} -> ${currState.controlState}. took: ${tookMs}ms.`
);
logger.debug<StateTransitionLogMeta>(
logMessagePrefix + `${prevState.controlState} -> ${currState.controlState}. took: ${tookMs}ms.`,
{
kibana: {
migrations: {
state: currState,
duration: tookMs,
},
},
}
);
};
@ -77,24 +71,6 @@ const logActionResponse = (
) => {
logger.debug(logMessagePrefix + `${state.controlState} RESPONSE`, res as LogMeta);
};
const dumpExecutionLog = (logger: Logger, logMessagePrefix: string, executionLog: ExecutionLog) => {
logger.error(logMessagePrefix + 'migration failed, dumping execution log:');
executionLog.forEach((log) => {
if (log.type === 'transition') {
logger.info<StateLogMeta>(
logMessagePrefix + `${log.prevControlState} -> ${log.controlState}`,
{
kibana: {
migrationState: log.state,
},
}
);
}
if (log.type === 'response') {
logger.info(logMessagePrefix + `${log.controlState} RESPONSE`, log.res as LogMeta);
}
});
};
/**
* A specialized migrations-specific state-action machine that:
@ -118,7 +94,6 @@ export async function migrationStateActionMachine({
model: Model<State>;
client: ElasticsearchClient;
}) {
const executionLog: ExecutionLog = [];
const startTime = Date.now();
// Since saved object index names usually start with a `.` and can be
// configured by users to include several `.`'s we can't use a logger tag to
@ -132,11 +107,6 @@ export async function migrationStateActionMachine({
(state) => next(state),
(state, res) => {
lastState = state;
executionLog.push({
type: 'response',
res,
controlState: state.controlState,
});
logActionResponse(logger, logMessagePrefix, state, res);
const newState = model(state, res);
// Redact the state to reduce the memory consumption and so that we
@ -158,12 +128,7 @@ export async function migrationStateActionMachine({
).map((batches) => batches.map((doc) => ({ _id: doc._id }))) as [SavedObjectsRawDoc[]],
},
};
executionLog.push({
type: 'transition',
state: redactedNewState,
controlState: newState.controlState,
prevControlState: state.controlState,
});
const now = Date.now();
logStateTransition(
logger,
@ -195,8 +160,11 @@ export async function migrationStateActionMachine({
};
}
} else if (finalState.controlState === 'FATAL') {
await cleanup(client, executionLog, finalState);
dumpExecutionLog(logger, logMessagePrefix, executionLog);
try {
await cleanup(client, finalState);
} catch (e) {
logger.warn('Failed to cleanup after migrations:', e.message);
}
return Promise.reject(
new Error(
`Unable to complete saved object migrations for the [${initialState.indexPrefix}] index: ` +
@ -207,7 +175,11 @@ export async function migrationStateActionMachine({
throw new Error('Invalid terminating control state');
}
} catch (e) {
await cleanup(client, executionLog, lastState);
try {
await cleanup(client, lastState);
} catch (err) {
logger.warn('Failed to cleanup after migrations:', err.message);
}
if (e instanceof EsErrors.ResponseError) {
// Log the failed request. This is very similar to the
// elasticsearch-service's debug logs, but we log everything in single
@ -219,15 +191,12 @@ export async function migrationStateActionMachine({
req.statusCode
}, method: ${req.method}, url: ${req.url} error: ${getErrorMessage(e)},`;
logger.error(logMessagePrefix + failedRequestMessage);
dumpExecutionLog(logger, logMessagePrefix, executionLog);
throw new Error(
`Unable to complete saved object migrations for the [${initialState.indexPrefix}] index. Please check the health of your Elasticsearch cluster and try again. ${failedRequestMessage}`
);
} else {
logger.error(e);
dumpExecutionLog(logger, logMessagePrefix, executionLog);
const newError = new Error(
`Unable to complete saved object migrations for the [${initialState.indexPrefix}] index. ${e}`
);

View file

@ -9,23 +9,10 @@
import type { ElasticsearchClient } from '../../elasticsearch';
import * as Actions from './actions';
import type { State } from './types';
import type { ExecutionLog } from './migrations_state_action_machine';
export async function cleanup(
client: ElasticsearchClient,
executionLog: ExecutionLog,
state?: State
) {
export async function cleanup(client: ElasticsearchClient, state?: State) {
if (!state) return;
if ('sourceIndexPitId' in state) {
try {
await Actions.closePit({ client, pitId: state.sourceIndexPitId })();
} catch (e) {
executionLog.push({
type: 'cleanup',
state,
message: e.message,
});
}
await Actions.closePit({ client, pitId: state.sourceIndexPitId })();
}
}

View file

@ -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 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 { IRouter } from '../../../http';
import { catchAndReturnBoomErrors } from '../utils';
import { deleteUnknownTypeObjects } from '../../deprecations';
import { SavedObjectConfig } from '../../saved_objects_config';
import { KibanaConfigType } from '../../../kibana_config';
interface RouteDependencies {
config: SavedObjectConfig;
kibanaConfig: KibanaConfigType;
kibanaVersion: string;
}
export const registerDeleteUnknownTypesRoute = (
router: IRouter,
{ config, kibanaConfig, kibanaVersion }: RouteDependencies
) => {
router.post(
{
path: '/deprecations/_delete_unknown_types',
validate: false,
},
catchAndReturnBoomErrors(async (context, req, res) => {
await deleteUnknownTypeObjects({
esClient: context.core.elasticsearch.client,
typeRegistry: context.core.savedObjects.typeRegistry,
savedObjectsConfig: config,
kibanaConfig,
kibanaVersion,
});
return res.ok({
body: {
success: true,
},
});
})
);
};

View file

@ -0,0 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export { registerDeleteUnknownTypesRoute } from './delete_unknown_types';

View file

@ -26,6 +26,8 @@ import { registerResolveImportErrorsRoute } from './resolve_import_errors';
import { registerMigrateRoute } from './migrate';
import { registerLegacyImportRoute } from './legacy_import_export/import';
import { registerLegacyExportRoute } from './legacy_import_export/export';
import { registerDeleteUnknownTypesRoute } from './deprecations';
import { KibanaConfigType } from '../../kibana_config';
export function registerRoutes({
http,
@ -34,6 +36,7 @@ export function registerRoutes({
config,
migratorPromise,
kibanaVersion,
kibanaConfig,
}: {
http: InternalHttpServiceSetup;
coreUsageData: InternalCoreUsageDataSetup;
@ -41,6 +44,7 @@ export function registerRoutes({
config: SavedObjectConfig;
migratorPromise: Promise<IKibanaMigrator>;
kibanaVersion: string;
kibanaConfig: KibanaConfigType;
}) {
const router = http.createRouter('/api/saved_objects/');
@ -68,4 +72,5 @@ export function registerRoutes({
const internalRouter = http.createRouter('/internal/saved_objects/');
registerMigrateRoute(internalRouter, migratorPromise);
registerDeleteUnknownTypesRoute(internalRouter, { config, kibanaConfig, kibanaVersion });
}

View file

@ -0,0 +1,93 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 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 supertest from 'supertest';
import { UnwrapPromise } from '@kbn/utility-types';
import { registerDeleteUnknownTypesRoute } from '../deprecations';
import { elasticsearchServiceMock } from '../../../../../core/server/elasticsearch/elasticsearch_service.mock';
import { typeRegistryMock } from '../../saved_objects_type_registry.mock';
import { setupServer } from '../test_utils';
import { KibanaConfigType } from '../../../kibana_config';
import { SavedObjectConfig } from '../../saved_objects_config';
import { SavedObjectsType } from 'kibana/server';
type SetupServerReturn = UnwrapPromise<ReturnType<typeof setupServer>>;
describe('POST /internal/saved_objects/deprecations/_delete_unknown_types', () => {
const kibanaVersion = '8.0.0';
const kibanaConfig: KibanaConfigType = {
enabled: true,
index: '.kibana',
};
const config: SavedObjectConfig = {
maxImportExportSize: 10000,
maxImportPayloadBytes: 24000000,
migration: {
enableV2: true,
} as SavedObjectConfig['migration'],
};
let server: SetupServerReturn['server'];
let httpSetup: SetupServerReturn['httpSetup'];
let handlerContext: SetupServerReturn['handlerContext'];
let typeRegistry: ReturnType<typeof typeRegistryMock.create>;
let elasticsearchClient: ReturnType<typeof elasticsearchServiceMock.createScopedClusterClient>;
beforeEach(async () => {
({ server, httpSetup, handlerContext } = await setupServer());
elasticsearchClient = elasticsearchServiceMock.createScopedClusterClient();
typeRegistry = typeRegistryMock.create();
typeRegistry.getAllTypes.mockReturnValue([{ name: 'known-type' } as SavedObjectsType]);
typeRegistry.getIndex.mockImplementation((type) => `${type}-index`);
handlerContext.savedObjects.typeRegistry = typeRegistry;
handlerContext.elasticsearch.client.asCurrentUser = elasticsearchClient.asCurrentUser;
handlerContext.elasticsearch.client.asInternalUser = elasticsearchClient.asInternalUser;
const router = httpSetup.createRouter('/internal/saved_objects/');
registerDeleteUnknownTypesRoute(router, {
kibanaVersion,
kibanaConfig,
config,
});
await server.start();
});
afterEach(async () => {
await server.stop();
});
it('formats successful response', async () => {
const result = await supertest(httpSetup.server.listener)
.post('/internal/saved_objects/deprecations/_delete_unknown_types')
.expect(200);
expect(result.body).toEqual({ success: true });
});
it('calls upon esClient.deleteByQuery', async () => {
await supertest(httpSetup.server.listener)
.post('/internal/saved_objects/deprecations/_delete_unknown_types')
.expect(200);
expect(elasticsearchClient.asInternalUser.deleteByQuery).toHaveBeenCalledTimes(1);
expect(elasticsearchClient.asInternalUser.deleteByQuery).toHaveBeenCalledWith({
index: ['known-type-index_8.0.0'],
wait_for_completion: false,
body: {
query: {
bool: {
must_not: expect.any(Array),
},
},
},
});
});
});

View file

@ -20,17 +20,26 @@ import { Env } from '../config';
import { configServiceMock } from '../mocks';
import { elasticsearchServiceMock } from '../elasticsearch/elasticsearch_service.mock';
import { coreUsageDataServiceMock } from '../core_usage_data/core_usage_data_service.mock';
import { deprecationsServiceMock } from '../deprecations/deprecations_service.mock';
import { httpServiceMock } from '../http/http_service.mock';
import { httpServerMock } from '../http/http_server.mocks';
import { SavedObjectsClientFactoryProvider } from './service/lib';
import { NodesVersionCompatibility } from '../elasticsearch/version_check/ensure_es_version';
import { SavedObjectsRepository } from './service/lib/repository';
import { registerCoreObjectTypes } from './object_types';
import { getSavedObjectsDeprecationsProvider } from './deprecations';
jest.mock('./service/lib/repository');
jest.mock('./object_types');
jest.mock('./deprecations');
describe('SavedObjectsService', () => {
let deprecationsSetup: ReturnType<typeof deprecationsServiceMock.createInternalSetupContract>;
beforeEach(() => {
deprecationsSetup = deprecationsServiceMock.createInternalSetupContract();
});
const createCoreContext = ({
skipMigration = true,
env,
@ -53,6 +62,7 @@ describe('SavedObjectsService', () => {
return {
http: httpServiceMock.createInternalSetupContract(),
elasticsearch: elasticsearchMock,
deprecations: deprecationsSetup,
coreUsageData: coreUsageDataServiceMock.createSetupContract(),
};
};
@ -79,6 +89,24 @@ describe('SavedObjectsService', () => {
expect(mockedRegisterCoreObjectTypes).toHaveBeenCalledTimes(1);
});
it('register the deprecation provider', async () => {
const coreContext = createCoreContext();
const soService = new SavedObjectsService(coreContext);
const mockRegistry = deprecationsServiceMock.createSetupContract();
deprecationsSetup.getRegistry.mockReturnValue(mockRegistry);
const deprecations = Symbol('deprecations');
const mockedGetSavedObjectsDeprecationsProvider = getSavedObjectsDeprecationsProvider as jest.Mock;
mockedGetSavedObjectsDeprecationsProvider.mockReturnValue(deprecations);
await soService.setup(createSetupDeps());
expect(deprecationsSetup.getRegistry).toHaveBeenCalledTimes(1);
expect(deprecationsSetup.getRegistry).toHaveBeenCalledWith('savedObjects');
expect(mockRegistry.registerDeprecations).toHaveBeenCalledTimes(1);
expect(mockRegistry.registerDeprecations).toHaveBeenCalledWith(deprecations);
});
describe('#setClientFactoryProvider', () => {
it('registers the factory to the clientProvider', async () => {
const coreContext = createCoreContext();

View file

@ -22,6 +22,7 @@ import {
InternalElasticsearchServiceSetup,
InternalElasticsearchServiceStart,
} from '../elasticsearch';
import { InternalDeprecationsServiceSetup } from '../deprecations';
import { KibanaConfigType } from '../kibana_config';
import {
SavedObjectsConfigType,
@ -44,6 +45,7 @@ import { registerRoutes } from './routes';
import { ServiceStatus } from '../status';
import { calculateStatus$ } from './status';
import { registerCoreObjectTypes } from './object_types';
import { getSavedObjectsDeprecationsProvider } from './deprecations';
/**
* Saved Objects is Kibana's data persistence mechanism allowing plugins to
@ -251,6 +253,7 @@ export interface SavedObjectsSetupDeps {
http: InternalHttpServiceSetup;
elasticsearch: InternalElasticsearchServiceSetup;
coreUsageData: InternalCoreUsageDataSetup;
deprecations: InternalDeprecationsServiceSetup;
}
interface WrappedClientFactoryWrapper {
@ -286,7 +289,7 @@ export class SavedObjectsService
this.logger.debug('Setting up SavedObjects service');
this.setupDeps = setupDeps;
const { http, elasticsearch, coreUsageData } = setupDeps;
const { http, elasticsearch, coreUsageData, deprecations } = setupDeps;
const savedObjectsConfig = await this.coreContext.configService
.atPath<SavedObjectsConfigType>('savedObjects')
@ -298,6 +301,20 @@ export class SavedObjectsService
.toPromise();
this.config = new SavedObjectConfig(savedObjectsConfig, savedObjectsMigrationConfig);
const kibanaConfig = await this.coreContext.configService
.atPath<KibanaConfigType>('kibana')
.pipe(first())
.toPromise();
deprecations.getRegistry('savedObjects').registerDeprecations(
getSavedObjectsDeprecationsProvider({
kibanaConfig,
savedObjectsConfig: this.config,
kibanaVersion: this.coreContext.env.packageInfo.version,
typeRegistry: this.typeRegistry,
})
);
coreUsageData.registerType(this.typeRegistry);
registerRoutes({
@ -306,6 +323,7 @@ export class SavedObjectsService
logger: this.logger,
config: this.config,
migratorPromise: this.migrator$.pipe(first()).toPromise(),
kibanaConfig,
kibanaVersion: this.coreContext.env.packageInfo.version,
});

View file

@ -0,0 +1,80 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 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 { getIndexForType } from './get_index_for_type';
import { typeRegistryMock } from '../../saved_objects_type_registry.mock';
describe('getIndexForType', () => {
const kibanaVersion = '8.0.0';
const defaultIndex = '.kibana';
let typeRegistry: ReturnType<typeof typeRegistryMock.create>;
beforeEach(() => {
typeRegistry = typeRegistryMock.create();
});
describe('when migV2 is enabled', () => {
const migV2Enabled = true;
it('returns the correct index for a type specifying a custom index', () => {
typeRegistry.getIndex.mockImplementation((type) => `.${type}-index`);
expect(
getIndexForType({
type: 'foo',
typeRegistry,
defaultIndex,
kibanaVersion,
migV2Enabled,
})
).toEqual('.foo-index_8.0.0');
});
it('returns the correct index for a type not specifying a custom index', () => {
typeRegistry.getIndex.mockImplementation((type) => undefined);
expect(
getIndexForType({
type: 'foo',
typeRegistry,
defaultIndex,
kibanaVersion,
migV2Enabled,
})
).toEqual('.kibana_8.0.0');
});
});
describe('when migV2 is disabled', () => {
const migV2Enabled = false;
it('returns the correct index for a type specifying a custom index', () => {
typeRegistry.getIndex.mockImplementation((type) => `.${type}-index`);
expect(
getIndexForType({
type: 'foo',
typeRegistry,
defaultIndex,
kibanaVersion,
migV2Enabled,
})
).toEqual('.foo-index');
});
it('returns the correct index for a type not specifying a custom index', () => {
typeRegistry.getIndex.mockImplementation((type) => undefined);
expect(
getIndexForType({
type: 'foo',
typeRegistry,
defaultIndex,
kibanaVersion,
migV2Enabled,
})
).toEqual('.kibana');
});
});
});

View file

@ -0,0 +1,36 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 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 { ISavedObjectTypeRegistry } from '../../saved_objects_type_registry';
interface GetIndexForTypeOptions {
type: string;
typeRegistry: ISavedObjectTypeRegistry;
migV2Enabled: boolean;
kibanaVersion: string;
defaultIndex: string;
}
export const getIndexForType = ({
type,
typeRegistry,
migV2Enabled,
defaultIndex,
kibanaVersion,
}: GetIndexForTypeOptions): string => {
// TODO migrationsV2: Remove once we remove migrations v1
// This is a hacky, but it required the least amount of changes to
// existing code to support a migrations v2 index. Long term we would
// want to always use the type registry to resolve a type's index
// (including the default index).
if (migV2Enabled) {
return `${typeRegistry.getIndex(type) || defaultIndex}_${kibanaVersion}`;
} else {
return typeRegistry.getIndex(type) || defaultIndex;
}
};

View file

@ -41,3 +41,5 @@ export type {
SavedObjectsUpdateObjectsSpacesResponse,
SavedObjectsUpdateObjectsSpacesResponseObject,
} from './update_objects_spaces';
export { getIndexForType } from './get_index_for_type';

Some files were not shown because too many files have changed in this diff Show more