diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 6da2d5d602186..0e0099ff672fc 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -26,30 +26,17 @@ /src/plugins/vis_type_vislib/ @elastic/kibana-app /src/plugins/vis_type_xy/ @elastic/kibana-app /src/plugins/visualize/ @elastic/kibana-app -/src/plugins/visualizations/ @elastic/kibana-app -#CC# /src/legacy/core_plugins/kibana/public/local_application_service/ @elastic/kibana-app -#CC# /src/legacy/core_plugins/kibana/ @elastic/kibana-app -#CC# /src/legacy/core_plugins/kibana/common/utils @elastic/kibana-app -#CC# /src/legacy/core_plugins/kibana/migrations @elastic/kibana-app -#CC# /src/legacy/core_plugins/kibana/public @elastic/kibana-app -#CC# /src/legacy/core_plugins/kibana/public/discover/ @elastic/kibana-app -#CC# /src/legacy/core_plugins/kibana/public/local_application_service/ @elastic/kibana-app -#CC# /src/legacy/core_plugins/timelion @elastic/kibana-app -#CC# /src/legacy/core_plugins/vis_type_tagcloud @elastic/kibana-app -#CC# /src/legacy/core_plugins/vis_type_vega @elastic/kibana-app -#CC# /src/legacy/core_plugins/vis_type_vislib/ @elastic/kibana-app -#CC# /src/legacy/server/url_shortening/ @elastic/kibana-app -#CC# /src/legacy/ui/public/state_management @elastic/kibana-app +/src/plugins/visualizations/ @elastic/kibana-application -# App Architecture +# Application Services /examples/bfetch_explorer/ @elastic/kibana-app-arch /examples/dashboard_embeddable_examples/ @elastic/kibana-app-arch /examples/demo_search/ @elastic/kibana-app-arch /examples/developer_examples/ @elastic/kibana-app-arch /examples/embeddable_examples/ @elastic/kibana-app-arch /examples/embeddable_explorer/ @elastic/kibana-app-arch -/examples/state_container_examples/ @elastic/kibana-app-arch -/examples/ui_actions_examples/ @elastic/kibana-app-arch +/examples/state_containers_examples/ @elastic/kibana-app-arch +/examples/ui_action_examples/ @elastic/kibana-app-arch /examples/ui_actions_explorer/ @elastic/kibana-app-arch /examples/url_generators_examples/ @elastic/kibana-app-arch /examples/url_generators_explorer/ @elastic/kibana-app-arch @@ -74,7 +61,6 @@ #CC# /src/plugins/index_pattern_management/ @elastic/kibana-app-arch #CC# /src/plugins/inspector/ @elastic/kibana-app-arch #CC# /src/plugins/share/ @elastic/kibana-app-arch -#CC# /x-pack/plugins/advanced_ui_actions/ @elastic/kibana-app-arch #CC# /x-pack/plugins/drilldowns/ @elastic/kibana-app-arch #CC# /packages/kbn-interpreter/ @elastic/kibana-app-arch @@ -84,9 +70,6 @@ /src/plugins/apm_oss/ @elastic/apm-ui /src/apm.js @watson @vigneshshanmugam #CC# /src/plugins/apm_oss/ @elastic/apm-ui -#CC# /src/legacy/core_plugins/apm_oss/ @elastic/apm-ui -#CC# /src/legacy/ui/public/apm @elastic/apm-ui -#CC# /x-pack/legacy/plugins/apm/ @elastic/apm-ui #CC# /x-pack/plugins/observability/ @elastic/apm-ui # Client Side Monitoring (lives in APM directories but owned by Uptime) @@ -97,12 +80,9 @@ /x-pack/plugins/apm/server/lib/rum_client @elastic/uptime /x-pack/plugins/apm/server/routes/rum_client.ts @elastic/uptime /x-pack/plugins/apm/server/projections/rum_page_load_transactions.ts @elastic/uptime -/x-pack/plugins/apm/server/projections/rum_overview.ts @elastic/uptime -#CC# /x-pack/legacy/plugins/uptime @elastic/uptime # Beats /x-pack/plugins/beats_management/ @elastic/beats -/x-pack/legacy/plugins/beats_management/ @elastic/beats #CC# /x-pack/plugins/beats_management/ @elastic/beats # Presentation @@ -112,12 +92,9 @@ /x-pack/plugins/canvas/ @elastic/kibana-presentation /x-pack/plugins/dashboard_enhanced/ @elastic/kibana-presentation /x-pack/test/functional/apps/canvas/ @elastic/kibana-presentation -#CC# /src/legacy/core_plugins/kibana/public/dashboard/ @elastic/kibana-presentation -#CC# /src/legacy/core_plugins/input_control_vis @elastic/kibana-presentation #CC# /src/plugins/kibana_react/public/code_editor/ @elastic/kibana-presentation #CC# /x-pack/legacy/plugins/canvas/ @elastic/kibana-presentation #CC# /x-pack/plugins/dashboard_mode @elastic/kibana-presentation -#CC# /x-pack/legacy/plugins/dashboard_mode/ @elastic/kibana-presentation # Core UI # Exclude tutorials folder for now because they are not owned by Kibana app and most will move out soon @@ -126,8 +103,6 @@ /src/plugins/home/server/services/ @elastic/kibana-core-ui /src/plugins/kibana_overview/ @elastic/kibana-core-ui /x-pack/plugins/global_search_bar/ @elastic/kibana-core-ui -#CC# /src/legacy/core_plugins/newsfeed @elastic/kibana-core-ui -#CC# /src/legacy/server/sample_data/ @elastic/kibana-core-ui #CC# /src/plugins/newsfeed @elastic/kibana-core-ui #CC# /src/plugins/home/public @elastic/kibana-core-ui #CC# /src/plugins/home/server/services/ @elastic/kibana-core-ui @@ -143,14 +118,17 @@ # Machine Learning /x-pack/plugins/ml/ @elastic/ml-ui -/x-pack/test/functional/apps/machine_learning/ @elastic/ml-ui -/x-pack/test/functional/services/machine_learning/ @elastic/ml-ui -/x-pack/test/functional/services/ml.ts @elastic/ml-ui +/x-pack/test/functional/apps/ml/ @elastic/ml-ui +/x-pack/test/functional/services/ml/ @elastic/ml-ui # ML team owns and maintains the transform plugin despite it living in the Elasticsearch management section. /x-pack/plugins/transform/ @elastic/ml-ui /x-pack/test/functional/apps/transform/ @elastic/ml-ui -/x-pack/test/functional/services/transform_ui/ @elastic/ml-ui -/x-pack/test/functional/services/transform.ts @elastic/ml-ui +/x-pack/test/functional/services/transform/ @elastic/ml-ui/ +x-pack/test/api_integration_basic/apis/ml/ @elastic/ml-ui +/x-pack/test/functional_basic/apps/ml/ @elastic/ml-ui + +/x-pack/test/api_integration_basic/apis/transform/ @elastic/ml-ui +/x-pack/test/functional_basic/apps/transform/ @elastic/ml-ui # Maps /x-pack/plugins/maps/ @elastic/kibana-gis @@ -180,9 +158,6 @@ /packages/kbn-es-archiver/ @elastic/kibana-operations /packages/kbn-utils/ @elastic/kibana-operations /src/legacy/server/keystore/ @elastic/kibana-operations -/src/legacy/server/pid/ @elastic/kibana-operations -/src/legacy/server/sass/ @elastic/kibana-operations -/src/legacy/server/utils/ @elastic/kibana-operations /src/legacy/server/warnings/ @elastic/kibana-operations /.ci/es-snapshots/ @elastic/kibana-operations /vars/ @elastic/kibana-operations @@ -211,37 +186,19 @@ /src/legacy/server/config/ @elastic/kibana-platform /src/legacy/server/http/ @elastic/kibana-platform /src/legacy/server/logging/ @elastic/kibana-platform -/src/legacy/server/saved_objects/ @elastic/kibana-platform -/src/legacy/server/status/ @elastic/kibana-platform /src/plugins/status_page/ @elastic/kibana-platform /src/plugins/saved_objects_management/ @elastic/kibana-platform /src/dev/run_check_published_api_changes.ts @elastic/kibana-platform #CC# /src/core/server/csp/ @elastic/kibana-platform -#CC# /src/legacy/core_plugins/kibana/server/lib @elastic/kibana-platform -#CC# /src/legacy/core_plugins/kibana/server/lib/management/saved_objects @elastic/kibana-platform -#CC# /src/legacy/core_plugins/kibana/server/routes/api/import/ @elastic/kibana-platform -#CC# /src/legacy/core_plugins/kibana/server/routes/api/export/ @elastic/kibana-platform -#CC# /src/legacy/core_plugins/elasticsearch @elastic/kibana-platform -#CC# /src/legacy/core_plugins/testbed @elastic/kibana-platform #CC# /src/legacy/server/config/ @elastic/kibana-platform #CC# /src/legacy/server/http/ @elastic/kibana-platform -#CC# /src/legacy/server/status/ @elastic/kibana-platform -#CC# /src/legacy/ui/public/new_platform @elastic/kibana-platform -#CC# /src/legacy/ui/public/plugin_discovery @elastic/kibana-platform -#CC# /src/legacy/ui/public/chrome @elastic/kibana-platform -#CC# /src/legacy/ui/public/notify @elastic/kibana-platform #CC# /src/legacy/ui/public/documentation_links @elastic/kibana-platform -#CC# /src/legacy/ui/public/autoload @elastic/kibana-platform #CC# /src/plugins/legacy_export/ @elastic/kibana-platform #CC# /src/plugins/saved_objects/ @elastic/kibana-platform #CC# /src/plugins/status_page/ @elastic/kibana-platform -#CC# /src/plugins/testbed/server/ @elastic/kibana-platform -#CC# /x-pack/legacy/plugins/xpack_main/server/ @elastic/kibana-platform -#CC# /x-pack/legacy/server/lib/ @elastic/kibana-platform #CC# /x-pack/plugins/cloud/ @elastic/kibana-platform #CC# /x-pack/plugins/features/ @elastic/kibana-platform #CC# /x-pack/plugins/global_search/ @elastic/kibana-platform -#CC# /src/legacy/plugin_discovery/ @elastic/kibana-platform # Security /src/core/server/csp/ @elastic/kibana-security @elastic/kibana-platform @@ -257,19 +214,13 @@ /x-pack/test/security_api_integration/ @elastic/kibana-security /x-pack/test/security_functional/ @elastic/kibana-security /x-pack/test/spaces_api_integration/ @elastic/kibana-security -/x-pack/test/token_api_integration/ @elastic/kibana-security -#CC# /src/legacy/ui/public/capabilities @elastic/kibana-security -#CC# /x-pack/legacy/plugins/encrypted_saved_objects/ @elastic/kibana-security #CC# /x-pack/plugins/security_solution/ @elastic/kibana-security #CC# /x-pack/plugins/security/ @elastic/kibana-security -#CC# /x-pack/plugins/audit_trail/ @elastic/kibana-security # Kibana Localization /src/dev/i18n/ @elastic/kibana-localization -/src/legacy/server/i18n/ @elastic/kibana-localization /src/core/public/i18n/ @elastic/kibana-localization /packages/kbn-i18n/ @elastic/kibana-localization -#CC# /src/legacy/server/i18n/ @elastic/kibana-localization #CC# /x-pack/plugins/translations/ @elastic/kibana-localization # Kibana Telemetry @@ -294,17 +245,12 @@ x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @elastic/kib /x-pack/plugins/event_log/ @elastic/kibana-alerting-services /x-pack/plugins/task_manager/ @elastic/kibana-alerting-services /x-pack/test/alerting_api_integration/ @elastic/kibana-alerting-services -/x-pack/test/plugin_api_integration/plugins/task_manager/ @elastic/kibana-alerting-services /x-pack/test/plugin_api_integration/test_suites/task_manager/ @elastic/kibana-alerting-services /x-pack/plugins/triggers_actions_ui/ @elastic/kibana-alerting-services /x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/ @elastic/kibana-alerting-services /x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/ @elastic/kibana-alerting-services /docs/user/alerting/ @elastic/kibana-alerting-services /docs/management/alerting/ @elastic/kibana-alerting-services -#CC# /x-pack/legacy/plugins/actions/ @elastic/kibana-alerting-services -#CC# /x-pack/legacy/plugins/alerting/ @elastic/kibana-alerting-services -#CC# /x-pack/legacy/plugins/task_manager @elastic/kibana-alerting-services -#CC# /x-pack/legacy/plugins/triggers_actions_ui/ @elastic/kibana-alerting-services #CC# /x-pack/plugins/stack_alerts @elastic/kibana-alerting-services # Enterprise Search @@ -315,11 +261,9 @@ x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @elastic/kib # Elasticsearch UI /src/plugins/dev_tools/ @elastic/es-ui /src/plugins/console/ @elastic/es-ui -/src/plugins/es_ui_shared/ @elastic/es-ui /x-pack/plugins/cross_cluster_replication/ @elastic/es-ui /x-pack/plugins/index_lifecycle_management/ @elastic/es-ui /x-pack/plugins/console_extensions/ @elastic/es-ui -/x-pack/plugins/es_ui_shared/ @elastic/es-ui /x-pack/plugins/grokdebugger/ @elastic/es-ui /x-pack/plugins/index_management/ @elastic/es-ui /x-pack/plugins/license_management/ @elastic/es-ui @@ -333,18 +277,11 @@ x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @elastic/kib /x-pack/plugins/ingest_pipelines/ @elastic/es-ui /packages/kbn-ace/ @elastic/es-ui /packages/kbn-monaco/ @elastic/es-ui -#CC# /src/legacy/core_plugins/kibana/public/dev_tools/ @elastic/es-ui -#CC# /src/legacy/core_plugins/console_legacy @elastic/es-ui -#CC# /x-pack/legacy/plugins/rollup/ @elastic/es-ui -#CC# /x-pack/legacy/server/lib/create_router/ @elastic/es-ui -#CC# /x-pack/legacy/server/lib/check_license/ @elastic/es-ui #CC# /x-pack/plugins/console_extensions/ @elastic/es-ui #CC# /x-pack/plugins/cross_cluster_replication/ @elastic/es-ui -#CC# /x-pack/plugins/es_ui_shared/ @elastic/es-u # Endpoint /x-pack/plugins/endpoint/ @elastic/endpoint-app-team @elastic/siem -/x-pack/test/api_integration/apis/endpoint/ @elastic/endpoint-app-team @elastic/siem /x-pack/test/endpoint_api_integration_no_ingest/ @elastic/endpoint-app-team @elastic/siem /x-pack/test/security_solution_endpoint/ @elastic/endpoint-app-team @elastic/siem /x-pack/test/functional/es_archives/endpoint/ @elastic/endpoint-app-team @elastic/siem @@ -361,6 +298,7 @@ x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @elastic/kib /x-pack/test/api_integration/apis/security_solution @elastic/siem @elastic/endpoint-app-team /x-pack/plugins/case @elastic/siem @elastic/endpoint-app-team /x-pack/plugins/lists @elastic/siem @elastic/endpoint-app-team +#CC# /x-pack/plugins/security_solution/ @elastic/siem # Security Intelligence And Analytics /x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules @elastic/security-intelligence-analytics @@ -369,13 +307,6 @@ x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @elastic/kib **/*.scss @elastic/kibana-design #CC# /packages/kbn-ui-framework/ @elastic/kibana-design -# Core UI design -/src/plugins/dashboard/**/*.scss @elastic/kibana-core-ui-designers -/src/plugins/embeddable/**/*.scss @elastic/kibana-core-ui-designers -/x-pack/plugins/canvas/**/*.scss @elastic/kibana-core-ui-designers -/x-pack/plugins/spaces/**/*.scss @elastic/kibana-core-ui-designers -/x-pack/plugins/security/**/*.scss @elastic/kibana-core-ui-designers - # Observability design /x-pack/plugins/apm/**/*.scss @elastic/observability-design /x-pack/plugins/infra/**/*.scss @elastic/observability-design diff --git a/docs/developer/best-practices/stability.asciidoc b/docs/developer/best-practices/stability.asciidoc index 348412e593d9e..29be86f58317f 100644 --- a/docs/developer/best-practices/stability.asciidoc +++ b/docs/developer/best-practices/stability.asciidoc @@ -43,11 +43,26 @@ dependency list! [discrete] === Test coverage -* Does the feature have sufficient unit test coverage? (does it handle -storeinSessions?) -* Does the feature have sufficient Functional UI test coverage? -* Does the feature have sufficient Rest API coverage test coverage? -* Does the feature have sufficient Integration test coverage? +Testing UI code is hard. We strive for https://github.com/elastic/engineering/blob/master/kibana_dev_principles.md#automate-tests-through-ci[total automated test coverage] of our code and UX, +but this is difficult to measure and we're constrained by time. During development, test coverage +measurement is subjective and manual, based on our understanding of the feature. Code coverage +reports indicate possible gaps, but it ultimately comes down to a judgment call. Here are some +guidelines to help you ensure sufficient automated test coverage. + +* Every PR should be accompanied by tests. +* Check the before and after automated test coverage metrics. If coverage has gone down you might +have missed some tests. +* Cover failure cases, edge cases, and happy paths with your tests. +* Pay special attention to code that could contain bugs that harm to the user. "Harm" includes +direct problems like data loss and data entering a bad state, as well as indirect problems like +making a poor business decision based on misinformation presented by the UI. For example, state +migrations and security permissions are important areas to cover. +* Pay special attention to public APIs, which may be used in unexpected ways. Any code you release +for consumption by other plugins should be rigorously tested with many permutations. +* Include end-to-end tests for areas where the logic spans global state, URLs, and multiple plugin APIs. +* Every time a bug is reported, add a test to cover it. +* Retrospectively gauge the quality of the code you ship by tracking how many bugs are reported for +features that are released. How can you reduce this number by improving your testing approach? [discrete] === Browser coverage @@ -63,4 +78,4 @@ Does the feature work efficiently on the list of supported browsers? * Does the feature affect old indices or saved objects? * Has the feature been tested with {kib} aliases? * Read/Write privileges of the indices before and after the -upgrade? +upgrade? \ No newline at end of file diff --git a/docs/developer/contributing/development-tests.asciidoc b/docs/developer/contributing/development-tests.asciidoc index e4bd49e12101b..4cf667195153d 100644 --- a/docs/developer/contributing/development-tests.asciidoc +++ b/docs/developer/contributing/development-tests.asciidoc @@ -50,6 +50,8 @@ yarn test:ftr:runner –config test/api_integration/config **Testing IE on OS X** +**Note:** IE11 is not supported from 7.9 onwards. + * http://www.vmware.com/products/fusion/fusion-evaluation.html[Download VMWare Fusion]. * https://developer.microsoft.com/en-us/microsoft-edge/tools/vms/#downloads[Download diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querysuggestiongetfnargs.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querysuggestiongetfnargs.md index 96e43ca836891..de6f4563b678a 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querysuggestiongetfnargs.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querysuggestiongetfnargs.md @@ -23,4 +23,5 @@ export interface QuerySuggestionGetFnArgs | [selectionEnd](./kibana-plugin-plugins-data-public.querysuggestiongetfnargs.selectionend.md) | number | | | [selectionStart](./kibana-plugin-plugins-data-public.querysuggestiongetfnargs.selectionstart.md) | number | | | [signal](./kibana-plugin-plugins-data-public.querysuggestiongetfnargs.signal.md) | AbortSignal | | +| [useTimeRange](./kibana-plugin-plugins-data-public.querysuggestiongetfnargs.usetimerange.md) | boolean | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querysuggestiongetfnargs.usetimerange.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querysuggestiongetfnargs.usetimerange.md new file mode 100644 index 0000000000000..a29cddd81d885 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querysuggestiongetfnargs.usetimerange.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QuerySuggestionGetFnArgs](./kibana-plugin-plugins-data-public.querysuggestiongetfnargs.md) > [useTimeRange](./kibana-plugin-plugins-data-public.querysuggestiongetfnargs.usetimerange.md) + +## QuerySuggestionGetFnArgs.useTimeRange property + +Signature: + +```typescript +useTimeRange?: boolean; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchbar.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchbar.md index bb45222d096a0..b886aafcfc00f 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchbar.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchbar.md @@ -7,7 +7,7 @@ Signature: ```typescript -SearchBar: React.ComponentClass, "query" | "isLoading" | "filters" | "onRefresh" | "onRefreshChange" | "refreshInterval" | "indexPatterns" | "dataTestSubj" | "screenTitle" | "showQueryInput" | "showDatePicker" | "showAutoRefreshOnly" | "dateRangeFrom" | "dateRangeTo" | "isRefreshPaused" | "customSubmitButton" | "timeHistory" | "indicateNoData" | "onFiltersUpdated" | "savedQuery" | "showSaveQuery" | "onClearSavedQuery" | "showQueryBar" | "showFilterBar" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated">, any> & { - WrappedComponent: React.ComponentType & ReactIntl.InjectedIntlProps>; +SearchBar: React.ComponentClass, "query" | "isLoading" | "filters" | "onRefresh" | "onRefreshChange" | "refreshInterval" | "indexPatterns" | "dataTestSubj" | "screenTitle" | "showQueryInput" | "showDatePicker" | "showAutoRefreshOnly" | "dateRangeFrom" | "dateRangeTo" | "isRefreshPaused" | "customSubmitButton" | "timeHistory" | "indicateNoData" | "onFiltersUpdated" | "trackUiMetric" | "savedQuery" | "showSaveQuery" | "onClearSavedQuery" | "showQueryBar" | "showFilterBar" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated">, any> & { + WrappedComponent: React.ComponentType & ReactIntl.InjectedIntlProps>; } ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ui_settings.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ui_settings.md index 6ed20beb396f1..9a0c37c8edd38 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ui_settings.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ui_settings.md @@ -37,5 +37,6 @@ UI_SETTINGS: { readonly INDEXPATTERN_PLACEHOLDER: "indexPattern:placeholder"; readonly FILTERS_PINNED_BY_DEFAULT: "filters:pinnedByDefault"; readonly FILTERS_EDITOR_SUGGEST_VALUES: "filterEditor:suggestValues"; + readonly AUTOCOMPLETE_USE_TIMERANGE: "autocomplete:useTimeRange"; } ``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ui_settings.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ui_settings.md index 2d4ce75b956df..c2edc64f292d2 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ui_settings.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ui_settings.md @@ -37,5 +37,6 @@ UI_SETTINGS: { readonly INDEXPATTERN_PLACEHOLDER: "indexPattern:placeholder"; readonly FILTERS_PINNED_BY_DEFAULT: "filters:pinnedByDefault"; readonly FILTERS_EDITOR_SUGGEST_VALUES: "filterEditor:suggestValues"; + readonly AUTOCOMPLETE_USE_TIMERANGE: "autocomplete:useTimeRange"; } ``` diff --git a/docs/maps/images/create_phrase_filter.png b/docs/maps/images/create_phrase_filter.png deleted file mode 100644 index 720aecf44d9fa..0000000000000 Binary files a/docs/maps/images/create_phrase_filter.png and /dev/null differ diff --git a/docs/maps/images/create_spatial_filter.png b/docs/maps/images/create_spatial_filter.png index abb52bd0b5b0a..21614aa0f4e24 100644 Binary files a/docs/maps/images/create_spatial_filter.png and b/docs/maps/images/create_spatial_filter.png differ diff --git a/docs/maps/images/gs_add_cloropeth_layer.png b/docs/maps/images/gs_add_cloropeth_layer.png index 2800a5a2d2584..1528f404026f2 100644 Binary files a/docs/maps/images/gs_add_cloropeth_layer.png and b/docs/maps/images/gs_add_cloropeth_layer.png differ diff --git a/docs/maps/images/gs_create_new_map.png b/docs/maps/images/gs_create_new_map.png deleted file mode 100644 index bf5fd56ceba13..0000000000000 Binary files a/docs/maps/images/gs_create_new_map.png and /dev/null differ diff --git a/docs/maps/maps-getting-started.asciidoc b/docs/maps/maps-getting-started.asciidoc index 5c6cd87b235e1..32a81c8e65f56 100644 --- a/docs/maps/maps-getting-started.asciidoc +++ b/docs/maps/maps-getting-started.asciidoc @@ -1,231 +1,197 @@ [role="xpack"] [[maps-getting-started]] -== Get started with Maps +== Create a map with multiple layers and data sources ++++ -Get started +Create a multilayer map ++++ +If you are new to **Maps**, this tutorial is a good place to start. +It guides you through the common steps for working with your location data. +You'll learn to: -You work with Maps by adding layers. The data for a layer can come from -sources such as {es} documents, vector sources, tile map services, web map -services, and more. You can symbolize the data in different ways. -For example, you might show which airports have the longest flight -delays by using circles from small to big. Or, -you might show the amount of web log traffic by shading countries from -light to dark. +- Create a map with multiple layers and data sources +- Use symbols, colors, and labels to style data values +- Embed a map in a dashboard +- Search across panels in your dashboard -[role="screenshot"] -image::maps/images/sample_data_web_logs.png[] - -[[maps-read-only-access]] -NOTE: If you have insufficient privileges to create or save maps, a read-only icon -appears in the application header. The buttons to create new maps or edit -existing maps won't be visible. For more information on granting access to -Kibana see <>. +When you complete this tutorial, you’ll have a map that looks like this: [role="screenshot"] -image::maps/images/read-only-badge.png[Example of Maps' read only access indicator in Kibana's header] +image::maps/images/sample_data_web_logs.png[] [float] === Prerequisites -Before you start this tutorial, <>. Each -sample data set includes a map to go along with the data. Once you've added the data, open Maps and -explore the different layers of the *[Logs] Total Requests and Bytes* map. -You'll re-create this map in this tutorial. -[float] -=== Take-away skills -In this tutorial, you'll learn to: +- If you don’t already have {kib}, set it up with https://www.elastic.co/cloud/elasticsearch-service/signup?baymax=docs-body&elektra=docs[our free trial]. +- This tutorial requires the <>. The sample data includes a [Logs] Total Requests and Bytes map, which you’ll re-create in this tutorial. +- You must have the correct privileges for creating a map. +If you don't have sufficient privileges to create or save maps, +a read-only icon appears in the toolbar. For more information, +refer to <>. -* Create a multi-layer map -* Connect a layer to a data source -* Use symbols, colors, and labels to style a layer -* Create layers for {es} data - -[role="xpack"] +[float] [[maps-create]] -=== Create a map - -The first thing to do is to create a new map. +=== Step 1. Create a map -. If you haven't already, open the main menu, then click *Maps*. -. On the maps list page, click *Create map*. +. Open the main menu, and then click *Dashboard*. +. Click **Create dashboard**. . Set the time range to *Last 7 days*. -+ -A new map is created using a base tile layer. -+ -[role="screenshot"] -image::maps/images/gs_create_new_map.png[] +. Click **Create new**. +. Click **Maps**. -[role="xpack"] +[float] [[maps-add-choropleth-layer]] -=== Add a choropleth layer +=== Step 2. Add a choropleth layer -Now that you have a map, you'll want to add layers to it. The first layer you'll add is a choropleth layer to shade world countries -by web log traffic. Darker shades symbolize countries with more web log traffic, -and lighter shades symbolize countries with less traffic. - -. Click *Add layer*. -. Select *Choropleth*. -. From the *Layer* dropdown menu, select *World Countries*. -. Under *Statistics source*, set *Index pattern* to *kibana_sample_data_logs*. -. Set *Join field* to *geo.src*. -. Click the *Add layer* button. -. Set *Name* to `Total Requests by Country`. -. Set *Opacity* to 50%. -. Click *Add* under *Tooltip fields*. -. In the popover, select *ISO 3166-1 alpha-2 code* and *name* and click *Add*. -. Under *Fill color*, select the grey color ramp. -. Under *Border color*, change the selected color to *white*. -. Click *Save & close*. +by web log traffic. Darker shades will symbolize countries with more web log traffic, +and lighter shades will symbolize countries with less traffic. + +. Click **Add layer**, and then click **Choropleth**. + +. From the **Layer** dropdown menu, select **World Countries**. + +. In **Statistics source**, set: +** **Index pattern** to **kibana_sample_data_logs** +** **Join field** to **geo.src** + +. Click **Add layer**. + +. In **Layer settings**, set: + +** **Name** to `Total Requests by Country` +** **Opacity** to 50% + +. Add a Tooltip field: + +** Select **ISO 3166-1 alpha-2 code** and **name**. +** Click **Add**. + +. In **Layer style**, set: + +** **Fill color** to the grey color ramp +** **Border color** to white + +. Click **Save & close**. + Your map now looks like this: + [role="screenshot"] image::maps/images/gs_add_cloropeth_layer.png[] -[role="xpack"] +[float] [[maps-add-elasticsearch-layer]] -=== Add layers for the {es} data - -To avoid overwhelming the user with too much data at once, you'll add two layers for {es} data. +=== Step 3. Add layers for the Elasticsearch data -* The first layer will display individual documents. -The layer will appear when the user zooms in the map to show smaller regions. -* The second layer will display aggregated data that represents many documents. -The layer will appear when the user zooms out the map to show larger amounts of the globe. +To avoid overwhelming the user with too much data at once, you'll add two layers +for the Elasticsearch data. The first layer will display individual documents +when users zoom in on the map. The second layer will +display aggregated data when users zoom the map out. -==== Add a vector layer to display individual documents +[float] +==== Add a layer for individual documents This layer displays web log documents as points. -The layer is only visible when users zoom in the map past zoom level 9. - -. Click *Add layer*. -. Select *Documents*. -. Set *Index pattern* to *kibana_sample_data_logs*. -. Click the *Add layer* button. -. Set *Name* to `Actual Requests`. -. Set *Visibilty* to the range [9, 24]. -. Set *Opacity* to 100%. -. Click *Add* under *Tooltip fields*. -. In the popover, select *clientip*, *timestamp*, *host*, *request*, *response*, *machine.os*, *agent*, and *bytes* and click *Add*. -. Set *Fill color* to *#2200ff*. -. Click *Save & close*. +The layer is only visible when users zoom in. + +. Click **Add layer**, and then click **Documents**. + +. Set **Index pattern** to **kibana_sample_data_logs**. + +. Set **Scaling** to *Limits results to 10000.* + +. Click **Add layer**. + +. In **Layer settings**, set: +** **Name** to `Actual Requests` +** **Visibilty** to the range [9, 24] +** **Opacity** to 100% + +. Add a tooltip field and select **agent**, **bytes**, **clientip**, **host**, +**machine.os**, **request**, **response**, and **timestamp**. + +. In **Layer style**, set **Fill color** to **#2200FF**. + +. Click **Save & close**. + -Your map now looks like this between zoom levels 9 and 24: +Your map will look like this from zoom level 9 to 24: + [role="screenshot"] image::maps/images/gs_add_es_document_layer.png[] -==== Add a vector layer to display aggregated data - -Aggregations group {es} documents into grids. You can calculate metrics -for each gridded cell. +[float] +==== Add a layer for aggregated data -You'll create a layer for aggregated data and make it visible only when the map -is zoomed out past zoom level 9. Darker colors will symbolize grids +You'll create a layer for {ref}/search-aggregations.html[aggregated data] and make it visible only when the map +is zoomed out. Darker colors will symbolize grids with more web log traffic, and lighter colors will symbolize grids with less traffic. Larger circles will symbolize grids with more total bytes transferred, and smaller circles will symbolize grids with less bytes transferred. -[role="screenshot"] -image::maps/images/grid_metrics_both.png[] - -===== Add the layer - -. Click *Add layer*. -. Select *Clusters and grids*. -. Set *Index pattern* to *kibana_sample_data_logs*. -. Click the *Add layer* button. -. Set *Name* to `Total Requests and Bytes`. -. Set *Visibility* to the range [0, 9]. -. Set *Opacity* to 100%. - -===== Configure the aggregation metrics - -. Click *Add metric* under of *Metrics* label. -. Select *Sum* in the aggregation select. -. Select *bytes* in the field select. - -===== Set the layer style - -. In *Layer style*, change *Symbol size*: - .. Set *Min size* to 7. - .. Set *Max size* to 25. - .. Change the field select from *count* to *sum of bytes*. -. Click *Save & close* button. -+ -Your map now looks like this between zoom levels 0 and 9: +. Click **Add layer**, and select **Clusters and grids**. +. Set **Index pattern** to **kibana_sample_data_logs**. +. Click **Add layer**. +. In **Layer settings**, set: +** **Name** to `Total Requests and Bytes` +** **Visibility** to the range [0, 9] +** **Opacity** to 100% +. Add a metric with: +** **Aggregation** set to **Sum** +** **Field** set to **bytes** +. In **Layer style**, change **Symbol size**: +** Set the field select to *sum bytes*. +** Set the min size to 7 and the max size to 25 px. +. Click **Save & close** button. ++ +Your map will look like this between zoom levels 0 and 9: + [role="screenshot"] image::maps/images/sample_data_web_logs.png[] -[role="xpack"] +[float] [[maps-save]] -=== Save the map -Now that your map is complete, you'll want to save it so others can use it. +=== Step 4. Save the map +Now that your map is complete, save it and return to the dashboard. -. In the application toolbar, click *Save*. +. In the toolbar, click *Save*. . Enter `Tutorial web logs map` for the title. -. Click *Save*. -+ -You have completed the steps for re-creating the sample data map. - -*Next steps:* +. Ensure *Add to Dashboard after saving* is enabled. +. Click *Save and return*. -* Continue with this tutorial and <>. -* Create a map using your own data. You might find these resources helpful: -** <> -** <> -** <> - -[role="xpack"] +[float] [[maps-embedding]] -=== Add the map to a dashboard -You can add your saved map to a {kibana-ref}/dashboard.html[dashboard] and view your geospatial data alongside bar charts, pie charts, and other visualizations. +=== Step 5. Explore your data from the dashboard -. Open the main menu, then click *Dashboard*. -. Click *Create dashboard*. -. Set the time range to *Last 7 days*. -. Click *Add*. -+ -A panel opens with a list of objects that you can add to the dashboard. You'll add a map and two visualizations. -+ -. Set the *Types* select to *Map*. -. Click the name of your saved map or the *[Logs] Total Requests and Bytes* map included with the sample data set to add a map to the dashboard. -. Set the *Types* select to *Visualization*. -. Click *[Logs] Heatmap* to add a heatmap to the dashboard. -. Click *[Logs] Visitors by OS* to add a pie chart to the dashboard. -. Close the panel. -+ -Your dashboard should look like this: +View your geospatial data alongside a heat map and pie chart, and then filter the data. +When you apply a filter in one panel, it is applied to all panels on the dashboard. + +. In the toolbar, click **Add** to open a list of objects that you can add to the dashboard. +. Set the **Types** select to **Visualization**. +. Add **[Logs] Heatmap** and **[Logs] Visitors by OS** to the dashboard. + [role="screenshot"] image::maps/images/gs_dashboard_with_map.png[] -==== Explore your data using filters +. To filter for documents where **machine.os.keyword** is **osx**, click +the **osx** slice in the pie chart. -You can apply filters to your dashboard to hone in on the data that you are most interested in. -The dashboard is interactive--you can quickly create filters by clicking on the desired data in the map and visualizations. -The panels are linked, so that when you apply a filter in one panel, the filter is applied to all panels on the dashboard. +. Remove the filter by clicking **x** next to its name in the filter bar. -. In the *[Logs] Visitors by OS* visualization, click on the *osx* pie slice. -+ -Both the visualizations and map are filtered to only show documents where *machine.os.keyword* is *osx*. -The *machine.os.keyword: osx* filter appears in the dashboard query bar. -+ -. Click the *x* to remove the *machine.os.keyword: osx* filter. -. In the map, click in the United States vector. -. Click plus image:maps/images/gs_plus_icon.png[] next to the *iso2* row in the tooltip. -+ -Both the visualizations and the map are filtered to only show documents where *geo.src* is *US*. -The *geo.src: US* filter appears in the dashboard query bar. -+ -Your dashboard should look like this: +. Set a filter from the map: + +.. Open a tooltip by clicking anywhere in the United States vector. + +.. To show only documents where **geo.src** is **US**, click the filter icon in the row for **ISO 3066-1 alpha-2**. + [role="screenshot"] image::maps/images/gs_dashboard_with_terms_filter.png[] + +[float] +=== What's next? + +* Check out <> that you can add to your map. +* Learn more ways <>. +* Learn more about <>. diff --git a/docs/maps/search.asciidoc b/docs/maps/search.asciidoc index 09d9788cd37e0..764a84d88b1d2 100644 --- a/docs/maps/search.asciidoc +++ b/docs/maps/search.asciidoc @@ -30,6 +30,9 @@ You can create two types of filters by interacting with your map: * <> * <> +[role="screenshot"] +image::maps/images/create_spatial_filter.png[] + [float] [[maps-spatial-filters]] ==== Spatial filters @@ -40,9 +43,6 @@ You can create spatial filters in two ways: * Click the tool icon image:maps/images/tools_icon.png[], and then draw a polygon or bounding box on the map to define the spatial filter. * Click *Filter by geometry* in a <>, and then use the feature's geometry for the spatial filter. -+ -[role="screenshot"] -image::maps/images/create_spatial_filter.png[] Spatial filters have the following properties: @@ -59,9 +59,6 @@ A phrase filter narrows search results to documents that contain the specified t You can create a phrase filter by clicking the plus icon image:maps/images/gs_plus_icon.png[] in a <>. If the map is a dashboard panel with drilldowns, you can apply a phrase filter to a drilldown by selecting the drilldown action. -[role="screenshot"] -image::maps/images/create_phrase_filter.png[] - [role="xpack"] [[maps-layer-based-filtering]] === Filter a single layer diff --git a/package.json b/package.json index 62cf12e4b1cd3..cca4f7cc255ce 100644 --- a/package.json +++ b/package.json @@ -140,8 +140,6 @@ "@slack/webhook": "^5.0.0", "@storybook/addons": "^6.0.16", "@turf/circle": "6.0.1", - "@types/pdfmake": "^0.1.15", - "@types/yauzl": "^2.9.1", "JSONStream": "1.3.5", "abort-controller": "^3.0.0", "abortcontroller-polyfill": "^1.4.0", @@ -509,6 +507,7 @@ "@types/ora": "^1.3.5", "@types/papaparse": "^5.0.3", "@types/parse-link-header": "^1.0.0", + "@types/pdfmake": "^0.1.15", "@types/pegjs": "^0.10.1", "@types/pngjs": "^3.4.0", "@types/prettier": "^2.0.2", @@ -566,6 +565,7 @@ "@types/write-pkg": "^3.1.0", "@types/xml-crypto": "^1.4.1", "@types/xml2js": "^0.4.5", + "@types/yauzl": "^2.9.1", "@types/zen-observable": "^0.8.0", "@typescript-eslint/eslint-plugin": "^3.10.0", "@typescript-eslint/parser": "^3.10.0", @@ -577,8 +577,8 @@ "angular-recursion": "^1.0.5", "angular-route": "^1.8.0", "angular-sortable-view": "^0.0.17", - "apidoc": "^0.20.1", - "apidoc-markdown": "^5.0.0", + "apidoc": "^0.25.0", + "apidoc-markdown": "^5.1.8", "apollo-link": "^1.2.3", "apollo-link-error": "^1.1.7", "apollo-link-state": "^0.4.1", diff --git a/packages/kbn-plugin-helpers/README.md b/packages/kbn-plugin-helpers/README.md index d7ed3106c1ceb..05eb9f5998737 100644 --- a/packages/kbn-plugin-helpers/README.md +++ b/packages/kbn-plugin-helpers/README.md @@ -4,13 +4,20 @@ Just some helpers for kibana plugin devs. ## Installation -To install the plugin helpers use `yarn` to link to the package from the Kibana project: +You don't actually need to install the plugin helpers, they are automatically inherited from the Kibana project by building your plugin within the Kibana repo. To use the plugin helpers just create the needed npm scripts on your plugin's `package.json` (as exemplified below) which +is already the case if you use the new `node scripts/generate_plugin` script. -```sh -yarn add --dev link:../../kibana/packages/kbn-plugin-helpers +```json +{ + "scripts" : { + "build": "yarn plugin-helpers build", + "plugin-helpers": "node ../../scripts/plugin_helpers", + "kbn": "node ../../scripts/kbn" + } +} ``` -This will link the package from the repository into your plugin, but the `plugin-helpers` executable won't be available in your project until you run bootstrap again. +This will make it easier to execute the `plugin-helpers` script from within your plugin repository. ```sh yarn kbn bootstrap @@ -18,24 +25,38 @@ yarn kbn bootstrap ## Usage -This simple CLI has several tasks that plugin devs can run from to easily debug, test, or package kibana plugins. +This simple CLI has a build task that plugin devs can run from to easily package Kibana plugins. + +Previously you could also use that tool to start and test your plugin. Currently you can run +your plugin along with Kibana running `yarn start` in the Kibana repository root folder. Finally to test +your plugin you should now configure and use your own tools. ```sh $ plugin-helpers help - Usage: plugin-helpers [options] [command] + Usage: plugin-helpers [command] [options] Commands: - - start Start kibana and have it include this plugin - build [options] [files...] Build a distributable archive - test Run the server and browser tests - test:mocha [files...] Run the server tests using mocha - - Options: - - -h, --help output usage information - -V, --version output the version number + build + Copies files from the source into a zip archive that can be distributed for + installation into production Kibana installs. The archive includes the non- + development npm dependencies and builds itself using raw files in the source + directory so make sure they are clean/up to date. The resulting archive can + be found at: + + build/{plugin.id}-{kibanaVersion}.zip + + Options: + --skip-archive Don't create the zip file, just create the build/kibana directory + --kibana-version, -v Kibana version that the + + + Global options: + --verbose, -v Log verbosely + --debug Log debug messages (less than verbose) + --quiet Only log errors + --silent Don't log anything + --help Show this message ``` @@ -55,21 +76,13 @@ All configuration setting listed below can simply can be included in the json co ## Global settings -### Settings for `start` - -Setting | Description -------- | ----------- -`includePlugins` | Intended to be used in a config file, an array of additional plugin paths to include, absolute or relative to the plugin root -`*` | Any options/flags included will be passed unmodified to the Kibana binary - ### Settings for `build` Setting | Description ------- | ----------- +`serverSourcePatterns` | Defines the files that are built with babel and written to your distributable for your server plugin. It is ignored if `kibana.json` has none `server: true` setting defined. `skipArchive` | Don't create the zip file, leave the build path alone -`buildDestination` | Target path for the build output, absolute or relative to the plugin root `skipInstallDependencies` | Don't install dependencies defined in package.json into build output -`buildVersion` | Version for the build output `kibanaVersion` | Kibana version for the build output (added to package.json) ## TypeScript support diff --git a/src/core/server/ui_settings/ui_settings_service.test.ts b/src/core/server/ui_settings/ui_settings_service.test.ts index 0c17a3a614d60..f4e24e3a517c3 100644 --- a/src/core/server/ui_settings/ui_settings_service.test.ts +++ b/src/core/server/ui_settings/ui_settings_service.test.ts @@ -89,6 +89,20 @@ describe('uiSettings', () => { describe('#start', () => { describe('validation', () => { + it('throws if validation schema is not provided', async () => { + const { register } = await service.setup(setupDeps); + register({ + // @ts-expect-error schema is required key + custom: { + value: 42, + }, + }); + + await expect(service.start()).rejects.toMatchInlineSnapshot( + `[Error: Validation schema is not provided for [custom] UI Setting]` + ); + }); + it('validates registered definitions', async () => { const { register } = await service.setup(setupDeps); register({ @@ -125,6 +139,21 @@ describe('uiSettings', () => { `[Error: [ui settings overrides [custom]]: expected value of type [string] but got [number]]` ); }); + + it('do not throw on unknown overrides', async () => { + const coreContext = mockCoreContext.create(); + coreContext.configService.atPath.mockReturnValueOnce( + new BehaviorSubject({ + overrides: { + custom: 42, + }, + }) + ); + const customizedService = new UiSettingsService(coreContext); + await customizedService.setup(setupDeps); + + await customizedService.start(); + }); }); describe('#asScopedToClient', () => { diff --git a/src/core/server/ui_settings/ui_settings_service.ts b/src/core/server/ui_settings/ui_settings_service.ts index 25062490f5b6b..4f757d18ea7da 100644 --- a/src/core/server/ui_settings/ui_settings_service.ts +++ b/src/core/server/ui_settings/ui_settings_service.ts @@ -109,15 +109,17 @@ export class UiSettingsService private validatesDefinitions() { for (const [key, definition] of this.uiSettingsDefaults) { - if (definition.schema) { - definition.schema.validate(definition.value, {}, `ui settings defaults [${key}]`); + if (!definition.schema) { + throw new Error(`Validation schema is not provided for [${key}] UI Setting`); } + definition.schema.validate(definition.value, {}, `ui settings defaults [${key}]`); } } private validatesOverrides() { for (const [key, value] of Object.entries(this.overrides)) { const definition = this.uiSettingsDefaults.get(key); + // overrides might contain UiSettings for a disabled plugin if (definition?.schema) { definition.schema.validate(value, {}, `ui settings overrides [${key}]`); } diff --git a/src/plugins/data/common/constants.ts b/src/plugins/data/common/constants.ts index 43120583bd3a4..a70c5eedbf847 100644 --- a/src/plugins/data/common/constants.ts +++ b/src/plugins/data/common/constants.ts @@ -49,4 +49,5 @@ export const UI_SETTINGS = { INDEXPATTERN_PLACEHOLDER: 'indexPattern:placeholder', FILTERS_PINNED_BY_DEFAULT: 'filters:pinnedByDefault', FILTERS_EDITOR_SUGGEST_VALUES: 'filterEditor:suggestValues', + AUTOCOMPLETE_USE_TIMERANGE: 'autocomplete:useTimeRange', } as const; diff --git a/src/plugins/data/public/autocomplete/providers/query_suggestion_provider.ts b/src/plugins/data/public/autocomplete/providers/query_suggestion_provider.ts index 16500ac9e239a..da523e740c3d6 100644 --- a/src/plugins/data/public/autocomplete/providers/query_suggestion_provider.ts +++ b/src/plugins/data/public/autocomplete/providers/query_suggestion_provider.ts @@ -39,6 +39,7 @@ export interface QuerySuggestionGetFnArgs { selectionStart: number; selectionEnd: number; signal?: AbortSignal; + useTimeRange?: boolean; boolFilter?: any; } diff --git a/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.test.ts b/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.test.ts index 6b0c0f07cf6c9..0ef5b7db958e4 100644 --- a/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.test.ts +++ b/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.test.ts @@ -21,6 +21,26 @@ import { stubIndexPattern, stubFields } from '../../stubs'; import { setupValueSuggestionProvider, ValueSuggestionsGetFn } from './value_suggestion_provider'; import { IUiSettingsClient, CoreSetup } from 'kibana/public'; +jest.mock('../../services', () => ({ + getQueryService: () => ({ + timefilter: { + timefilter: { + createFilter: () => { + return { + time: 'fake', + }; + }, + getTime: () => { + return { + to: 'now', + from: 'now-15m', + }; + }, + }, + }, + }), +})); + describe('FieldSuggestions', () => { let getValueSuggestions: ValueSuggestionsGetFn; let http: any; @@ -94,6 +114,7 @@ describe('FieldSuggestions', () => { indexPattern: stubIndexPattern, field, query: '', + useTimeRange: false, }); expect(http.fetch).toHaveBeenCalled(); @@ -107,6 +128,7 @@ describe('FieldSuggestions', () => { indexPattern: stubIndexPattern, field, query: '', + useTimeRange: false, }; await getValueSuggestions(args); @@ -123,6 +145,7 @@ describe('FieldSuggestions', () => { indexPattern: stubIndexPattern, field, query: '', + useTimeRange: false, }; const { now } = Date; @@ -146,50 +169,76 @@ describe('FieldSuggestions', () => { indexPattern: stubIndexPattern, field: fields[0], query: '', + useTimeRange: false, }); await getValueSuggestions({ indexPattern: stubIndexPattern, field: fields[0], query: 'query', + useTimeRange: false, }); await getValueSuggestions({ indexPattern: stubIndexPattern, field: fields[1], query: '', + useTimeRange: false, }); await getValueSuggestions({ indexPattern: stubIndexPattern, field: fields[1], query: 'query', + useTimeRange: false, }); const customIndexPattern = { ...stubIndexPattern, title: 'customIndexPattern', + useTimeRange: false, }; await getValueSuggestions({ indexPattern: customIndexPattern, field: fields[0], query: '', + useTimeRange: false, }); await getValueSuggestions({ indexPattern: customIndexPattern, field: fields[0], query: 'query', + useTimeRange: false, }); await getValueSuggestions({ indexPattern: customIndexPattern, field: fields[1], query: '', + useTimeRange: false, }); await getValueSuggestions({ indexPattern: customIndexPattern, field: fields[1], query: 'query', + useTimeRange: false, }); expect(http.fetch).toHaveBeenCalledTimes(8); }); + + it('should apply timefilter', async () => { + const [field] = stubFields.filter( + ({ type, aggregatable }) => type === 'string' && aggregatable + ); + + await getValueSuggestions({ + indexPattern: stubIndexPattern, + field, + query: '', + useTimeRange: true, + }); + const callParams = http.fetch.mock.calls[0][1]; + + expect(JSON.parse(callParams.body).filters).toHaveLength(1); + expect(http.fetch).toHaveBeenCalled(); + }); }); }); diff --git a/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.ts b/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.ts index a6a45a26f06b3..fe9f939a0261d 100644 --- a/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.ts +++ b/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.ts @@ -17,15 +17,16 @@ * under the License. */ +import dateMath from '@elastic/datemath'; import { memoize } from 'lodash'; import { CoreSetup } from 'src/core/public'; -import { IIndexPattern, IFieldType, UI_SETTINGS } from '../../../common'; +import { IIndexPattern, IFieldType, UI_SETTINGS, buildQueryFromFilters } from '../../../common'; +import { getQueryService } from '../../services'; -function resolver(title: string, field: IFieldType, query: string, boolFilter: any) { +function resolver(title: string, field: IFieldType, query: string, filters: any[]) { // Only cache results for a minute const ttl = Math.floor(Date.now() / 1000 / 60); - - return [ttl, query, title, field.name, JSON.stringify(boolFilter)].join('|'); + return [ttl, query, title, field.name, JSON.stringify(filters)].join('|'); } export type ValueSuggestionsGetFn = (args: ValueSuggestionsGetFnArgs) => Promise; @@ -34,18 +35,31 @@ interface ValueSuggestionsGetFnArgs { indexPattern: IIndexPattern; field: IFieldType; query: string; + useTimeRange?: boolean; boolFilter?: any[]; signal?: AbortSignal; } +const getAutocompleteTimefilter = (indexPattern: IIndexPattern) => { + const { timefilter } = getQueryService().timefilter; + const timeRange = timefilter.getTime(); + + // Use a rounded timerange so that memoizing works properly + const roundedTimerange = { + from: dateMath.parse(timeRange.from)!.startOf('minute').toISOString(), + to: dateMath.parse(timeRange.to)!.endOf('minute').toISOString(), + }; + return timefilter.createFilter(indexPattern, roundedTimerange); +}; + export const getEmptyValueSuggestions = (() => Promise.resolve([])) as ValueSuggestionsGetFn; export const setupValueSuggestionProvider = (core: CoreSetup): ValueSuggestionsGetFn => { const requestSuggestions = memoize( - (index: string, field: IFieldType, query: string, boolFilter: any = [], signal?: AbortSignal) => + (index: string, field: IFieldType, query: string, filters: any = [], signal?: AbortSignal) => core.http.fetch(`/api/kibana/suggestions/values/${index}`, { method: 'POST', - body: JSON.stringify({ query, field: field.name, boolFilter }), + body: JSON.stringify({ query, field: field.name, filters }), signal, }), resolver @@ -55,12 +69,15 @@ export const setupValueSuggestionProvider = (core: CoreSetup): ValueSuggestionsG indexPattern, field, query, + useTimeRange, boolFilter, signal, }: ValueSuggestionsGetFnArgs): Promise => { const shouldSuggestValues = core!.uiSettings.get( UI_SETTINGS.FILTERS_EDITOR_SUGGEST_VALUES ); + useTimeRange = + useTimeRange ?? core!.uiSettings.get(UI_SETTINGS.AUTOCOMPLETE_USE_TIMERANGE); const { title } = indexPattern; if (field.type === 'boolean') { @@ -69,6 +86,9 @@ export const setupValueSuggestionProvider = (core: CoreSetup): ValueSuggestionsG return []; } - return await requestSuggestions(title, field, query, boolFilter, signal); + const timeFilter = useTimeRange ? getAutocompleteTimefilter(indexPattern) : undefined; + const filterQuery = timeFilter ? buildQueryFromFilters([timeFilter], indexPattern).filter : []; + const filters = [...(boolFilter ? boolFilter : []), ...filterQuery]; + return await requestSuggestions(title, field, query, filters, signal); }; }; diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts index 5abf4d3648af7..afa8d935f367b 100644 --- a/src/plugins/data/public/plugin.ts +++ b/src/plugins/data/public/plugin.ts @@ -72,6 +72,7 @@ import { import { SavedObjectsClientPublicToCommon } from './index_patterns'; import { indexPatternLoad } from './index_patterns/expressions/load_index_pattern'; +import { UsageCollectionSetup } from '../../usage_collection/public'; declare module '../../ui_actions/public' { export interface ActionContextMapping { @@ -94,6 +95,7 @@ export class DataPublicPlugin private readonly fieldFormatsService: FieldFormatsService; private readonly queryService: QueryService; private readonly storage: IStorageWrapper; + private usageCollection: UsageCollectionSetup | undefined; constructor(initializerContext: PluginInitializerContext) { this.searchService = new SearchService(initializerContext); @@ -112,6 +114,8 @@ export class DataPublicPlugin expressions.registerFunction(esaggs); expressions.registerFunction(indexPatternLoad); + this.usageCollection = usageCollection; + const queryService = this.queryService.setup({ uiSettings: core.uiSettings, storage: this.storage, @@ -208,6 +212,7 @@ export class DataPublicPlugin core, data: dataServices, storage: this.storage, + trackUiMetric: this.usageCollection?.reportUiStats.bind(this.usageCollection, 'data_plugin'), }); return { diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 31b05bd4763a2..78b974758f8c0 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -84,6 +84,7 @@ import { TransportRequestPromise } from '@elastic/elasticsearch/lib/Transport'; import { TypeOf } from '@kbn/config-schema'; import { UiActionsSetup } from 'src/plugins/ui_actions/public'; import { UiActionsStart } from 'src/plugins/ui_actions/public'; +import { UiStatsMetricType } from '@kbn/analytics'; import { Unit } from '@elastic/datemath'; import { UnregisterCallback } from 'history'; import { UserProvidedValues } from 'src/core/server/types'; @@ -1840,6 +1841,8 @@ export interface QuerySuggestionGetFnArgs { selectionStart: number; // (undocumented) signal?: AbortSignal; + // (undocumented) + useTimeRange?: boolean; } // Warning: (ae-missing-release-tag) "QuerySuggestionTypes" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -1996,8 +1999,8 @@ export const search: { // Warning: (ae-missing-release-tag) "SearchBar" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const SearchBar: React.ComponentClass, "query" | "isLoading" | "filters" | "onRefresh" | "onRefreshChange" | "refreshInterval" | "indexPatterns" | "dataTestSubj" | "screenTitle" | "showQueryInput" | "showDatePicker" | "showAutoRefreshOnly" | "dateRangeFrom" | "dateRangeTo" | "isRefreshPaused" | "customSubmitButton" | "timeHistory" | "indicateNoData" | "onFiltersUpdated" | "savedQuery" | "showSaveQuery" | "onClearSavedQuery" | "showQueryBar" | "showFilterBar" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated">, any> & { - WrappedComponent: React.ComponentType & ReactIntl.InjectedIntlProps>; +export const SearchBar: React.ComponentClass, "query" | "isLoading" | "filters" | "onRefresh" | "onRefreshChange" | "refreshInterval" | "indexPatterns" | "dataTestSubj" | "screenTitle" | "showQueryInput" | "showDatePicker" | "showAutoRefreshOnly" | "dateRangeFrom" | "dateRangeTo" | "isRefreshPaused" | "customSubmitButton" | "timeHistory" | "indicateNoData" | "onFiltersUpdated" | "trackUiMetric" | "savedQuery" | "showSaveQuery" | "onClearSavedQuery" | "showQueryBar" | "showFilterBar" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated">, any> & { + WrappedComponent: React.ComponentType & ReactIntl.InjectedIntlProps>; }; // Warning: (ae-forgotten-export) The symbol "SearchBarOwnProps" needs to be exported by the entry point index.d.ts @@ -2309,6 +2312,7 @@ export const UI_SETTINGS: { readonly INDEXPATTERN_PLACEHOLDER: "indexPattern:placeholder"; readonly FILTERS_PINNED_BY_DEFAULT: "filters:pinnedByDefault"; readonly FILTERS_EDITOR_SUGGEST_VALUES: "filterEditor:suggestValues"; + readonly AUTOCOMPLETE_USE_TIMERANGE: "autocomplete:useTimeRange"; }; diff --git a/src/plugins/data/public/query/timefilter/timefilter.ts b/src/plugins/data/public/query/timefilter/timefilter.ts index 49a8c68f6916f..7278ceaaddcce 100644 --- a/src/plugins/data/public/query/timefilter/timefilter.ts +++ b/src/plugins/data/public/query/timefilter/timefilter.ts @@ -24,9 +24,14 @@ import { PublicMethodsOf } from '@kbn/utility-types'; import { areRefreshIntervalsDifferent, areTimeRangesDifferent } from './lib/diff_time_picker_vals'; import { getForceNow } from './lib/get_force_now'; import { TimefilterConfig, InputTimeRange, TimeRangeBounds } from './types'; -import { calculateBounds, getTime, RefreshInterval, TimeRange } from '../../../common'; +import { + calculateBounds, + getTime, + IIndexPattern, + RefreshInterval, + TimeRange, +} from '../../../common'; import { TimeHistoryContract } from './time_history'; -import { IndexPattern } from '../../index_patterns'; // TODO: remove! @@ -170,7 +175,7 @@ export class Timefilter { } }; - public createFilter = (indexPattern: IndexPattern, timeRange?: TimeRange) => { + public createFilter = (indexPattern: IIndexPattern, timeRange?: TimeRange) => { return getTime(indexPattern, timeRange ? timeRange : this._time, { forceNow: this.getForceNow(), }); diff --git a/src/plugins/data/public/ui/filter_bar/filter_bar.tsx b/src/plugins/data/public/ui/filter_bar/filter_bar.tsx index 8c009576ff280..194e253fd7b26 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_bar.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_bar.tsx @@ -22,6 +22,7 @@ import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; import classNames from 'classnames'; import React, { useState } from 'react'; +import { METRIC_TYPE, UiStatsMetricType } from '@kbn/analytics'; import { FilterEditor } from './filter_editor'; import { FILTER_EDITOR_WIDTH, FilterItem } from './filter_item'; import { FilterOptions } from './filter_options'; @@ -45,6 +46,9 @@ interface Props { className: string; indexPatterns: IIndexPattern[]; intl: InjectedIntl; + appName: string; + // Track UI Metrics + trackUiMetric?: (metricType: UiStatsMetricType, eventName: string | string[]) => void; } function FilterBarUI(props: Props) { @@ -128,6 +132,9 @@ function FilterBarUI(props: Props) { function onAdd(filter: Filter) { setIsAddFilterPopoverOpen(false); + if (props.trackUiMetric) { + props.trackUiMetric(METRIC_TYPE.CLICK, `${props.appName}:filter_added`); + } const filters = [...props.filters, filter]; onFiltersUpdated(filters); } @@ -139,6 +146,9 @@ function FilterBarUI(props: Props) { } function onUpdate(i: number, filter: Filter) { + if (props.trackUiMetric) { + props.trackUiMetric(METRIC_TYPE.CLICK, `${props.appName}:filter_edited`); + } const filters = [...props.filters]; filters[i] = filter; onFiltersUpdated(filters); @@ -165,11 +175,17 @@ function FilterBarUI(props: Props) { } function onToggleAllNegated() { + if (props.trackUiMetric) { + props.trackUiMetric(METRIC_TYPE.CLICK, `${props.appName}:filter_invertInclusion`); + } const filters = props.filters.map(toggleFilterNegated); onFiltersUpdated(filters); } function onToggleAllDisabled() { + if (props.trackUiMetric) { + props.trackUiMetric(METRIC_TYPE.CLICK, `${props.appName}:filter_toggleAllDisabled`); + } const filters = props.filters.map(toggleFilterDisabled); onFiltersUpdated(filters); } diff --git a/src/plugins/data/public/ui/filter_bar/filter_editor/phrase_suggestor.tsx b/src/plugins/data/public/ui/filter_bar/filter_editor/phrase_suggestor.tsx index 719827a98cc63..c420734a43d41 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_editor/phrase_suggestor.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_editor/phrase_suggestor.tsx @@ -85,6 +85,8 @@ export class PhraseSuggestorUI extends React.Com field, query, signal: this.abortController.signal, + // Show all results in filter bar autocomplete + useTimeRange: false, }); this.setState({ suggestions, isLoading: false }); diff --git a/src/plugins/data/public/ui/search_bar/create_search_bar.tsx b/src/plugins/data/public/ui/search_bar/create_search_bar.tsx index 48e2e8dab7580..f120aae920774 100644 --- a/src/plugins/data/public/ui/search_bar/create_search_bar.tsx +++ b/src/plugins/data/public/ui/search_bar/create_search_bar.tsx @@ -21,6 +21,7 @@ import _ from 'lodash'; import React, { useEffect, useRef } from 'react'; import { CoreStart } from 'src/core/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; +import { UiStatsMetricType } from '@kbn/analytics'; import { KibanaContextProvider } from '../../../../kibana_react/public'; import { QueryStart, SavedQuery } from '../../query'; import { SearchBar, SearchBarOwnProps } from './'; @@ -35,6 +36,7 @@ interface StatefulSearchBarDeps { core: CoreStart; data: Omit; storage: IStorageWrapper; + trackUiMetric?: (metricType: UiStatsMetricType, eventName: string | string[]) => void; } export type StatefulSearchBarProps = SearchBarOwnProps & { @@ -119,7 +121,7 @@ const overrideDefaultBehaviors = (props: StatefulSearchBarProps) => { return props.useDefaultBehaviors ? {} : props; }; -export function createSearchBar({ core, storage, data }: StatefulSearchBarDeps) { +export function createSearchBar({ core, storage, data, trackUiMetric }: StatefulSearchBarDeps) { // App name should come from the core application service. // Until it's available, we'll ask the user to provide it for the pre-wired component. return (props: StatefulSearchBarProps) => { @@ -197,6 +199,7 @@ export function createSearchBar({ core, storage, data }: StatefulSearchBarDeps) onClearSavedQuery={defaultOnClearSavedQuery(props, clearSavedQuery)} onSavedQueryUpdated={defaultOnSavedQueryUpdated(props, setSavedQuery)} onSaved={defaultOnSavedQueryUpdated(props, setSavedQuery)} + trackUiMetric={trackUiMetric} {...overrideDefaultBehaviors(props)} /> diff --git a/src/plugins/data/public/ui/search_bar/search_bar.tsx b/src/plugins/data/public/ui/search_bar/search_bar.tsx index daa6fa0dd80ab..e77f58f572f33 100644 --- a/src/plugins/data/public/ui/search_bar/search_bar.tsx +++ b/src/plugins/data/public/ui/search_bar/search_bar.tsx @@ -24,6 +24,7 @@ import React, { Component } from 'react'; import ResizeObserver from 'resize-observer-polyfill'; import { get, isEqual } from 'lodash'; +import { METRIC_TYPE, UiStatsMetricType } from '@kbn/analytics'; import { withKibana, KibanaReactContextValue } from '../../../../kibana_react/public'; import QueryBarTopRow from '../query_string_input/query_bar_top_row'; @@ -78,6 +79,8 @@ export interface SearchBarOwnProps { onRefresh?: (payload: { dateRange: TimeRange }) => void; indicateNoData?: boolean; + // Track UI Metrics + trackUiMetric?: (metricType: UiStatsMetricType, eventName: string | string[]) => void; } export type SearchBarProps = SearchBarOwnProps & SearchBarInjectedDeps; @@ -331,6 +334,9 @@ class SearchBarUI extends Component { }, }); } + if (this.props.trackUiMetric) { + this.props.trackUiMetric(METRIC_TYPE.CLICK, `${this.services.appName}:query_submitted`); + } } ); }; @@ -432,6 +438,8 @@ class SearchBarUI extends Component { filters={this.props.filters!} onFiltersUpdated={this.props.onFiltersUpdated} indexPatterns={this.props.indexPatterns!} + appName={this.services.appName} + trackUiMetric={this.props.trackUiMetric} /> diff --git a/src/plugins/data/server/autocomplete/value_suggestions_route.ts b/src/plugins/data/server/autocomplete/value_suggestions_route.ts index 6a5b7d1d5b414..89ee0995f4140 100644 --- a/src/plugins/data/server/autocomplete/value_suggestions_route.ts +++ b/src/plugins/data/server/autocomplete/value_suggestions_route.ts @@ -45,7 +45,7 @@ export function registerValueSuggestionsRoute( { field: schema.string(), query: schema.string(), - boolFilter: schema.maybe(schema.any()), + filters: schema.maybe(schema.any()), }, { unknowns: 'allow' } ), @@ -53,7 +53,7 @@ export function registerValueSuggestionsRoute( }, async (context, request, response) => { const config = await config$.pipe(first()).toPromise(); - const { field: fieldName, query, boolFilter } = request.body; + const { field: fieldName, query, filters } = request.body; const { index } = request.params; const { client } = context.core.elasticsearch.legacy; const signal = getRequestAbortedSignal(request.events.aborted$); @@ -66,7 +66,7 @@ export function registerValueSuggestionsRoute( const indexPattern = await findIndexPatternById(context.core.savedObjects.client, index); const field = indexPattern && getFieldByName(fieldName, indexPattern); - const body = await getBody(autocompleteSearchOptions, field || fieldName, query, boolFilter); + const body = await getBody(autocompleteSearchOptions, field || fieldName, query, filters); try { const result = await client.callAsCurrentUser('search', { index, body }, { signal }); @@ -88,7 +88,7 @@ async function getBody( { timeout, terminate_after }: Record, field: IFieldType | string, query: string, - boolFilter: Filter[] = [] + filters: Filter[] = [] ) { const isFieldObject = (f: any): f is IFieldType => Boolean(f && f.name); @@ -108,7 +108,7 @@ async function getBody( terminate_after, query: { bool: { - filter: boolFilter, + filter: filters, }, }, aggs: { diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index 131b3e4c39c6b..2984ca336819a 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -1181,6 +1181,7 @@ export const UI_SETTINGS: { readonly INDEXPATTERN_PLACEHOLDER: "indexPattern:placeholder"; readonly FILTERS_PINNED_BY_DEFAULT: "filters:pinnedByDefault"; readonly FILTERS_EDITOR_SUGGEST_VALUES: "filterEditor:suggestValues"; + readonly AUTOCOMPLETE_USE_TIMERANGE: "autocomplete:useTimeRange"; }; // Warning: (ae-missing-release-tag) "usageProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) diff --git a/src/plugins/data/server/ui_settings.ts b/src/plugins/data/server/ui_settings.ts index 763a086d7688d..9393700a0e771 100644 --- a/src/plugins/data/server/ui_settings.ts +++ b/src/plugins/data/server/ui_settings.ts @@ -684,5 +684,17 @@ export function getUiSettings(): Record> { }), schema: schema.boolean(), }, + [UI_SETTINGS.AUTOCOMPLETE_USE_TIMERANGE]: { + name: i18n.translate('data.advancedSettings.autocompleteIgnoreTimerange', { + defaultMessage: 'Use time range', + description: 'Restrict autocomplete results to the current time range', + }), + value: true, + description: i18n.translate('data.advancedSettings.autocompleteIgnoreTimerangeText', { + defaultMessage: + 'Disable this property to get autocomplete suggestions from your full dataset, rather than from the current time range.', + }), + schema: schema.boolean(), + }, }; } diff --git a/src/plugins/discover/public/application/angular/discover.js b/src/plugins/discover/public/application/angular/discover.js index af763240bccfd..9319c58db3e33 100644 --- a/src/plugins/discover/public/application/angular/discover.js +++ b/src/plugins/discover/public/application/angular/discover.js @@ -83,6 +83,8 @@ import { MODIFY_COLUMNS_ON_SWITCH, } from '../../../common'; import { METRIC_TYPE } from '@kbn/analytics'; +import { SEARCH_SESSION_ID_QUERY_PARAM } from '../../url_generator'; +import { removeQueryParam, getQueryParams } from '../../../../kibana_utils/public'; const fetchStatuses = { UNINITIALIZED: 'uninitialized', @@ -91,6 +93,9 @@ const fetchStatuses = { ERROR: 'error', }; +const getSearchSessionIdFromURL = (history) => + getQueryParams(history.location)[SEARCH_SESSION_ID_QUERY_PARAM]; + const app = getAngularModule(); app.config(($routeProvider) => { @@ -208,6 +213,8 @@ function discoverController($element, $route, $scope, $timeout, $window, Promise }; const history = getHistory(); + // used for restoring background session + let isInitialSearch = true; const { appStateContainer, @@ -798,17 +805,30 @@ function discoverController($element, $route, $scope, $timeout, $window, Promise if (abortController) abortController.abort(); abortController = new AbortController(); - const sessionId = data.search.session.start(); + const searchSessionId = (() => { + const searchSessionIdFromURL = getSearchSessionIdFromURL(history); + if (searchSessionIdFromURL) { + if (isInitialSearch) { + data.search.session.restore(searchSessionIdFromURL); + isInitialSearch = false; + return searchSessionIdFromURL; + } else { + // navigating away from background search + removeQueryParam(history, SEARCH_SESSION_ID_QUERY_PARAM); + } + } + return data.search.session.start(); + })(); $scope .updateDataSource() .then(setupVisualization) .then(function () { $scope.fetchStatus = fetchStatuses.LOADING; - logInspectorRequest(); + logInspectorRequest({ searchSessionId }); return $scope.searchSource.fetch({ abortSignal: abortController.signal, - sessionId, + sessionId: searchSessionId, }); }) .then(onResults) @@ -900,7 +920,7 @@ function discoverController($element, $route, $scope, $timeout, $window, Promise $scope.fetchStatus = fetchStatuses.COMPLETE; } - function logInspectorRequest() { + function logInspectorRequest({ searchSessionId = null } = { searchSessionId: null }) { inspectorAdapters.requests.reset(); const title = i18n.translate('discover.inspectorRequestDataTitle', { defaultMessage: 'data', @@ -908,7 +928,7 @@ function discoverController($element, $route, $scope, $timeout, $window, Promise const description = i18n.translate('discover.inspectorRequestDescription', { defaultMessage: 'This request queries Elasticsearch to fetch the data for the search.', }); - inspectorRequest = inspectorAdapters.requests.start(title, { description }); + inspectorRequest = inspectorAdapters.requests.start(title, { description, searchSessionId }); inspectorRequest.stats(getRequestInspectorStats($scope.searchSource)); $scope.searchSource.getSearchRequestBody().then((body) => { inspectorRequest.json(body); diff --git a/src/plugins/discover/public/url_generator.test.ts b/src/plugins/discover/public/url_generator.test.ts index a18ee486ab007..98b7625e63c72 100644 --- a/src/plugins/discover/public/url_generator.test.ts +++ b/src/plugins/discover/public/url_generator.test.ts @@ -212,6 +212,15 @@ describe('Discover url generator', () => { }); }); + test('can specify a search session id', async () => { + const { generator } = await setup(); + const url = await generator.createUrl({ + searchSessionId: '__test__', + }); + expect(url).toMatchInlineSnapshot(`"xyz/app/discover#/?_g=()&_a=()&searchSessionId=__test__"`); + expect(url).toContain('__test__'); + }); + describe('useHash property', () => { describe('when default useHash is set to false', () => { test('when using default, sets index pattern ID in the generated URL', async () => { diff --git a/src/plugins/discover/public/url_generator.ts b/src/plugins/discover/public/url_generator.ts index c7f2e2147e819..df9b16a4627ec 100644 --- a/src/plugins/discover/public/url_generator.ts +++ b/src/plugins/discover/public/url_generator.ts @@ -67,6 +67,11 @@ export interface DiscoverUrlGeneratorState { * whether to hash the data in the url to avoid url length issues. */ useHash?: boolean; + + /** + * Background search session id + */ + searchSessionId?: string; } interface Params { @@ -74,6 +79,8 @@ interface Params { useHash: boolean; } +export const SEARCH_SESSION_ID_QUERY_PARAM = 'searchSessionId'; + export class DiscoverUrlGenerator implements UrlGeneratorsDefinition { constructor(private readonly params: Params) {} @@ -88,6 +95,7 @@ export class DiscoverUrlGenerator savedSearchId, timeRange, useHash = this.params.useHash, + searchSessionId, }: DiscoverUrlGeneratorState): Promise => { const savedSearchPath = savedSearchId ? encodeURIComponent(savedSearchId) : ''; const appState: { @@ -111,6 +119,10 @@ export class DiscoverUrlGenerator url = setStateToKbnUrl('_g', queryState, { useHash }, url); url = setStateToKbnUrl('_a', appState, { useHash }, url); + if (searchSessionId) { + url = `${url}&${SEARCH_SESSION_ID_QUERY_PARAM}=${searchSessionId}`; + } + return url; }; } diff --git a/src/plugins/embeddable/public/public.api.md b/src/plugins/embeddable/public/public.api.md index 4406dded98547..6a2565edf2f67 100644 --- a/src/plugins/embeddable/public/public.api.md +++ b/src/plugins/embeddable/public/public.api.md @@ -80,6 +80,7 @@ import { TransportRequestParams } from '@elastic/elasticsearch/lib/Transport'; import { TransportRequestPromise } from '@elastic/elasticsearch/lib/Transport'; import { TypeOf } from '@kbn/config-schema'; import { UiComponent } from 'src/plugins/kibana_utils/public'; +import { UiStatsMetricType } from '@kbn/analytics'; import { UnregisterCallback } from 'history'; import { UserProvidedValues } from 'src/core/server/types'; diff --git a/src/plugins/expressions/common/expression_functions/index.ts b/src/plugins/expressions/common/expression_functions/index.ts index b29e6b78b8f4d..094fbe83efd22 100644 --- a/src/plugins/expressions/common/expression_functions/index.ts +++ b/src/plugins/expressions/common/expression_functions/index.ts @@ -22,3 +22,4 @@ export * from './arguments'; export * from './expression_function_parameter'; export * from './expression_function'; export * from './specs'; +export * from './series_calculation_helpers'; diff --git a/src/plugins/expressions/common/expression_functions/specs/series_calculation_helpers.ts b/src/plugins/expressions/common/expression_functions/series_calculation_helpers.ts similarity index 97% rename from src/plugins/expressions/common/expression_functions/specs/series_calculation_helpers.ts rename to src/plugins/expressions/common/expression_functions/series_calculation_helpers.ts index 8ba9d527d4c59..99ad2098ab10a 100644 --- a/src/plugins/expressions/common/expression_functions/specs/series_calculation_helpers.ts +++ b/src/plugins/expressions/common/expression_functions/series_calculation_helpers.ts @@ -18,7 +18,7 @@ */ import { i18n } from '@kbn/i18n'; -import { Datatable, DatatableRow } from '../../expression_types'; +import { Datatable, DatatableRow } from '../expression_types'; /** * Returns a string identifying the group of a row by a list of columns to group by diff --git a/src/plugins/expressions/common/expression_functions/specs/cumulative_sum.ts b/src/plugins/expressions/common/expression_functions/specs/cumulative_sum.ts index 672abadd3c016..0d9547f70dd3b 100644 --- a/src/plugins/expressions/common/expression_functions/specs/cumulative_sum.ts +++ b/src/plugins/expressions/common/expression_functions/specs/cumulative_sum.ts @@ -20,7 +20,7 @@ import { i18n } from '@kbn/i18n'; import { ExpressionFunctionDefinition } from '../types'; import { Datatable } from '../../expression_types'; -import { buildResultColumns, getBucketIdentifier } from './series_calculation_helpers'; +import { buildResultColumns, getBucketIdentifier } from '../series_calculation_helpers'; export interface CumulativeSumArgs { by?: string[]; diff --git a/src/plugins/expressions/common/expression_functions/specs/derivative.ts b/src/plugins/expressions/common/expression_functions/specs/derivative.ts index 44ac198e2d17c..320a254bf94a9 100644 --- a/src/plugins/expressions/common/expression_functions/specs/derivative.ts +++ b/src/plugins/expressions/common/expression_functions/specs/derivative.ts @@ -20,7 +20,7 @@ import { i18n } from '@kbn/i18n'; import { ExpressionFunctionDefinition } from '../types'; import { Datatable } from '../../expression_types'; -import { buildResultColumns, getBucketIdentifier } from './series_calculation_helpers'; +import { buildResultColumns, getBucketIdentifier } from '../series_calculation_helpers'; export interface DerivativeArgs { by?: string[]; diff --git a/src/plugins/expressions/common/expression_functions/specs/moving_average.ts b/src/plugins/expressions/common/expression_functions/specs/moving_average.ts index 00a4d8c45839e..c4887e0240ec0 100644 --- a/src/plugins/expressions/common/expression_functions/specs/moving_average.ts +++ b/src/plugins/expressions/common/expression_functions/specs/moving_average.ts @@ -20,7 +20,7 @@ import { i18n } from '@kbn/i18n'; import { ExpressionFunctionDefinition } from '../types'; import { Datatable } from '../../expression_types'; -import { buildResultColumns, getBucketIdentifier } from './series_calculation_helpers'; +import { buildResultColumns, getBucketIdentifier } from '../series_calculation_helpers'; export interface MovingAverageArgs { by?: string[]; diff --git a/test/functional/apps/management/_index_patterns_empty.ts b/test/functional/apps/management/_index_patterns_empty.ts index 4ae2e7836ac37..2a1d723f1a06e 100644 --- a/test/functional/apps/management/_index_patterns_empty.ts +++ b/test/functional/apps/management/_index_patterns_empty.ts @@ -22,6 +22,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getPageObjects, getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); + const log = getService('log'); const PageObjects = getPageObjects(['common', 'settings']); const testSubjects = getService('testSubjects'); const globalNav = getService('globalNav'); @@ -30,6 +31,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { describe('index pattern empty view', () => { before(async () => { await esArchiver.load('empty_kibana'); + await esArchiver.unload('logstash_functional'); + await esArchiver.unload('makelogs'); await kibanaServer.uiSettings.replace({}); await PageObjects.settings.navigateTo(); }); @@ -37,16 +40,26 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { after(async () => { await esArchiver.unload('empty_kibana'); await esArchiver.loadIfNeeded('makelogs'); - }); - - // create index pattern and return to verify list - it(`shows empty views`, async () => { // @ts-expect-error await es.transport.request({ - path: '/_all', + path: '/logstash-a', method: 'DELETE', }); + }); + + // create index pattern and return to verify list + it(`shows empty views`, async () => { await PageObjects.settings.clickKibanaIndexPatterns(); + log.debug( + `\n\nNOTE: If this test fails make sure there aren't any non-system indices in the _cat/indices output (use esArchiver.unload on them)` + ); + log.debug( + // @ts-expect-error + await es.transport.request({ + path: '/_cat/indices', + method: 'GET', + }) + ); await testSubjects.existOrFail('createAnyway'); // @ts-expect-error await es.transport.request({ diff --git a/test/functional/services/common/browser.ts b/test/functional/services/common/browser.ts index f5fb54c72177f..b3b7fd32eae19 100644 --- a/test/functional/services/common/browser.ts +++ b/test/functional/services/common/browser.ts @@ -216,13 +216,17 @@ export async function BrowserProvider({ getService }: FtrProviderContext) { * Does a drag-and-drop action from one point to another * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/input_exports_Actions.html#dragAndDrop * - * @param {{element: WebElementWrapper | {x: number, y: number}, offset: {x: number, y: number}}} from - * @param {{element: WebElementWrapper | {x: number, y: number}, offset: {x: number, y: number}}} to * @return {Promise} */ public async dragAndDrop( - from: { offset?: { x: any; y: any }; location: any }, - to: { offset?: { x: any; y: any }; location: any } + from: { + location: WebElementWrapper | { x?: number; y?: number }; + offset?: { x?: number; y?: number }; + }, + to: { + location: WebElementWrapper | { x?: number; y?: number }; + offset?: { x?: number; y?: number }; + } ) { // The offset should be specified in pixels relative to the center of the element's bounding box const getW3CPoint = (data: any) => { @@ -230,7 +234,11 @@ export async function BrowserProvider({ getService }: FtrProviderContext) { data.offset = {}; } return data.location instanceof WebElementWrapper - ? { x: data.offset.x || 0, y: data.offset.y || 0, origin: data.location._webElement } + ? { + x: data.offset.x || 0, + y: data.offset.y || 0, + origin: data.location._webElement, + } : { x: data.location.x, y: data.location.y, origin: Origin.POINTER }; }; @@ -240,6 +248,62 @@ export async function BrowserProvider({ getService }: FtrProviderContext) { return await this.getActions().move(startPoint).press().move(endPoint).release().perform(); } + /** + * Performs drag and drop for html5 native drag and drop implementation + * There's a bug in Chromedriver for html5 dnd that doesn't allow to use the method `dragAndDrop` defined above + * https://github.com/SeleniumHQ/selenium/issues/6235 + * This implementation simulates user's action by calling the drag and drop specific events directly. + * + * @param {string} from html selector + * @param {string} to html selector + * @return {Promise} + */ + public async html5DragAndDrop(from: string, to: string) { + await this.execute( + ` + function createEvent(typeOfEvent) { + const event = document.createEvent("CustomEvent"); + event.initCustomEvent(typeOfEvent, true, true, null); + event.dataTransfer = { + data: {}, + setData: function (key, value) { + this.data[key] = value; + }, + getData: function (key) { + return this.data[key]; + } + }; + return event; + } + function dispatchEvent(element, event, transferData) { + if (transferData !== undefined) { + event.dataTransfer = transferData; + } + if (element.dispatchEvent) { + element.dispatchEvent(event); + } else if (element.fireEvent) { + element.fireEvent("on" + event.type, event); + } + } + + const origin = document.querySelector(arguments[0]); + const target = document.querySelector(arguments[1]); + + const dragStartEvent = createEvent('dragstart'); + dispatchEvent(origin, dragStartEvent); + + setTimeout(() => { + const dropEvent = createEvent('drop'); + dispatchEvent(target, dropEvent, dragStartEvent.dataTransfer); + const dragEndEvent = createEvent('dragend'); + dispatchEvent(origin, dragEndEvent, dropEvent.dataTransfer); + }, 50); + `, + from, + to + ); + } + /** * Reloads the current browser window/frame. * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/webdriver_exports_Navigation.html#refresh diff --git a/x-pack/plugins/apm/public/components/app/Home/alerting_popover_flyout/index.tsx b/x-pack/plugins/apm/public/application/action_menu/alerting_popover_flyout.tsx similarity index 83% rename from x-pack/plugins/apm/public/components/app/Home/alerting_popover_flyout/index.tsx rename to x-pack/plugins/apm/public/application/action_menu/alerting_popover_flyout.tsx index 7e6331c1fa3a8..394b4caea3e7b 100644 --- a/x-pack/plugins/apm/public/components/app/Home/alerting_popover_flyout/index.tsx +++ b/x-pack/plugins/apm/public/application/action_menu/alerting_popover_flyout.tsx @@ -5,16 +5,16 @@ */ import { - EuiButtonEmpty, EuiContextMenu, EuiContextMenuPanelDescriptor, + EuiHeaderLink, EuiPopover, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useState } from 'react'; -import { AlertType } from '../../../../../common/alert_types'; -import { useApmPluginContext } from '../../../../hooks/useApmPluginContext'; -import { AlertingFlyout } from '../../../alerting/AlertingFlyout'; +import { IBasePath } from '../../../../../../src/core/public'; +import { AlertType } from '../../../common/alert_types'; +import { AlertingFlyout } from '../../components/alerting/AlertingFlyout'; const alertLabel = i18n.translate('xpack.apm.home.alertsMenu.alerts', { defaultMessage: 'Alerts', @@ -46,28 +46,32 @@ const CREATE_TRANSACTION_ERROR_RATE_ALERT_PANEL_ID = const CREATE_ERROR_COUNT_ALERT_PANEL_ID = 'create_error_count_panel'; interface Props { + basePath: IBasePath; canReadAlerts: boolean; canSaveAlerts: boolean; canReadAnomalies: boolean; + includeTransactionDuration: boolean; } -export function AlertingPopoverAndFlyout(props: Props) { - const { canSaveAlerts, canReadAlerts, canReadAnomalies } = props; - - const plugin = useApmPluginContext(); - +export function AlertingPopoverAndFlyout({ + basePath, + canSaveAlerts, + canReadAlerts, + canReadAnomalies, + includeTransactionDuration, +}: Props) { const [popoverOpen, setPopoverOpen] = useState(false); - const [alertType, setAlertType] = useState(null); const button = ( - setPopoverOpen(true)} + onClick={() => setPopoverOpen((prevState) => !prevState)} > {alertLabel} - + ); const panels: EuiContextMenuPanelDescriptor[] = [ @@ -98,7 +102,7 @@ export function AlertingPopoverAndFlyout(props: Props) { 'xpack.apm.home.alertsMenu.viewActiveAlerts', { defaultMessage: 'View active alerts' } ), - href: plugin.core.http.basePath.prepend( + href: basePath.prepend( '/app/management/insightsAndAlerting/triggersActions/alerts' ), icon: 'tableOfContents', @@ -113,6 +117,19 @@ export function AlertingPopoverAndFlyout(props: Props) { id: CREATE_TRANSACTION_DURATION_ALERT_PANEL_ID, title: transactionDurationLabel, items: [ + // threshold alerts + ...(includeTransactionDuration + ? [ + { + name: createThresholdAlertLabel, + onClick: () => { + setAlertType(AlertType.TransactionDuration); + setPopoverOpen(false); + }, + }, + ] + : []), + // anomaly alerts ...(canReadAnomalies ? [ diff --git a/x-pack/plugins/apm/public/components/shared/Links/apm/anomaly_detection_setup_link.test.tsx b/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.test.tsx similarity index 96% rename from x-pack/plugins/apm/public/components/shared/Links/apm/anomaly_detection_setup_link.test.tsx rename to x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.test.tsx index a53468e2ad06c..b90f606d276eb 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/apm/anomaly_detection_setup_link.test.tsx +++ b/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.test.tsx @@ -6,8 +6,8 @@ import React from 'react'; import { render, fireEvent, waitFor } from '@testing-library/react'; -import { MissingJobsAlert } from './AnomalyDetectionSetupLink'; -import * as hooks from '../../../../hooks/useFetcher'; +import { MissingJobsAlert } from './anomaly_detection_setup_link'; +import * as hooks from '../../hooks/useFetcher'; async function renderTooltipAnchor({ jobs, diff --git a/x-pack/plugins/apm/public/components/shared/Links/apm/AnomalyDetectionSetupLink.tsx b/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.tsx similarity index 67% rename from x-pack/plugins/apm/public/components/shared/Links/apm/AnomalyDetectionSetupLink.tsx rename to x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.tsx index 368837b3c9411..d75446cb0dd48 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/apm/AnomalyDetectionSetupLink.tsx +++ b/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.tsx @@ -3,19 +3,25 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; -import { EuiButtonEmpty, EuiToolTip, EuiIcon } from '@elastic/eui'; +import { + EuiHeaderLink, + EuiIcon, + EuiLoadingSpinner, + EuiToolTip, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { useApmPluginContext } from '../../../../hooks/useApmPluginContext'; -import { APIReturnType } from '../../../../services/rest/createCallApmApi'; -import { APMLink } from './APMLink'; +import React from 'react'; import { ENVIRONMENT_ALL, getEnvironmentLabel, -} from '../../../../../common/environment_filter_values'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; -import { useFetcher, FETCH_STATUS } from '../../../../hooks/useFetcher'; -import { useLicense } from '../../../../hooks/useLicense'; +} from '../../../common/environment_filter_values'; +import { getAPMHref } from '../../components/shared/Links/apm/APMLink'; +import { useApmPluginContext } from '../../hooks/useApmPluginContext'; +import { FETCH_STATUS, useFetcher } from '../../hooks/useFetcher'; +import { useLicense } from '../../hooks/useLicense'; +import { useUrlParams } from '../../hooks/useUrlParams'; +import { APIReturnType } from '../../services/rest/createCallApmApi'; +import { units } from '../../style/variables'; export type AnomalyDetectionApiResponse = APIReturnType< '/api/apm/settings/anomaly-detection', @@ -27,24 +33,27 @@ const DEFAULT_DATA = { jobs: [], hasLegacyJobs: false }; export function AnomalyDetectionSetupLink() { const { uiFilters } = useUrlParams(); const environment = uiFilters.environment; - const plugin = useApmPluginContext(); - const canGetJobs = !!plugin.core.application.capabilities.ml?.canGetJobs; + const { core } = useApmPluginContext(); + const canGetJobs = !!core.application.capabilities.ml?.canGetJobs; const license = useLicense(); const hasValidLicense = license?.isActive && license?.hasAtLeast('platinum'); + const { basePath } = core.http; return ( - - - {ANOMALY_DETECTION_LINK_LABEL} - - {canGetJobs && hasValidLicense ? ( - ) : null} - + ) : ( + + )} + + {ANOMALY_DETECTION_LINK_LABEL} + + ); } @@ -56,8 +65,14 @@ export function MissingJobsAlert({ environment }: { environment?: string }) { { preservePreviousData: false, showToastOnError: false } ); + const defaultIcon = ; + + if (status === FETCH_STATUS.LOADING) { + return ; + } + if (status !== FETCH_STATUS.SUCCESS) { - return null; + return defaultIcon; } const isEnvironmentSelected = @@ -65,7 +80,7 @@ export function MissingJobsAlert({ environment }: { environment?: string }) { // there are jobs for at least one environment if (!isEnvironmentSelected && data.jobs.length > 0) { - return null; + return defaultIcon; } // there are jobs for the selected environment @@ -73,7 +88,7 @@ export function MissingJobsAlert({ environment }: { environment?: string }) { isEnvironmentSelected && data.jobs.some((job) => environment === job.environment) ) { - return null; + return defaultIcon; } return ( diff --git a/x-pack/plugins/apm/public/application/action_menu/index.tsx b/x-pack/plugins/apm/public/application/action_menu/index.tsx new file mode 100644 index 0000000000000..1713ef61fac1e --- /dev/null +++ b/x-pack/plugins/apm/public/application/action_menu/index.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiHeaderLink, EuiHeaderLinks } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { useParams } from 'react-router-dom'; +import { getAlertingCapabilities } from '../../components/alerting/get_alert_capabilities'; +import { getAPMHref } from '../../components/shared/Links/apm/APMLink'; +import { useApmPluginContext } from '../../hooks/useApmPluginContext'; +import { AlertingPopoverAndFlyout } from './alerting_popover_flyout'; +import { AnomalyDetectionSetupLink } from './anomaly_detection_setup_link'; + +export function ActionMenu() { + const { core, plugins } = useApmPluginContext(); + const { serviceName } = useParams<{ serviceName?: string }>(); + const { search } = window.location; + const { application, http } = core; + const { basePath } = http; + const { capabilities } = application; + const canAccessML = !!capabilities.ml?.canAccessML; + const { + isAlertingAvailable, + canReadAlerts, + canSaveAlerts, + canReadAnomalies, + } = getAlertingCapabilities(plugins, capabilities); + + function apmHref(path: string) { + return getAPMHref({ basePath, path, search }); + } + + function kibanaHref(path: string) { + return basePath.prepend(path); + } + + return ( + + + {i18n.translate('xpack.apm.settingsLinkLabel', { + defaultMessage: 'Settings', + })} + + {isAlertingAvailable && ( + + )} + {canAccessML && } + + {i18n.translate('xpack.apm.addDataButtonLabel', { + defaultMessage: 'Add data', + })} + + + ); +} diff --git a/x-pack/plugins/apm/public/application/application.test.tsx b/x-pack/plugins/apm/public/application/application.test.tsx index 97700b9bc96b7..75b7835c13151 100644 --- a/x-pack/plugins/apm/public/application/application.test.tsx +++ b/x-pack/plugins/apm/public/application/application.test.tsx @@ -53,6 +53,7 @@ describe('renderApp', () => { const params = { element: document.createElement('div'), history: createMemoryHistory(), + setHeaderActionMenu: () => {}, }; jest.spyOn(window, 'scrollTo').mockReturnValueOnce(undefined); createCallApmApi((core.http as unknown) as HttpSetup); diff --git a/x-pack/plugins/apm/public/application/csmApp.tsx b/x-pack/plugins/apm/public/application/csmApp.tsx index d8f54c7bfc94f..dfc3d6b4b9ec8 100644 --- a/x-pack/plugins/apm/public/application/csmApp.tsx +++ b/x-pack/plugins/apm/public/application/csmApp.tsx @@ -66,21 +66,23 @@ function CsmApp() { } export function CsmAppRoot({ + appMountParameters, core, deps, - history, config, corePlugins: { embeddable }, }: { + appMountParameters: AppMountParameters; core: CoreStart; deps: ApmPluginSetupDeps; - history: AppMountParameters['history']; config: ConfigSchema; corePlugins: ApmPluginStartDeps; }) { + const { history } = appMountParameters; const i18nCore = core.i18n; const plugins = deps; const apmPluginContextValue = { + appMountParameters, config, core, plugins, @@ -109,10 +111,12 @@ export function CsmAppRoot({ export const renderApp = ( core: CoreStart, deps: ApmPluginSetupDeps, - { element, history }: AppMountParameters, + appMountParameters: AppMountParameters, config: ConfigSchema, corePlugins: ApmPluginStartDeps ) => { + const { element } = appMountParameters; + createCallApmApi(core.http); // Automatically creates static index pattern and stores as saved object @@ -123,9 +127,9 @@ export const renderApp = ( ReactDOM.render( , diff --git a/x-pack/plugins/apm/public/application/index.tsx b/x-pack/plugins/apm/public/application/index.tsx index 4e3217ce17ed1..2e1f259bd8c42 100644 --- a/x-pack/plugins/apm/public/application/index.tsx +++ b/x-pack/plugins/apm/public/application/index.tsx @@ -22,7 +22,10 @@ import { import { AlertsContextProvider } from '../../../triggers_actions_ui/public'; import { routes } from '../components/app/Main/route_config'; import { ScrollToTopOnPathChange } from '../components/app/Main/ScrollToTopOnPathChange'; -import { ApmPluginContext } from '../context/ApmPluginContext'; +import { + ApmPluginContext, + ApmPluginContextValue, +} from '../context/ApmPluginContext'; import { LicenseProvider } from '../context/LicenseContext'; import { UrlParamsProvider } from '../context/UrlParamsContext'; import { useBreadcrumbs } from '../hooks/use_breadcrumbs'; @@ -64,23 +67,14 @@ function App() { } export function ApmAppRoot({ - core, - deps, - history, - config, + apmPluginContextValue, }: { - core: CoreStart; - deps: ApmPluginSetupDeps; - history: AppMountParameters['history']; - config: ConfigSchema; + apmPluginContextValue: ApmPluginContextValue; }) { + const { appMountParameters, core, plugins } = apmPluginContextValue; + const { history } = appMountParameters; const i18nCore = core.i18n; - const plugins = deps; - const apmPluginContextValue = { - config, - core, - plugins, - }; + return ( @@ -117,14 +111,21 @@ export function ApmAppRoot({ export const renderApp = ( core: CoreStart, - deps: ApmPluginSetupDeps, - { element, history }: AppMountParameters, + setupDeps: ApmPluginSetupDeps, + appMountParameters: AppMountParameters, config: ConfigSchema ) => { + const { element } = appMountParameters; + const apmPluginContextValue = { + appMountParameters, + config, + core, + plugins: setupDeps, + }; + // render APM feedback link in global help menu setHelpExtension(core); setReadonlyBadge(core); - createCallApmApi(core.http); // Automatically creates static index pattern and stores as saved object @@ -134,7 +135,7 @@ export const renderApp = ( }); ReactDOM.render( - , + , element ); return () => { diff --git a/x-pack/plugins/apm/public/components/app/Home/__snapshots__/Home.test.tsx.snap b/x-pack/plugins/apm/public/components/app/Home/__snapshots__/Home.test.tsx.snap index 0a22604837b97..82fabff610191 100644 --- a/x-pack/plugins/apm/public/components/app/Home/__snapshots__/Home.test.tsx.snap +++ b/x-pack/plugins/apm/public/components/app/Home/__snapshots__/Home.test.tsx.snap @@ -4,6 +4,9 @@ exports[`Home component should render services 1`] = ` - {i18n.translate('xpack.apm.home.servicesTabLabel', { - defaultMessage: 'Services', - })} - - ), - render: () => , - name: 'services', - }, - { - link: ( - - {i18n.translate('xpack.apm.home.tracesTabLabel', { - defaultMessage: 'Traces', - })} - - ), - render: () => , - name: 'traces', - }, - ]; - - if (serviceMapEnabled) { - homeTabs.push({ - link: ( - - {i18n.translate('xpack.apm.home.serviceMapTabLabel', { - defaultMessage: 'Service Map', - })} - - ), - render: () => , - name: 'service-map', - }); - } - - return homeTabs; -} - -const SETTINGS_LINK_LABEL = i18n.translate('xpack.apm.settingsLinkLabel', { - defaultMessage: 'Settings', -}); +const homeTabs = [ + { + link: ( + + {i18n.translate('xpack.apm.home.servicesTabLabel', { + defaultMessage: 'Services', + })} + + ), + render: () => , + name: 'services', + }, + { + link: ( + + {i18n.translate('xpack.apm.home.tracesTabLabel', { + defaultMessage: 'Traces', + })} + + ), + render: () => , + name: 'traces', + }, + { + link: ( + + {i18n.translate('xpack.apm.home.serviceMapTabLabel', { + defaultMessage: 'Service Map', + })} + + ), + render: () => , + name: 'service-map', + }, +]; interface Props { tab: 'traces' | 'services' | 'service-map'; } export function Home({ tab }: Props) { - const { config, core, plugins } = useApmPluginContext(); - const capabilities = core.application.capabilities; - const canAccessML = !!capabilities.ml?.canAccessML; - const homeTabs = getHomeTabs(config); const selectedTab = homeTabs.find( (homeTab) => homeTab.name === tab ) as $ElementType; - const { - isAlertingAvailable, - canReadAlerts, - canSaveAlerts, - canReadAnomalies, - } = getAlertingCapabilities(plugins, core.application.capabilities); - return (
- - - -

APM

-
-
- - - - {SETTINGS_LINK_LABEL} - - - - {isAlertingAvailable && ( - - - - )} - {canAccessML && ( - - - - )} - - - -
+ +

APM

+
{homeTabs.map((homeTab) => ( diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/EmbeddedMap.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/EmbeddedMap.tsx index 77afe92a8f521..b757635af1702 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/EmbeddedMap.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/EmbeddedMap.tsx @@ -77,6 +77,7 @@ export function EmbeddedMapComponent() { ); const input: MapEmbeddableInput = { + attributes: { title: '' }, id: uuid.v4(), filters: mapFilters, refreshConfig: { diff --git a/x-pack/plugins/apm/public/components/app/ServiceDetails/alerting_popover_flyout/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceDetails/alerting_popover_flyout/index.tsx deleted file mode 100644 index 3a8d24f0a8b02..0000000000000 --- a/x-pack/plugins/apm/public/components/app/ServiceDetails/alerting_popover_flyout/index.tsx +++ /dev/null @@ -1,197 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { - EuiButtonEmpty, - EuiContextMenu, - EuiContextMenuPanelDescriptor, - EuiPopover, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import React, { useState } from 'react'; -import { AlertType } from '../../../../../common/alert_types'; -import { useApmPluginContext } from '../../../../hooks/useApmPluginContext'; -import { AlertingFlyout } from '../../../alerting/AlertingFlyout'; - -const alertLabel = i18n.translate( - 'xpack.apm.serviceDetails.alertsMenu.alerts', - { defaultMessage: 'Alerts' } -); -const transactionDurationLabel = i18n.translate( - 'xpack.apm.serviceDetails.alertsMenu.transactionDuration', - { defaultMessage: 'Transaction duration' } -); -const transactionErrorRateLabel = i18n.translate( - 'xpack.apm.serviceDetails.alertsMenu.transactionErrorRate', - { defaultMessage: 'Transaction error rate' } -); -const errorCountLabel = i18n.translate( - 'xpack.apm.serviceDetails.alertsMenu.errorCount', - { defaultMessage: 'Error count' } -); -const createThresholdAlertLabel = i18n.translate( - 'xpack.apm.serviceDetails.alertsMenu.createThresholdAlert', - { defaultMessage: 'Create threshold alert' } -); -const createAnomalyAlertAlertLabel = i18n.translate( - 'xpack.apm.serviceDetails.alertsMenu.createAnomalyAlert', - { defaultMessage: 'Create anomaly alert' } -); - -const CREATE_TRANSACTION_DURATION_ALERT_PANEL_ID = - 'create_transaction_duration_panel'; -const CREATE_TRANSACTION_ERROR_RATE_ALERT_PANEL_ID = - 'create_transaction_error_rate_panel'; -const CREATE_ERROR_COUNT_ALERT_PANEL_ID = 'create_error_count_panel'; - -interface Props { - canReadAlerts: boolean; - canSaveAlerts: boolean; - canReadAnomalies: boolean; -} - -export function AlertingPopoverAndFlyout(props: Props) { - const { canSaveAlerts, canReadAlerts, canReadAnomalies } = props; - - const plugin = useApmPluginContext(); - - const [popoverOpen, setPopoverOpen] = useState(false); - - const [alertType, setAlertType] = useState(null); - - const button = ( - setPopoverOpen(true)} - > - {alertLabel} - - ); - - const panels: EuiContextMenuPanelDescriptor[] = [ - { - id: 0, - title: alertLabel, - items: [ - ...(canSaveAlerts - ? [ - { - name: transactionDurationLabel, - panel: CREATE_TRANSACTION_DURATION_ALERT_PANEL_ID, - }, - { - name: transactionErrorRateLabel, - panel: CREATE_TRANSACTION_ERROR_RATE_ALERT_PANEL_ID, - }, - { - name: errorCountLabel, - panel: CREATE_ERROR_COUNT_ALERT_PANEL_ID, - }, - ] - : []), - ...(canReadAlerts - ? [ - { - name: i18n.translate( - 'xpack.apm.serviceDetails.alertsMenu.viewActiveAlerts', - { defaultMessage: 'View active alerts' } - ), - href: plugin.core.http.basePath.prepend( - '/app/management/insightsAndAlerting/triggersActions/alerts' - ), - icon: 'tableOfContents', - }, - ] - : []), - ], - }, - - // transaction duration panel - { - id: CREATE_TRANSACTION_DURATION_ALERT_PANEL_ID, - title: transactionDurationLabel, - items: [ - // threshold alerts - { - name: createThresholdAlertLabel, - onClick: () => { - setAlertType(AlertType.TransactionDuration); - setPopoverOpen(false); - }, - }, - - // anomaly alerts - ...(canReadAnomalies - ? [ - { - name: createAnomalyAlertAlertLabel, - onClick: () => { - setAlertType(AlertType.TransactionDurationAnomaly); - setPopoverOpen(false); - }, - }, - ] - : []), - ], - }, - - // transaction error rate panel - { - id: CREATE_TRANSACTION_ERROR_RATE_ALERT_PANEL_ID, - title: transactionErrorRateLabel, - items: [ - // threshold alerts - { - name: createThresholdAlertLabel, - onClick: () => { - setAlertType(AlertType.TransactionErrorRate); - setPopoverOpen(false); - }, - }, - ], - }, - - // error alerts panel - { - id: CREATE_ERROR_COUNT_ALERT_PANEL_ID, - title: errorCountLabel, - items: [ - { - name: createThresholdAlertLabel, - onClick: () => { - setAlertType(AlertType.ErrorCount); - setPopoverOpen(false); - }, - }, - ], - }, - ]; - - return ( - <> - setPopoverOpen(false)} - panelPaddingSize="none" - anchorPosition="downRight" - > - - - { - if (!visible) { - setAlertType(null); - } - }} - /> - - ); -} diff --git a/x-pack/plugins/apm/public/components/app/ServiceDetails/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceDetails/index.tsx index 8825702cafd51..aa5dcd5a5ea18 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceDetails/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceDetails/index.tsx @@ -4,19 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - EuiButtonEmpty, - EuiFlexGroup, - EuiFlexItem, - EuiTitle, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; +import { EuiTitle } from '@elastic/eui'; import React from 'react'; import { RouteComponentProps } from 'react-router-dom'; -import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; -import { getAlertingCapabilities } from '../../alerting/get_alert_capabilities'; import { ApmHeader } from '../../shared/ApmHeader'; -import { AlertingPopoverAndFlyout } from './alerting_popover_flyout'; import { ServiceDetailTabs } from './ServiceDetailTabs'; interface Props extends RouteComponentProps<{ serviceName: string }> { @@ -24,51 +15,15 @@ interface Props extends RouteComponentProps<{ serviceName: string }> { } export function ServiceDetails({ match, tab }: Props) { - const { core, plugins } = useApmPluginContext(); const { serviceName } = match.params; - const { - isAlertingAvailable, - canReadAlerts, - canSaveAlerts, - canReadAnomalies, - } = getAlertingCapabilities(plugins, core.application.capabilities); - - const ADD_DATA_LABEL = i18n.translate('xpack.apm.addDataButtonLabel', { - defaultMessage: 'Add data', - }); - return (
- - - -

{serviceName}

-
-
- {isAlertingAvailable && ( - - - - )} - - - {ADD_DATA_LABEL} - - -
+ +

{serviceName}

+
-
); diff --git a/x-pack/plugins/apm/public/components/app/Settings/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/index.tsx index e770116ac2759..c9c577285ee80 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/index.tsx @@ -14,6 +14,8 @@ import { import { i18n } from '@kbn/i18n'; import React, { ReactNode } from 'react'; import { RouteComponentProps } from 'react-router-dom'; +import { HeaderMenuPortal } from '../../../../../observability/public'; +import { ActionMenu } from '../../../application/action_menu'; import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; import { getAPMHref } from '../../shared/Links/apm/APMLink'; import { HomeLink } from '../../shared/Links/apm/HomeLink'; @@ -23,7 +25,7 @@ interface SettingsProps extends RouteComponentProps<{}> { } export function Settings({ children, location }: SettingsProps) { - const { core } = useApmPluginContext(); + const { appMountParameters, core } = useApmPluginContext(); const { basePath } = core.http; const canAccessML = !!core.application.capabilities.ml?.canAccessML; const { search, pathname } = location; @@ -34,6 +36,11 @@ export function Settings({ children, location }: SettingsProps) { return ( <> + + + {i18n.translate('xpack.apm.settings.returnLinkLabel', { diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.test.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.test.tsx index d394c7db62554..247e91fb438ef 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.test.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.test.tsx @@ -149,7 +149,7 @@ describe('ServiceInventory', () => { "Looks like you don't have any APM services installed. Let's add some!" ); - expect(gettingStartedMessage).not.toBeEmpty(); + expect(gettingStartedMessage).not.toBeEmptyDOMElement(); }); it('should render empty message, when list is empty and historical data is found', async () => { @@ -165,7 +165,7 @@ describe('ServiceInventory', () => { await waitFor(() => expect(httpGet).toHaveBeenCalledTimes(1)); const noServicesText = await findByText('No services found'); - expect(noServicesText).not.toBeEmpty(); + expect(noServicesText).not.toBeEmptyDOMElement(); }); describe('when legacy data is found', () => { diff --git a/x-pack/plugins/apm/public/components/shared/ApmHeader/index.tsx b/x-pack/plugins/apm/public/components/shared/ApmHeader/index.tsx index 6674abe9b8ce5..96f170fa6a093 100644 --- a/x-pack/plugins/apm/public/components/shared/ApmHeader/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/ApmHeader/index.tsx @@ -6,13 +6,21 @@ import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import React, { ReactNode } from 'react'; +import { HeaderMenuPortal } from '../../../../../observability/public'; +import { ActionMenu } from '../../../application/action_menu'; +import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; import { DatePicker } from '../DatePicker'; import { EnvironmentFilter } from '../EnvironmentFilter'; import { KueryBar } from '../KueryBar'; export function ApmHeader({ children }: { children: ReactNode }) { + const { setHeaderActionMenu } = useApmPluginContext().appMountParameters; + return ( <> + + + {children} diff --git a/x-pack/plugins/apm/public/components/shared/KueryBar/get_bool_filter.ts b/x-pack/plugins/apm/public/components/shared/KueryBar/get_bool_filter.ts index 74d7ace20dae0..f0d12fd16bf7a 100644 --- a/x-pack/plugins/apm/public/components/shared/KueryBar/get_bool_filter.ts +++ b/x-pack/plugins/apm/public/components/shared/KueryBar/get_bool_filter.ts @@ -26,23 +26,7 @@ export function getBoolFilter({ serviceName?: string; urlParams: IUrlParams; }) { - const { start, end } = urlParams; - - if (!start || !end) { - throw new Error('Date range was not defined'); - } - - const boolFilter: ESFilter[] = [ - { - range: { - '@timestamp': { - gte: new Date(start).getTime(), - lte: new Date(end).getTime(), - format: 'epoch_millis', - }, - }, - }, - ]; + const boolFilter: ESFilter[] = []; if (serviceName) { boolFilter.push({ diff --git a/x-pack/plugins/apm/public/components/shared/KueryBar/index.tsx b/x-pack/plugins/apm/public/components/shared/KueryBar/index.tsx index 157e014bee424..dce8e49deec41 100644 --- a/x-pack/plugins/apm/public/components/shared/KueryBar/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/KueryBar/index.tsx @@ -111,6 +111,7 @@ export function KueryBar() { query: inputValue, selectionStart, selectionEnd: selectionStart, + useTimeRange: true, })) || [] ) .filter((suggestion) => !startsWith(suggestion.text, 'span.')) diff --git a/x-pack/plugins/apm/public/components/shared/Links/SetupInstructionsLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/SetupInstructionsLink.tsx index a5bcec1501ad3..0ff73d91d7c5b 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/SetupInstructionsLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/SetupInstructionsLink.tsx @@ -34,7 +34,7 @@ export function SetupInstructionsLink({ {SETUP_INSTRUCTIONS_LABEL} ) : ( - + {ADD_DATA_LABEL} )} diff --git a/x-pack/plugins/apm/public/context/ApmPluginContext/MockApmPluginContext.tsx b/x-pack/plugins/apm/public/context/ApmPluginContext/MockApmPluginContext.tsx index 3b915045f54b6..25e7f23a00125 100644 --- a/x-pack/plugins/apm/public/context/ApmPluginContext/MockApmPluginContext.tsx +++ b/x-pack/plugins/apm/public/context/ApmPluginContext/MockApmPluginContext.tsx @@ -38,6 +38,7 @@ const mockCore = { application: { capabilities: { apm: {}, + ml: {}, }, currentAppId$: new Observable(), navigateToUrl: (url: string) => {}, @@ -93,7 +94,13 @@ const mockPlugin = { }, }, }; + +const mockAppMountParameters = { + setHeaderActionMenu: () => {}, +}; + export const mockApmPluginContextValue = { + appMountParameters: mockAppMountParameters, config: mockConfig, core: mockCore, plugins: mockPlugin, diff --git a/x-pack/plugins/apm/public/context/ApmPluginContext/index.tsx b/x-pack/plugins/apm/public/context/ApmPluginContext/index.tsx index 39d961f6a8164..44952e64db59c 100644 --- a/x-pack/plugins/apm/public/context/ApmPluginContext/index.tsx +++ b/x-pack/plugins/apm/public/context/ApmPluginContext/index.tsx @@ -4,12 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CoreStart } from 'kibana/public'; +import { AppMountParameters, CoreStart } from 'kibana/public'; import { createContext } from 'react'; import { ConfigSchema } from '../../'; import { ApmPluginSetupDeps } from '../../plugin'; export interface ApmPluginContextValue { + appMountParameters: AppMountParameters; config: ConfigSchema; core: CoreStart; plugins: ApmPluginSetupDeps; diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts index 9020cb1b9953a..d23d729db3924 100644 --- a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts +++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts @@ -5,7 +5,6 @@ */ import { ValuesType } from 'utility-types'; -import { APMBaseDoc } from '../../../../../typings/es_schemas/raw/apm_base_doc'; import { APMError } from '../../../../../typings/es_schemas/ui/apm_error'; import { KibanaRequest, @@ -21,6 +20,7 @@ import { addFilterToExcludeLegacyData } from './add_filter_to_exclude_legacy_dat import { callClientWithDebug } from '../call_client_with_debug'; import { Transaction } from '../../../../../typings/es_schemas/ui/transaction'; import { Span } from '../../../../../typings/es_schemas/ui/span'; +import { Metric } from '../../../../../typings/es_schemas/ui/metric'; import { unpackProcessorEvents } from './unpack_processor_events'; export type APMEventESSearchRequest = Omit & { @@ -33,7 +33,7 @@ type TypeOfProcessorEvent = { [ProcessorEvent.error]: APMError; [ProcessorEvent.transaction]: Transaction; [ProcessorEvent.span]: Span; - [ProcessorEvent.metric]: APMBaseDoc; + [ProcessorEvent.metric]: Metric; [ProcessorEvent.onboarding]: unknown; [ProcessorEvent.sourcemap]: unknown; }[T]; diff --git a/x-pack/plugins/apm/typings/es_schemas/raw/apm_base_doc.ts b/x-pack/plugins/apm/typings/es_schemas/raw/apm_base_doc.ts index 19bd1129677dd..91335b39dab0f 100644 --- a/x-pack/plugins/apm/typings/es_schemas/raw/apm_base_doc.ts +++ b/x-pack/plugins/apm/typings/es_schemas/raw/apm_base_doc.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Observer } from './fields/observer'; + // all documents types extend APMBaseDoc and inherit all properties export interface APMBaseDoc { '@timestamp': string; @@ -11,10 +13,10 @@ export interface APMBaseDoc { name: string; version: string; }; - timestamp: { us: number }; parent?: { id: string }; // parent ID is not available on root transactions trace?: { id: string }; labels?: { [key: string]: string | number | boolean; }; + observer?: Observer; } diff --git a/x-pack/plugins/apm/typings/es_schemas/raw/error_raw.ts b/x-pack/plugins/apm/typings/es_schemas/raw/error_raw.ts index b8eb79aabdcc5..6b366090931cd 100644 --- a/x-pack/plugins/apm/typings/es_schemas/raw/error_raw.ts +++ b/x-pack/plugins/apm/typings/es_schemas/raw/error_raw.ts @@ -13,9 +13,9 @@ import { Page } from './fields/page'; import { Process } from './fields/process'; import { Service } from './fields/service'; import { Stackframe } from './fields/stackframe'; +import { TimestampUs } from './fields/timestamp_us'; import { Url } from './fields/url'; import { User } from './fields/user'; -import { Observer } from './fields/observer'; interface Processor { name: 'error'; @@ -41,6 +41,7 @@ interface Log { export interface ErrorRaw extends APMBaseDoc { processor: Processor; + timestamp: TimestampUs; transaction?: { id: string; sampled?: boolean; @@ -66,5 +67,4 @@ export interface ErrorRaw extends APMBaseDoc { service: Service; url?: Url; user?: User; - observer?: Observer; } diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/types.ts b/x-pack/plugins/apm/typings/es_schemas/raw/fields/timestamp_us.ts similarity index 82% rename from x-pack/plugins/enterprise_search/public/applications/shared/types.ts rename to x-pack/plugins/apm/typings/es_schemas/raw/fields/timestamp_us.ts index 3fd1dcad0066e..20b35295e9ba5 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/types.ts +++ b/x-pack/plugins/apm/typings/es_schemas/raw/fields/timestamp_us.ts @@ -4,4 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export { IFlashMessage } from './flash_messages'; +export interface TimestampUs { + us: number; +} diff --git a/x-pack/plugins/apm/typings/es_schemas/raw/metric_raw.ts b/x-pack/plugins/apm/typings/es_schemas/raw/metric_raw.ts new file mode 100644 index 0000000000000..b4a1954783db0 --- /dev/null +++ b/x-pack/plugins/apm/typings/es_schemas/raw/metric_raw.ts @@ -0,0 +1,99 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { APMBaseDoc } from './apm_base_doc'; +import { Container } from './fields/container'; +import { Kubernetes } from './fields/kubernetes'; + +type BaseMetric = APMBaseDoc & { + processor: { + name: 'metric'; + event: 'metric'; + }; +}; + +type BaseBreakdownMetric = BaseMetric & { + transaction: { + name: string; + type: string; + }; + span: { + self_time: { + count: number; + sum: { + us: number; + }; + }; + }; +}; + +type TransactionBreakdownMetric = BaseBreakdownMetric & { + transaction: { + duration: { + count: number; + sum: { + us: number; + }; + }; + breakdown: { + count: number; + }; + }; +}; + +type SpanBreakdownMetric = BaseBreakdownMetric & { + span: { + type: string; + subtype?: string; + }; +}; + +type SystemMetric = BaseMetric & { + system: unknown; + service: { + node?: { + name: string; + }; + }; +}; + +type CGroupMetric = SystemMetric; +type JVMMetric = SystemMetric & { + jvm: unknown; +}; + +type TransactionDurationMetric = BaseMetric & { + transaction: { + name: string; + type: string; + result?: string; + duration: { + histogram: { + values: number[]; + counts: number[]; + }; + }; + }; + service: { + name: string; + node?: { + name: string; + }; + environment?: string; + version?: string; + }; + container?: Container; + kubernetes?: Kubernetes; +}; + +export type MetricRaw = + | BaseMetric + | TransactionBreakdownMetric + | SpanBreakdownMetric + | TransactionDurationMetric + | SystemMetric + | CGroupMetric + | JVMMetric; diff --git a/x-pack/plugins/apm/typings/es_schemas/raw/span_raw.ts b/x-pack/plugins/apm/typings/es_schemas/raw/span_raw.ts index 5c2e391059783..dcb3dc02f6519 100644 --- a/x-pack/plugins/apm/typings/es_schemas/raw/span_raw.ts +++ b/x-pack/plugins/apm/typings/es_schemas/raw/span_raw.ts @@ -6,7 +6,7 @@ import { APMBaseDoc } from './apm_base_doc'; import { Stackframe } from './fields/stackframe'; -import { Observer } from './fields/observer'; +import { TimestampUs } from './fields/timestamp_us'; interface Processor { name: 'transaction'; @@ -48,9 +48,9 @@ export interface SpanRaw extends APMBaseDoc { headers?: Record; }; }; + timestamp: TimestampUs; transaction?: { id: string; }; - observer?: Observer; child?: { id: string[] }; } diff --git a/x-pack/plugins/apm/typings/es_schemas/raw/transaction_raw.ts b/x-pack/plugins/apm/typings/es_schemas/raw/transaction_raw.ts index cdfe4183c96f5..68db3bca94641 100644 --- a/x-pack/plugins/apm/typings/es_schemas/raw/transaction_raw.ts +++ b/x-pack/plugins/apm/typings/es_schemas/raw/transaction_raw.ts @@ -12,10 +12,10 @@ import { Kubernetes } from './fields/kubernetes'; import { Page } from './fields/page'; import { Process } from './fields/process'; import { Service } from './fields/service'; +import { TimestampUs } from './fields/timestamp_us'; import { Url } from './fields/url'; import { User } from './fields/user'; import { UserAgent } from './fields/user_agent'; -import { Observer } from './fields/observer'; interface Processor { name: 'transaction'; @@ -24,6 +24,7 @@ interface Processor { export interface TransactionRaw extends APMBaseDoc { processor: Processor; + timestamp: TimestampUs; trace: { id: string }; // trace is required transaction: { duration: { us: number }; @@ -63,5 +64,4 @@ export interface TransactionRaw extends APMBaseDoc { url?: Url; user?: User; user_agent?: UserAgent; - observer?: Observer; } diff --git a/x-pack/plugins/maps/public/routing/store_operations.ts b/x-pack/plugins/apm/typings/es_schemas/ui/metric.ts similarity index 66% rename from x-pack/plugins/maps/public/routing/store_operations.ts rename to x-pack/plugins/apm/typings/es_schemas/ui/metric.ts index 53ebbb3328ff9..eefb0923f16da 100644 --- a/x-pack/plugins/maps/public/routing/store_operations.ts +++ b/x-pack/plugins/apm/typings/es_schemas/ui/metric.ts @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { createMapStore } from '../reducers/store'; +import { MetricRaw } from '../raw/metric_raw'; -const store = createMapStore(); - -export const getStore = () => store; +export type Metric = MetricRaw; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_map.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_map.ts index a64ff7da2aa19..7bb6e43b38d59 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_map.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_map.ts @@ -83,6 +83,7 @@ export function savedMap(): ExpressionFunctionDefinition< return { type: EmbeddableExpressionType, input: { + attributes: { title: '' }, id: args.id, filters: getQueryFilters(filters), timeRange: args.timerange || defaultTimeRange, diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/map.test.ts b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/map.test.ts index d2c803a1ff208..635e0ec2d0dcb 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/map.test.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/map.test.ts @@ -9,6 +9,7 @@ import { MapEmbeddableInput } from '../../../../../../plugins/maps/public/embedd import { fromExpression, Ast } from '@kbn/interpreter/common'; const baseSavedMapInput = { + attributes: { title: '' }, id: 'embeddableId', filters: [], isLayerTOCOpen: false, diff --git a/x-pack/plugins/cloud/kibana.json b/x-pack/plugins/cloud/kibana.json index 27b35bcbdd88b..9bca2f30bd23c 100644 --- a/x-pack/plugins/cloud/kibana.json +++ b/x-pack/plugins/cloud/kibana.json @@ -3,7 +3,7 @@ "version": "8.0.0", "kibanaVersion": "kibana", "configPath": ["xpack", "cloud"], - "optionalPlugins": ["usageCollection", "home"], + "optionalPlugins": ["usageCollection", "home", "security"], "server": true, "ui": true } diff --git a/x-pack/plugins/cloud/public/index.ts b/x-pack/plugins/cloud/public/index.ts index 39ef5f452c18b..680b2f1ad2bd6 100644 --- a/x-pack/plugins/cloud/public/index.ts +++ b/x-pack/plugins/cloud/public/index.ts @@ -7,7 +7,7 @@ import { PluginInitializerContext } from '../../../../src/core/public'; import { CloudPlugin } from './plugin'; -export { CloudSetup } from './plugin'; +export { CloudSetup, CloudConfigType } from './plugin'; export function plugin(initializerContext: PluginInitializerContext) { return new CloudPlugin(initializerContext); } diff --git a/x-pack/plugins/cloud/public/mocks.ts b/x-pack/plugins/cloud/public/mocks.ts new file mode 100644 index 0000000000000..bafebbca4ecdd --- /dev/null +++ b/x-pack/plugins/cloud/public/mocks.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +function createSetupMock() { + return { + cloudId: 'mock-cloud-id', + isCloudEnabled: true, + resetPasswordUrl: 'reset-password-url', + accountUrl: 'account-url', + }; +} + +export const cloudMock = { + createSetup: createSetupMock, +}; diff --git a/x-pack/plugins/cloud/public/plugin.ts b/x-pack/plugins/cloud/public/plugin.ts index 45005f3f5e422..bc410b89c30e7 100644 --- a/x-pack/plugins/cloud/public/plugin.ts +++ b/x-pack/plugins/cloud/public/plugin.ts @@ -6,40 +6,51 @@ import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'src/core/public'; import { i18n } from '@kbn/i18n'; +import { SecurityPluginStart } from '../../security/public'; import { getIsCloudEnabled } from '../common/is_cloud_enabled'; import { ELASTIC_SUPPORT_LINK } from '../common/constants'; import { HomePublicPluginSetup } from '../../../../src/plugins/home/public'; +import { createUserMenuLinks } from './user_menu_links'; -interface CloudConfigType { +export interface CloudConfigType { id?: string; resetPasswordUrl?: string; deploymentUrl?: string; + accountUrl?: string; } interface CloudSetupDependencies { home?: HomePublicPluginSetup; } +interface CloudStartDependencies { + security?: SecurityPluginStart; +} + export interface CloudSetup { cloudId?: string; cloudDeploymentUrl?: string; isCloudEnabled: boolean; + resetPasswordUrl?: string; + accountUrl?: string; } export class CloudPlugin implements Plugin { private config!: CloudConfigType; + private isCloudEnabled: boolean; constructor(private readonly initializerContext: PluginInitializerContext) { this.config = this.initializerContext.config.get(); + this.isCloudEnabled = false; } public async setup(core: CoreSetup, { home }: CloudSetupDependencies) { const { id, resetPasswordUrl, deploymentUrl } = this.config; - const isCloudEnabled = getIsCloudEnabled(id); + this.isCloudEnabled = getIsCloudEnabled(id); if (home) { - home.environment.update({ cloud: isCloudEnabled }); - if (isCloudEnabled) { + home.environment.update({ cloud: this.isCloudEnabled }); + if (this.isCloudEnabled) { home.tutorials.setVariable('cloud', { id, resetPasswordUrl }); } } @@ -47,11 +58,11 @@ export class CloudPlugin implements Plugin { return { cloudId: id, cloudDeploymentUrl: deploymentUrl, - isCloudEnabled, + isCloudEnabled: this.isCloudEnabled, }; } - public start(coreStart: CoreStart) { + public start(coreStart: CoreStart, { security }: CloudStartDependencies) { const { deploymentUrl } = this.config; coreStart.chrome.setHelpSupportUrl(ELASTIC_SUPPORT_LINK); if (deploymentUrl) { @@ -63,5 +74,10 @@ export class CloudPlugin implements Plugin { href: deploymentUrl, }); } + + if (security && this.isCloudEnabled) { + const userMenuLinks = createUserMenuLinks(this.config); + security.navControlService.addUserMenuLinks(userMenuLinks); + } } } diff --git a/x-pack/plugins/cloud/public/user_menu_links.ts b/x-pack/plugins/cloud/public/user_menu_links.ts new file mode 100644 index 0000000000000..15e2f14e885ba --- /dev/null +++ b/x-pack/plugins/cloud/public/user_menu_links.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { UserMenuLink } from '../../security/public'; +import { CloudConfigType } from '.'; + +export const createUserMenuLinks = (config: CloudConfigType): UserMenuLink[] => { + const { resetPasswordUrl, accountUrl } = config; + const userMenuLinks = [] as UserMenuLink[]; + + if (resetPasswordUrl) { + userMenuLinks.push({ + label: i18n.translate('xpack.cloud.userMenuLinks.profileLinkText', { + defaultMessage: 'Cloud profile', + }), + iconType: 'logoCloud', + href: resetPasswordUrl, + order: 100, + }); + } + + if (accountUrl) { + userMenuLinks.push({ + label: i18n.translate('xpack.cloud.userMenuLinks.accountLinkText', { + defaultMessage: 'Account & Billing', + }), + iconType: 'gear', + href: accountUrl, + order: 200, + }); + } + + return userMenuLinks; +}; diff --git a/x-pack/plugins/cloud/server/config.ts b/x-pack/plugins/cloud/server/config.ts index ff8a2c5acdf9a..eaa4ab7a482dd 100644 --- a/x-pack/plugins/cloud/server/config.ts +++ b/x-pack/plugins/cloud/server/config.ts @@ -23,6 +23,7 @@ const configSchema = schema.object({ apm: schema.maybe(apmConfigSchema), resetPasswordUrl: schema.maybe(schema.string()), deploymentUrl: schema.maybe(schema.string()), + accountUrl: schema.maybe(schema.string()), }); export type CloudConfigType = TypeOf; @@ -32,6 +33,7 @@ export const config: PluginConfigDescriptor = { id: true, resetPasswordUrl: true, deploymentUrl: true, + accountUrl: true, }, schema: configSchema, }; diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/value.ts b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/value.ts index 441e5a6f775dd..cab3a657b7b6b 100644 --- a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/value.ts +++ b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/value.ts @@ -27,7 +27,7 @@ const wrapAsSuggestions = (start: number, end: number, query: string, values: st export const setupGetValueSuggestions: KqlQuerySuggestionProvider = () => { return async ( - { indexPatterns, boolFilter, signal }, + { indexPatterns, boolFilter, useTimeRange, signal }, { start, end, prefix, suffix, fieldName, nestedPath } ): Promise => { const fullFieldName = nestedPath ? `${nestedPath}.${fieldName}` : fieldName; @@ -49,6 +49,7 @@ export const setupGetValueSuggestions: KqlQuerySuggestionProvider = () => { field, query, boolFilter, + useTimeRange, signal, }).then((valueSuggestions) => { const quotedValues = valueSuggestions.map((value) => diff --git a/x-pack/plugins/enterprise_search/common/types/app_search.ts b/x-pack/plugins/enterprise_search/common/types/app_search.ts index 203b77834bc15..9f754412ec730 100644 --- a/x-pack/plugins/enterprise_search/common/types/app_search.ts +++ b/x-pack/plugins/enterprise_search/common/types/app_search.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export interface IAccount { +export interface Account { accountId: string; onboardingComplete: boolean; role: { @@ -21,7 +21,7 @@ export interface IAccount { }; } -export interface IConfiguredLimits { +export interface ConfiguredLimits { engine: { maxDocumentByteSize: number; maxEnginesPerMetaEngine: number; diff --git a/x-pack/plugins/enterprise_search/common/types/index.ts b/x-pack/plugins/enterprise_search/common/types/index.ts index 1006d39138759..39d9aa8607bc2 100644 --- a/x-pack/plugins/enterprise_search/common/types/index.ts +++ b/x-pack/plugins/enterprise_search/common/types/index.ts @@ -5,39 +5,39 @@ */ import { - IAccount as IAppSearchAccount, - IConfiguredLimits as IAppSearchConfiguredLimits, + Account as AppSearchAccount, + ConfiguredLimits as AppSearchConfiguredLimits, } from './app_search'; import { - IWorkplaceSearchInitialData, - IConfiguredLimits as IWorkplaceSearchConfiguredLimits, + WorkplaceSearchInitialData, + ConfiguredLimits as WorkplaceSearchConfiguredLimits, } from './workplace_search'; -export interface IInitialAppData { +export interface InitialAppData { readOnlyMode?: boolean; ilmEnabled?: boolean; isFederatedAuth?: boolean; - configuredLimits?: IConfiguredLimits; + configuredLimits?: ConfiguredLimits; access?: { hasAppSearchAccess: boolean; hasWorkplaceSearchAccess: boolean; }; - appSearch?: IAppSearchAccount; - workplaceSearch?: IWorkplaceSearchInitialData; + appSearch?: AppSearchAccount; + workplaceSearch?: WorkplaceSearchInitialData; } -export interface IConfiguredLimits { - appSearch: IAppSearchConfiguredLimits; - workplaceSearch: IWorkplaceSearchConfiguredLimits; +export interface ConfiguredLimits { + appSearch: AppSearchConfiguredLimits; + workplaceSearch: WorkplaceSearchConfiguredLimits; } -export interface IMetaPage { +export interface MetaPage { current: number; size: number; total_pages: number; total_results: number; } -export interface IMeta { - page: IMetaPage; +export interface Meta { + page: MetaPage; } diff --git a/x-pack/plugins/enterprise_search/common/types/workplace_search.ts b/x-pack/plugins/enterprise_search/common/types/workplace_search.ts index 886597fcd9891..883cc6939b4bc 100644 --- a/x-pack/plugins/enterprise_search/common/types/workplace_search.ts +++ b/x-pack/plugins/enterprise_search/common/types/workplace_search.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export interface IAccount { +export interface Account { id: string; groups: string[]; isAdmin: boolean; @@ -14,65 +14,19 @@ export interface IAccount { viewedOnboardingPage: boolean; } -export interface IOrganization { +export interface Organization { name: string; defaultOrgName: string; } -export interface IWorkplaceSearchInitialData { - organization: IOrganization; - account: IAccount; +export interface WorkplaceSearchInitialData { + organization: Organization; + account: Account; } -export interface IConfiguredLimits { +export interface ConfiguredLimits { customApiSource: { maxDocumentByteSize: number; totalFields: number; }; } - -export interface IGroup { - id: string; - name: string; - createdAt: string; - updatedAt: string; - contentSources: IContentSource[]; - users: IUser[]; - usersCount: number; - color?: string; -} - -export interface IGroupDetails extends IGroup { - contentSources: IContentSourceDetails[]; - canEditGroup: boolean; - canDeleteGroup: boolean; -} - -export interface IUser { - id: string; - name: string | null; - initials: string; - pictureUrl: string | null; - color: string; - email: string; - role?: string; - groupIds: string[]; -} - -export interface IContentSource { - id: string; - serviceType: string; - name: string; -} - -export interface IContentSourceDetails extends IContentSource { - status: string; - statusMessage: string; - documentCount: string; - isFederatedSource: boolean; - searchable: boolean; - supportedByLicense: boolean; - errorReason: number; - allowsReauth: boolean; - boost: number; -} diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/mount_async.mock.tsx b/x-pack/plugins/enterprise_search/public/applications/__mocks__/mount_async.mock.tsx index a33e116c7ca72..a3d817e1da904 100644 --- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/mount_async.mock.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/mount_async.mock.tsx @@ -20,13 +20,13 @@ import { mountWithIntl } from './'; * const wrapper = mountAsync(); */ -interface IOptions { +interface Options { i18n?: boolean; } export const mountAsync = async ( children: React.ReactElement, - options: IOptions + options: Options ): Promise => { let wrapper: ReactWrapper | undefined; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.ts index 932e84af45c2b..2b475073c6ea5 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.ts @@ -6,24 +6,24 @@ import { kea, MakeLogicType } from 'kea'; -import { IInitialAppData } from '../../../common/types'; -import { IConfiguredLimits, IAccount, IRole } from './types'; +import { InitialAppData } from '../../../common/types'; +import { ConfiguredLimits, Account, Role } from './types'; import { getRoleAbilities } from './utils/role'; -export interface IAppValues { +interface AppValues { hasInitialized: boolean; ilmEnabled: boolean; - configuredLimits: Partial; - account: Partial; - myRole: Partial; + configuredLimits: Partial; + account: Partial; + myRole: Partial; } -export interface IAppActions { - initializeAppData(props: IInitialAppData): Required; +interface AppActions { + initializeAppData(props: InitialAppData): Required; setOnboardingComplete(): boolean; } -export const AppLogic = kea>({ +export const AppLogic = kea>({ path: ['enterprise_search', 'app_search', 'app_logic'], actions: { initializeAppData: (props) => props, diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_read_write_access.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_read_write_access.tsx index a02b00b6ad377..d96c57b3c8bc3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_read_write_access.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_read_write_access.tsx @@ -10,7 +10,7 @@ import { EuiCheckbox, EuiText, EuiTitle, EuiSpacer, EuiPanel } from '@elastic/eu import { i18n } from '@kbn/i18n'; import { CredentialsLogic } from '../../credentials_logic'; -import { ITokenReadWrite } from '../../types'; +import { TokenReadWrite } from '../../types'; export const FormKeyReadWriteAccess: React.FC = () => { const { setTokenReadWrite } = useActions(CredentialsLogic); @@ -37,7 +37,7 @@ export const FormKeyReadWriteAccess: React.FC = () => { name="read" id="read" checked={activeApiToken.read} - onChange={(e) => setTokenReadWrite(e.target as ITokenReadWrite)} + onChange={(e) => setTokenReadWrite(e.target as TokenReadWrite)} label={i18n.translate( 'xpack.enterpriseSearch.appSearch.credentials.formReadWrite.readLabel', { defaultMessage: 'Read Access' } @@ -47,7 +47,7 @@ export const FormKeyReadWriteAccess: React.FC = () => { name="write" id="write" checked={activeApiToken.write} - onChange={(e) => setTokenReadWrite(e.target as ITokenReadWrite)} + onChange={(e) => setTokenReadWrite(e.target as TokenReadWrite)} label={i18n.translate( 'xpack.enterpriseSearch.appSearch.credentials.formReadWrite.writeLabel', { defaultMessage: 'Write Access' } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/header.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/header.test.tsx index a8d9505136faa..803789ebee568 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/header.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/header.test.tsx @@ -11,12 +11,12 @@ import { shallow } from 'enzyme'; import { EuiFlyoutHeader } from '@elastic/eui'; import { ApiTokenTypes } from '../constants'; -import { IApiToken } from '../types'; +import { ApiToken } from '../types'; import { CredentialsFlyoutHeader } from './header'; describe('CredentialsFlyoutHeader', () => { - const apiToken: IApiToken = { + const apiToken: ApiToken = { name: '', type: ApiTokenTypes.Private, read: true, diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.test.tsx index 97d29b9333f4b..4f5ded0a3ccc1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.test.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import { EuiBasicTable, EuiCopy, EuiEmptyPrompt } from '@elastic/eui'; -import { IApiToken } from '../types'; +import { ApiToken } from '../types'; import { ApiTokenTypes } from '../constants'; import { HiddenText } from '../../../../shared/hidden_text'; @@ -18,7 +18,7 @@ import { Key } from './key'; import { CredentialsList } from './credentials_list'; describe('Credentials', () => { - const apiToken: IApiToken = { + const apiToken: ApiToken = { name: '', type: ApiTokenTypes.Private, read: true, @@ -77,7 +77,7 @@ describe('Credentials', () => { }); const wrapper = shallow(); const { items } = wrapper.find(EuiBasicTable).props(); - expect(items.map((i: IApiToken) => i.id)).toEqual([undefined, 1, 2]); + expect(items.map((i: ApiToken) => i.id)).toEqual([undefined, 1, 2]); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.tsx index f9752dca582e1..9240bade4975e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.tsx @@ -14,7 +14,7 @@ import { i18n } from '@kbn/i18n'; import { CredentialsLogic } from '../credentials_logic'; import { Key } from './key'; import { HiddenText } from '../../../../shared/hidden_text'; -import { IApiToken } from '../types'; +import { ApiToken } from '../types'; import { TOKEN_TYPE_DISPLAY_NAMES } from '../constants'; import { apiTokenSort } from '../utils/api_token_sort'; import { getModeDisplayText, getEnginesDisplayText } from '../utils'; @@ -26,21 +26,21 @@ export const CredentialsList: React.FC = () => { const items = useMemo(() => apiTokens.slice().sort(apiTokenSort), [apiTokens]); - const columns: Array> = [ + const columns: Array> = [ { name: 'Name', width: '12%', - render: (token: IApiToken) => token.name, + render: (token: ApiToken) => token.name, }, { name: 'Type', width: '15%', - render: (token: IApiToken) => TOKEN_TYPE_DISPLAY_NAMES[token.type], + render: (token: ApiToken) => TOKEN_TYPE_DISPLAY_NAMES[token.type], }, { name: 'Key', width: '36%', - render: (token: IApiToken) => { + render: (token: ApiToken) => { const { key } = token; if (!key) return null; return ( @@ -64,12 +64,12 @@ export const CredentialsList: React.FC = () => { { name: 'Modes', width: '10%', - render: (token: IApiToken) => getModeDisplayText(token), + render: (token: ApiToken) => getModeDisplayText(token), }, { name: 'Engines', width: '18%', - render: (token: IApiToken) => getEnginesDisplayText(token), + render: (token: ApiToken) => getEnginesDisplayText(token), }, { actions: [ @@ -83,7 +83,7 @@ export const CredentialsList: React.FC = () => { type: 'icon', icon: 'pencil', color: 'primary', - onClick: (token: IApiToken) => showCredentialsForm(token), + onClick: (token: ApiToken) => showCredentialsForm(token), }, { name: i18n.translate('xpack.enterpriseSearch.actions.delete', { @@ -95,7 +95,7 @@ export const CredentialsList: React.FC = () => { type: 'icon', icon: 'trash', color: 'danger', - onClick: (token: IApiToken) => deleteApiKey(token.name), + onClick: (token: ApiToken) => deleteApiKey(token.name), }, ], }, @@ -108,7 +108,7 @@ export const CredentialsList: React.FC = () => { hidePerPageOptions: true, }; - const onTableChange = ({ page }: CriteriaWithPagination) => { + const onTableChange = ({ page }: CriteriaWithPagination) => { const { index: current } = page; fetchCredentials(current + 1); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/key.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/key.tsx index 5c0c24ec733a4..fa2d124cbccdf 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/key.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/key.tsx @@ -8,14 +8,14 @@ import React from 'react'; import { EuiButtonIcon } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -interface IProps { +interface Props { copy: () => void; toggleIsHidden: () => void; isHidden: boolean; text: React.ReactNode; } -export const Key: React.FC = ({ copy, toggleIsHidden, isHidden, text }) => { +export const Key: React.FC = ({ copy, toggleIsHidden, isHidden, text }) => { const hideIcon = isHidden ? 'eye' : 'eyeClosed'; const hideIconLabel = isHidden ? i18n.translate('xpack.enterpriseSearch.appSearch.credentials.showApiKey', { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_logic.test.ts index de79862b540ba..6523b4fb110b0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_logic.test.ts @@ -88,8 +88,8 @@ describe('CredentialsLogic', () => { const credentialsDetails = { engines: [ - { name: 'engine1', type: 'indexed', language: 'english', result_fields: [] }, - { name: 'engine1', type: 'indexed', language: 'english', result_fields: [] }, + { name: 'engine1', type: 'indexed', language: 'english', result_fields: {} }, + { name: 'engine1', type: 'indexed', language: 'english', result_fields: {} }, ], }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_logic.ts index 7b8b864b3a317..166cbae9a4512 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_logic.ts @@ -17,11 +17,11 @@ import { } from '../../../shared/flash_messages'; import { AppLogic } from '../../app_logic'; -import { IMeta } from '../../../../../common/types'; -import { IEngine } from '../../types'; -import { IApiToken, ICredentialsDetails, ITokenReadWrite } from './types'; +import { Meta } from '../../../../../common/types'; +import { Engine } from '../../types'; +import { ApiToken, CredentialsDetails, TokenReadWrite } from './types'; -export const defaultApiToken: IApiToken = { +export const defaultApiToken: ApiToken = { name: '', type: ApiTokenTypes.Private, read: true, @@ -29,21 +29,21 @@ export const defaultApiToken: IApiToken = { access_all_engines: true, }; -interface ICredentialsLogicActions { +interface CredentialsLogicActions { addEngineName(engineName: string): string; onApiKeyDelete(tokenName: string): string; - onApiTokenCreateSuccess(apiToken: IApiToken): IApiToken; + onApiTokenCreateSuccess(apiToken: ApiToken): ApiToken; onApiTokenError(formErrors: string[]): string[]; - onApiTokenUpdateSuccess(apiToken: IApiToken): IApiToken; + onApiTokenUpdateSuccess(apiToken: ApiToken): ApiToken; removeEngineName(engineName: string): string; setAccessAllEngines(accessAll: boolean): boolean; - setCredentialsData(meta: IMeta, apiTokens: IApiToken[]): { meta: IMeta; apiTokens: IApiToken[] }; - setCredentialsDetails(details: ICredentialsDetails): ICredentialsDetails; + setCredentialsData(meta: Meta, apiTokens: ApiToken[]): { meta: Meta; apiTokens: ApiToken[] }; + setCredentialsDetails(details: CredentialsDetails): CredentialsDetails; setNameInputBlurred(isBlurred: boolean): boolean; - setTokenReadWrite(tokenReadWrite: ITokenReadWrite): ITokenReadWrite; + setTokenReadWrite(tokenReadWrite: TokenReadWrite): TokenReadWrite; setTokenName(name: string): string; setTokenType(tokenType: string): string; - showCredentialsForm(apiToken?: IApiToken): IApiToken; + showCredentialsForm(apiToken?: ApiToken): ApiToken; hideCredentialsForm(): { value: boolean }; resetCredentials(): { value: boolean }; initializeCredentialsData(): { value: boolean }; @@ -54,25 +54,25 @@ interface ICredentialsLogicActions { onEngineSelect(engineName: string): string; } -interface ICredentialsLogicValues { - activeApiToken: IApiToken; +interface CredentialsLogicValues { + activeApiToken: ApiToken; activeApiTokenExists: boolean; activeApiTokenRawName: string; - apiTokens: IApiToken[]; + apiTokens: ApiToken[]; dataLoading: boolean; - engines: IEngine[]; + engines: Engine[]; formErrors: string[]; isCredentialsDataComplete: boolean; isCredentialsDetailsComplete: boolean; fullEngineAccessChecked: boolean; - meta: Partial; + meta: Partial; nameInputBlurred: boolean; shouldShowCredentialsForm: boolean; } -export const CredentialsLogic = kea< - MakeLogicType ->({ +type CredentialsLogicType = MakeLogicType; // If we leave this inline, Prettier does some horrifying indenting nonsense :/ + +export const CredentialsLogic = kea({ path: ['enterprise_search', 'app_search', 'credentials_logic'], actions: () => ({ addEngineName: (engineName) => engineName, @@ -267,7 +267,7 @@ export const CredentialsLogic = kea< onApiTokenChange: async () => { const { id, name, engines, type, read, write } = values.activeApiToken; - const data: IApiToken = { + const data: ApiToken = { name, type, }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/types.ts index 9ca4d086d55c8..23f78b44c0db5 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/types.ts @@ -4,14 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IEngine } from '../../types'; +import { Engine } from '../../types'; import { ApiTokenTypes } from './constants'; -export interface ICredentialsDetails { - engines: IEngine[]; +export interface CredentialsDetails { + engines: Engine[]; } -export interface IApiToken { +export interface ApiToken { access_all_engines?: boolean; key?: string; engines?: string[]; @@ -22,7 +22,7 @@ export interface IApiToken { write?: boolean; } -export interface ITokenReadWrite { +export interface TokenReadWrite { name: 'read' | 'write'; checked: boolean; } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/api_token_sort.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/api_token_sort.test.ts index 84818322b3570..2287125bb5eb8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/api_token_sort.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/api_token_sort.test.ts @@ -7,10 +7,10 @@ import { apiTokenSort } from '.'; import { ApiTokenTypes } from '../constants'; -import { IApiToken } from '../types'; +import { ApiToken } from '../types'; describe('apiTokenSort', () => { - const apiToken: IApiToken = { + const apiToken: ApiToken = { name: '', type: ApiTokenTypes.Private, read: true, diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/api_token_sort.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/api_token_sort.ts index 80a46f30e0930..b9fb501ccabe2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/api_token_sort.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/api_token_sort.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IApiToken } from '../types'; +import { ApiToken } from '../types'; -export const apiTokenSort = (apiTokenA: IApiToken, apiTokenB: IApiToken): number => { +export const apiTokenSort = (apiTokenA: ApiToken, apiTokenB: ApiToken): number => { if (!apiTokenA.id) { return -1; } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/get_engines_display_text.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/get_engines_display_text.test.tsx index b06ed63f8616c..bee19a44facd3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/get_engines_display_text.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/get_engines_display_text.test.tsx @@ -8,10 +8,10 @@ import React from 'react'; import { shallow } from 'enzyme'; import { getEnginesDisplayText } from './get_engines_display_text'; -import { IApiToken } from '../types'; +import { ApiToken } from '../types'; import { ApiTokenTypes } from '../constants'; -const apiToken: IApiToken = { +const apiToken: ApiToken = { name: '', type: ApiTokenTypes.Private, read: true, diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/get_engines_display_text.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/get_engines_display_text.tsx index 1b216c46307db..fb23551302f3b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/get_engines_display_text.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/get_engines_display_text.tsx @@ -6,9 +6,9 @@ import React from 'react'; import { ApiTokenTypes, ALL } from '../constants'; -import { IApiToken } from '../types'; +import { ApiToken } from '../types'; -export const getEnginesDisplayText = (apiToken: IApiToken): JSX.Element | string => { +export const getEnginesDisplayText = (apiToken: ApiToken): JSX.Element | string => { const { type, access_all_engines: accessAll, engines = [] } = apiToken; if (type === ApiTokenTypes.Admin) { return '--'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/get_mode_display_text.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/get_mode_display_text.test.ts index b2083f22c8e1c..46b4c9b2c786c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/get_mode_display_text.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/get_mode_display_text.test.ts @@ -5,11 +5,11 @@ */ import { ApiTokenTypes } from '../constants'; -import { IApiToken } from '../types'; +import { ApiToken } from '../types'; import { getModeDisplayText } from './get_mode_display_text'; -const apiToken: IApiToken = { +const apiToken: ApiToken = { name: '', type: ApiTokenTypes.Private, read: true, diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/get_mode_display_text.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/get_mode_display_text.ts index 9c8758d83882d..b150aa2cfc3be 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/get_mode_display_text.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/get_mode_display_text.ts @@ -5,9 +5,9 @@ */ import { ApiTokenTypes, READ_ONLY, READ_WRITE, SEARCH_DISPLAY, WRITE_ONLY } from '../constants'; -import { IApiToken } from '../types'; +import { ApiToken } from '../types'; -export const getModeDisplayText = (apiToken: IApiToken): string => { +export const getModeDisplayText = (apiToken: ApiToken): string => { const { read = false, write = false, type } = apiToken; switch (type) { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts new file mode 100644 index 0000000000000..13db440df739e --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts @@ -0,0 +1,266 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { resetContext } from 'kea'; + +import { mockHttpValues } from '../../../__mocks__'; +jest.mock('../../../shared/http', () => ({ + HttpLogic: { values: mockHttpValues }, +})); +const { http } = mockHttpValues; + +import { EngineLogic } from './'; + +describe('EngineLogic', () => { + const mockEngineData = { + name: 'some-engine', + type: 'default', + created_at: 'some date timestamp', + language: null, + document_count: 1, + field_count: 1, + result_fields: { + id: { raw: {} }, + }, + unconfirmedFields: [], + unsearchedUnconfirmedFields: false, + sample: false, + isMeta: false, + invalidBoosts: false, + schema: {}, + apiTokens: [], + apiKey: 'some-key', + }; + + const DEFAULT_VALUES = { + dataLoading: true, + engine: {}, + engineName: '', + isMetaEngine: false, + isSampleEngine: false, + hasSchemaConflicts: false, + hasUnconfirmedSchemaFields: false, + engineNotFound: false, + }; + + const mount = (values?: object) => { + if (!values) { + resetContext({}); + } else { + resetContext({ + defaults: { + enterprise_search: { + app_search: { + engine_logic: { + ...values, + }, + }, + }, + }, + }); + } + EngineLogic.mount(); + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('has expected default values', () => { + mount(); + expect(EngineLogic.values).toEqual(DEFAULT_VALUES); + }); + + describe('actions', () => { + describe('setEngineData', () => { + describe('engine & dataLoading', () => { + it('should set engine to the provided value and dataLoading to false', () => { + mount(); + EngineLogic.actions.setEngineData(mockEngineData); + + expect(EngineLogic.values).toEqual({ + ...DEFAULT_VALUES, + engine: mockEngineData, + dataLoading: false, + }); + }); + }); + }); + + describe('setEngineName', () => { + describe('engineName', () => { + it('should be set to the provided value', () => { + mount(); + EngineLogic.actions.setEngineName('some-new-engine'); + + expect(EngineLogic.values).toEqual({ + ...DEFAULT_VALUES, + engineName: 'some-new-engine', + }); + }); + }); + }); + + describe('setIndexingStatus', () => { + describe('engine', () => { + it('should set the nested obj property to the provided value', () => { + mount({ engine: mockEngineData }); + const mockReindexJob = { + percentageComplete: 50, + numDocumentsWithErrors: 2, + activeReindexJobId: 123, + }; + EngineLogic.actions.setIndexingStatus(mockReindexJob); + + expect(EngineLogic.values).toEqual({ + ...DEFAULT_VALUES, + engine: { + ...mockEngineData, + activeReindexJob: mockReindexJob, + }, + }); + }); + }); + }); + + describe('setEngineNotFound', () => { + describe('engineNotFound', () => { + it('should be set to the provided value', () => { + mount(); + EngineLogic.actions.setEngineNotFound(true); + + expect(EngineLogic.values).toEqual({ + ...DEFAULT_VALUES, + engineNotFound: true, + }); + }); + }); + }); + + describe('clearEngine', () => { + describe('engine', () => { + it('should be reset to an empty obj', () => { + mount({ engine: mockEngineData }); + EngineLogic.actions.clearEngine(); + + expect(EngineLogic.values).toEqual({ + ...DEFAULT_VALUES, + engine: {}, + }); + }); + }); + + describe('dataLoading', () => { + it('should be set to true', () => { + mount({ dataLoading: false }); + EngineLogic.actions.clearEngine(); + + expect(EngineLogic.values).toEqual({ + ...DEFAULT_VALUES, + dataLoading: true, + }); + }); + }); + }); + + describe('initializeEngine', () => { + it('fetches and sets engine data', async () => { + mount({ engineName: 'some-engine' }); + jest.spyOn(EngineLogic.actions, 'setEngineData'); + const promise = Promise.resolve(mockEngineData); + http.get.mockReturnValueOnce(promise); + + EngineLogic.actions.initializeEngine(); + await promise; + + expect(http.get).toHaveBeenCalledWith('/api/app_search/engines/some-engine'); + expect(EngineLogic.actions.setEngineData).toHaveBeenCalledWith(mockEngineData); + }); + + it('handles errors', async () => { + mount(); + jest.spyOn(EngineLogic.actions, 'setEngineNotFound'); + const promise = Promise.reject('An error occured'); + http.get.mockReturnValue(promise); + + try { + EngineLogic.actions.initializeEngine(); + await promise; + } catch { + // Do nothing + } + expect(EngineLogic.actions.setEngineNotFound).toHaveBeenCalledWith(true); + }); + }); + }); + + describe('selectors', () => { + describe('isSampleEngine', () => { + it('should be set based on engine.sample', () => { + const mockSampleEngine = { ...mockEngineData, sample: true }; + mount({ engine: mockSampleEngine }); + + expect(EngineLogic.values).toEqual({ + ...DEFAULT_VALUES, + engine: mockSampleEngine, + isSampleEngine: true, + }); + }); + }); + + describe('isMetaEngine', () => { + it('should be set based on engine.type', () => { + const mockMetaEngine = { ...mockEngineData, type: 'meta' }; + mount({ engine: mockMetaEngine }); + + expect(EngineLogic.values).toEqual({ + ...DEFAULT_VALUES, + engine: mockMetaEngine, + isMetaEngine: true, + }); + }); + }); + + describe('hasSchemaConflicts', () => { + it('should be set based on engine.schemaConflicts', () => { + const mockSchemaEngine = { + ...mockEngineData, + schemaConflicts: { + someSchemaField: { + fieldTypes: { + number: ['some-engine'], + date: ['another-engine'], + }, + }, + }, + }; + mount({ engine: mockSchemaEngine }); + + expect(EngineLogic.values).toEqual({ + ...DEFAULT_VALUES, + engine: mockSchemaEngine, + hasSchemaConflicts: true, + }); + }); + }); + + describe('hasUnconfirmedSchemaFields', () => { + it('should be set based on engine.unconfirmedFields', () => { + const mockUnconfirmedFieldsEngine = { + ...mockEngineData, + unconfirmedFields: ['new_field_1', 'new_field_2'], + }; + mount({ engine: mockUnconfirmedFieldsEngine }); + + expect(EngineLogic.values).toEqual({ + ...DEFAULT_VALUES, + engine: mockUnconfirmedFieldsEngine, + hasUnconfirmedSchemaFields: true, + }); + }); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.ts new file mode 100644 index 0000000000000..2e7595e3ee87b --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.ts @@ -0,0 +1,101 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { kea, MakeLogicType } from 'kea'; + +import { HttpLogic } from '../../../shared/http'; + +import { IndexingStatus } from '../schema/types'; +import { EngineDetails } from './types'; + +interface EngineValues { + dataLoading: boolean; + engine: EngineDetails | {}; + engineName: string; + isMetaEngine: boolean; + isSampleEngine: boolean; + hasSchemaConflicts: boolean; + hasUnconfirmedSchemaFields: boolean; + engineNotFound: boolean; +} + +interface EngineActions { + setEngineData(engine: EngineDetails): { engine: EngineDetails }; + setEngineName(engineName: string): { engineName: string }; + setIndexingStatus(activeReindexJob: IndexingStatus): { activeReindexJob: IndexingStatus }; + setEngineNotFound(notFound: boolean): { notFound: boolean }; + clearEngine(): void; + initializeEngine(): void; +} + +export const EngineLogic = kea>({ + path: ['enterprise_search', 'app_search', 'engine_logic'], + actions: { + setEngineData: (engine) => ({ engine }), + setEngineName: (engineName) => ({ engineName }), + setIndexingStatus: (activeReindexJob) => ({ activeReindexJob }), + setEngineNotFound: (notFound) => ({ notFound }), + clearEngine: true, + initializeEngine: true, + }, + reducers: { + dataLoading: [ + true, + { + setEngineData: () => false, + clearEngine: () => true, + }, + ], + engine: [ + {}, + { + setEngineData: (_, { engine }) => engine, + clearEngine: () => ({}), + setIndexingStatus: (state, { activeReindexJob }) => ({ + ...state, + activeReindexJob, + }), + }, + ], + engineName: [ + '', + { + setEngineName: (_, { engineName }) => engineName, + }, + ], + engineNotFound: [ + false, + { + setEngineNotFound: (_, { notFound }) => notFound, + }, + ], + }, + selectors: ({ selectors }) => ({ + isMetaEngine: [() => [selectors.engine], (engine) => engine?.type === 'meta'], + isSampleEngine: [() => [selectors.engine], (engine) => !!engine?.sample], + hasSchemaConflicts: [ + () => [selectors.engine], + (engine) => !!(engine?.schemaConflicts && Object.keys(engine.schemaConflicts).length > 0), + ], + hasUnconfirmedSchemaFields: [ + () => [selectors.engine], + (engine) => engine?.unconfirmedFields?.length > 0, + ], + }), + listeners: ({ actions, values }) => ({ + initializeEngine: async () => { + const { engineName } = values; + const { http } = HttpLogic.values; + + try { + const response = await http.get(`/api/app_search/engines/${engineName}`); + actions.setEngineData(response); + } catch (error) { + actions.setEngineNotFound(true); + } + }, + }), +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/index.ts index a3320ba5024ca..3d8f343312cc6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/index.ts @@ -5,3 +5,4 @@ */ export { EngineRouter, EngineNav } from './engine_nav'; +export { EngineLogic } from './engine_logic'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/types.ts new file mode 100644 index 0000000000000..635d1136291aa --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/types.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ApiToken } from '../credentials/types'; +import { Schema, SchemaConflicts, IndexingStatus } from '../schema/types'; + +export interface Engine { + name: string; + type: string; + language: string | null; + result_fields: { + [key: string]: ResultField; + }; +} + +export interface EngineDetails extends Engine { + created_at: string; + document_count: number; + field_count: number; + unsearchedUnconfirmedFields: boolean; + apiTokens: ApiToken[]; + apiKey: string; + schema: Schema; + schemaConflicts?: SchemaConflicts; + unconfirmedFields?: string[]; + activeReindexJob?: IndexingStatus; + invalidBoosts: boolean; + sample?: boolean; + isMeta: boolean; + engine_count?: number; + includedEngines?: EngineDetails[]; +} + +interface ResultField { + raw: object; + snippet?: { + size: number; + fallback: boolean; + }; +} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.tsx index 559fef695d63b..0381c3806fec7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.tsx @@ -28,11 +28,11 @@ import { EnginesTable } from './engines_table'; import './engines_overview.scss'; -interface IGetEnginesParams { +interface GetEnginesParams { type: string; pageIndex: number; } -interface ISetEnginesCallbacks { +interface SetEnginesCallbacks { setResults: React.Dispatch>; setResultsTotal: React.Dispatch>; } @@ -49,12 +49,12 @@ export const EnginesOverview: React.FC = () => { const [metaEnginesPage, setMetaEnginesPage] = useState(1); const [metaEnginesTotal, setMetaEnginesTotal] = useState(0); - const getEnginesData = async ({ type, pageIndex }: IGetEnginesParams) => { + const getEnginesData = async ({ type, pageIndex }: GetEnginesParams) => { return await http.get('/api/app_search/engines', { query: { type, pageIndex }, }); }; - const setEnginesData = async (params: IGetEnginesParams, callbacks: ISetEnginesCallbacks) => { + const setEnginesData = async (params: GetEnginesParams, callbacks: SetEnginesCallbacks) => { const response = await getEnginesData(params); callbacks.setResults(response.results); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.tsx index a9cf64b3dffda..9591bbda1f7c2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.tsx @@ -16,28 +16,28 @@ import { getEngineRoute } from '../../routes'; import { ENGINES_PAGE_SIZE } from '../../../../../common/constants'; -interface IEnginesTableData { +interface EnginesTableData { name: string; created_at: string; document_count: number; field_count: number; } -interface IEnginesTablePagination { +interface EnginesTablePagination { totalEngines: number; pageIndex: number; onPaginate(pageIndex: number): void; } -interface IEnginesTableProps { - data: IEnginesTableData[]; - pagination: IEnginesTablePagination; +interface EnginesTableProps { + data: EnginesTableData[]; + pagination: EnginesTablePagination; } -interface IOnChange { +interface OnChange { page: { index: number; }; } -export const EnginesTable: React.FC = ({ +export const EnginesTable: React.FC = ({ data, pagination: { totalEngines, pageIndex, onPaginate }, }) => { @@ -52,7 +52,7 @@ export const EnginesTable: React.FC = ({ }), }); - const columns: Array> = [ + const columns: Array> = [ { field: 'name', name: i18n.translate('xpack.enterpriseSearch.appSearch.enginesOverview.table.column.name', { @@ -144,7 +144,7 @@ export const EnginesTable: React.FC = ({ totalItemCount: totalEngines, hidePerPageOptions: true, }} - onChange={({ page }: IOnChange) => { + onChange={({ page }: OnChange) => { const { index } = page; onPaginate(index + 1); // Note on paging - App Search's API pages start at 1, EuiBasicTables' pages start at 0 }} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/types.ts new file mode 100644 index 0000000000000..84f402dd3b95f --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/types.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export type SchemaTypes = 'text' | 'number' | 'geolocation' | 'date'; + +export interface Schema { + [key: string]: SchemaTypes; +} + +// this is a mapping of schema field types ("string", "number", "geolocation", "date") to the names +// of source engines which utilize that type +export type SchemaConflictFieldTypes = { + [key in SchemaTypes]: string[]; +}; + +export interface SchemaConflict { + fieldTypes: SchemaConflictFieldTypes; + resolution?: string; +} + +// For now these values are ISchemaConflictFieldTypes, but in the near future will be ISchemaConflict +// once we implement schema conflict resolution +export interface SchemaConflicts { + [key: string]: SchemaConflictFieldTypes; +} + +export interface IndexingStatus { + percentageComplete: number; + numDocumentsWithErrors: number; + activeReindexJobId: number; +} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_logic.test.ts index 367c7b085123f..c86d7e3e915e2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_logic.test.ts @@ -17,7 +17,7 @@ jest.mock('../../../../shared/flash_messages', () => ({ })); import { flashAPIErrors } from '../../../../shared/flash_messages'; -import { ELogRetentionOptions } from './types'; +import { LogRetentionOptions } from './types'; import { LogRetentionLogic } from './log_retention_logic'; describe('LogRetentionLogic', () => { @@ -87,11 +87,11 @@ describe('LogRetentionLogic', () => { it('should be set to the provided value', () => { mount(); - LogRetentionLogic.actions.setOpenedModal(ELogRetentionOptions.Analytics); + LogRetentionLogic.actions.setOpenedModal(LogRetentionOptions.Analytics); expect(LogRetentionLogic.values).toEqual({ ...DEFAULT_VALUES, - openedModal: ELogRetentionOptions.Analytics, + openedModal: LogRetentionOptions.Analytics, }); }); }); @@ -194,10 +194,10 @@ describe('LogRetentionLogic', () => { describe('openedModal', () => { it('should be reset to null', () => { mount({ - openedModal: ELogRetentionOptions.Analytics, + openedModal: LogRetentionOptions.Analytics, }); - LogRetentionLogic.actions.saveLogRetention(ELogRetentionOptions.Analytics, true); + LogRetentionLogic.actions.saveLogRetention(LogRetentionOptions.Analytics, true); expect(LogRetentionLogic.values).toEqual({ ...DEFAULT_VALUES, @@ -211,7 +211,7 @@ describe('LogRetentionLogic', () => { const promise = Promise.resolve(TYPICAL_SERVER_LOG_RETENTION); http.put.mockReturnValue(promise); - LogRetentionLogic.actions.saveLogRetention(ELogRetentionOptions.Analytics, true); + LogRetentionLogic.actions.saveLogRetention(LogRetentionOptions.Analytics, true); expect(http.put).toHaveBeenCalledWith('/api/app_search/log_settings', { body: JSON.stringify({ @@ -233,7 +233,7 @@ describe('LogRetentionLogic', () => { const promise = Promise.reject('An error occured'); http.put.mockReturnValue(promise); - LogRetentionLogic.actions.saveLogRetention(ELogRetentionOptions.Analytics, true); + LogRetentionLogic.actions.saveLogRetention(LogRetentionOptions.Analytics, true); try { await promise; @@ -252,7 +252,7 @@ describe('LogRetentionLogic', () => { isLogRetentionUpdating: false, }); - LogRetentionLogic.actions.toggleLogRetention(ELogRetentionOptions.Analytics); + LogRetentionLogic.actions.toggleLogRetention(LogRetentionOptions.Analytics); expect(LogRetentionLogic.values).toEqual({ ...DEFAULT_VALUES, @@ -264,17 +264,17 @@ describe('LogRetentionLogic', () => { it('will call setOpenedModal if already enabled', () => { mount({ logRetention: { - [ELogRetentionOptions.Analytics]: { + [LogRetentionOptions.Analytics]: { enabled: true, }, }, }); jest.spyOn(LogRetentionLogic.actions, 'setOpenedModal'); - LogRetentionLogic.actions.toggleLogRetention(ELogRetentionOptions.Analytics); + LogRetentionLogic.actions.toggleLogRetention(LogRetentionOptions.Analytics); expect(LogRetentionLogic.actions.setOpenedModal).toHaveBeenCalledWith( - ELogRetentionOptions.Analytics + LogRetentionOptions.Analytics ); }); }); @@ -337,17 +337,17 @@ describe('LogRetentionLogic', () => { it('will call saveLogRetention if NOT already enabled', () => { mount({ logRetention: { - [ELogRetentionOptions.Analytics]: { + [LogRetentionOptions.Analytics]: { enabled: false, }, }, }); jest.spyOn(LogRetentionLogic.actions, 'saveLogRetention'); - LogRetentionLogic.actions.toggleLogRetention(ELogRetentionOptions.Analytics); + LogRetentionLogic.actions.toggleLogRetention(LogRetentionOptions.Analytics); expect(LogRetentionLogic.actions.saveLogRetention).toHaveBeenCalledWith( - ELogRetentionOptions.Analytics, + LogRetentionOptions.Analytics, true ); }); @@ -359,7 +359,7 @@ describe('LogRetentionLogic', () => { jest.spyOn(LogRetentionLogic.actions, 'saveLogRetention'); jest.spyOn(LogRetentionLogic.actions, 'setOpenedModal'); - LogRetentionLogic.actions.toggleLogRetention(ELogRetentionOptions.API); + LogRetentionLogic.actions.toggleLogRetention(LogRetentionOptions.API); expect(LogRetentionLogic.actions.saveLogRetention).not.toHaveBeenCalled(); expect(LogRetentionLogic.actions.setOpenedModal).not.toHaveBeenCalled(); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_logic.ts index 28830f2edb1d9..31fc41213492d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_logic.ts @@ -6,31 +6,31 @@ import { kea, MakeLogicType } from 'kea'; -import { ELogRetentionOptions, ILogRetention, ILogRetentionServer } from './types'; +import { LogRetentionOptions, LogRetention, LogRetentionServer } from './types'; import { HttpLogic } from '../../../../shared/http'; import { flashAPIErrors } from '../../../../shared/flash_messages'; import { convertLogRetentionFromServerToClient } from './utils/convert_log_retention'; -interface ILogRetentionActions { +interface LogRetentionActions { clearLogRetentionUpdating(): { value: boolean }; closeModals(): { value: boolean }; fetchLogRetention(): { value: boolean }; saveLogRetention( - option: ELogRetentionOptions, + option: LogRetentionOptions, enabled: boolean - ): { option: ELogRetentionOptions; enabled: boolean }; - setOpenedModal(option: ELogRetentionOptions): { option: ELogRetentionOptions }; - toggleLogRetention(option: ELogRetentionOptions): { option: ELogRetentionOptions }; - updateLogRetention(logRetention: ILogRetention): { logRetention: ILogRetention }; + ): { option: LogRetentionOptions; enabled: boolean }; + setOpenedModal(option: LogRetentionOptions): { option: LogRetentionOptions }; + toggleLogRetention(option: LogRetentionOptions): { option: LogRetentionOptions }; + updateLogRetention(logRetention: LogRetention): { logRetention: LogRetention }; } -interface ILogRetentionValues { - logRetention: ILogRetention | null; +interface LogRetentionValues { + logRetention: LogRetention | null; isLogRetentionUpdating: boolean; - openedModal: ELogRetentionOptions | null; + openedModal: LogRetentionOptions | null; } -export const LogRetentionLogic = kea>({ +export const LogRetentionLogic = kea>({ path: ['enterprise_search', 'app_search', 'log_retention_logic'], actions: () => ({ clearLogRetentionUpdating: true, @@ -72,7 +72,7 @@ export const LogRetentionLogic = kea', () => { const actions = { @@ -160,7 +160,7 @@ describe('', () => { }); }); -const mockLogRetention = (logRetention: Partial) => { +const mockLogRetention = (logRetention: Partial) => { const baseLogRetention = { analytics: { disabledAt: null, diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_panel.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_panel.tsx index 7b8eea061d110..23572074b3c69 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_panel.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_panel.tsx @@ -12,7 +12,7 @@ import { useActions, useValues } from 'kea'; import { LogRetentionLogic } from './log_retention_logic'; import { AnalyticsLogRetentionMessage, ApiLogRetentionMessage } from './messaging'; -import { ELogRetentionOptions } from './types'; +import { LogRetentionOptions } from './types'; export const LogRetentionPanel: React.FC = () => { const { toggleLogRetention, fetchLogRetention } = useActions(LogRetentionLogic); @@ -20,8 +20,8 @@ export const LogRetentionPanel: React.FC = () => { const { logRetention, isLogRetentionUpdating } = useValues(LogRetentionLogic); const hasILM = logRetention !== null; - const analyticsLogRetentionSettings = logRetention?.[ELogRetentionOptions.Analytics]; - const apiLogRetentionSettings = logRetention?.[ELogRetentionOptions.API]; + const analyticsLogRetentionSettings = logRetention?.[LogRetentionOptions.Analytics]; + const apiLogRetentionSettings = logRetention?.[LogRetentionOptions.API]; useEffect(() => { fetchLogRetention(); @@ -73,7 +73,7 @@ export const LogRetentionPanel: React.FC = () => { } checked={!!analyticsLogRetentionSettings?.enabled} - onChange={() => toggleLogRetention(ELogRetentionOptions.Analytics)} + onChange={() => toggleLogRetention(LogRetentionOptions.Analytics)} disabled={isLogRetentionUpdating} data-test-subj="LogRetentionPanelAnalyticsSwitch" /> @@ -100,7 +100,7 @@ export const LogRetentionPanel: React.FC = () => { } checked={!!apiLogRetentionSettings?.enabled} - onChange={() => toggleLogRetention(ELogRetentionOptions.API)} + onChange={() => toggleLogRetention(LogRetentionOptions.API)} disabled={isLogRetentionUpdating} data-test-subj="LogRetentionPanelAPISwitch" /> diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/messaging/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/messaging/constants.ts index f7f0fbdff1acb..6db087e092697 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/messaging/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/messaging/constants.ts @@ -6,7 +6,7 @@ import { i18n } from '@kbn/i18n'; -import { ILogRetentionMessages } from './types'; +import { LogRetentionMessages } from './types'; import { renderLogRetentionDate } from '.'; const ANALYTICS_NO_LOGGING = i18n.translate( @@ -92,7 +92,7 @@ const API_STORED = (minAgeDays: number | null | undefined) => values: { minAgeDays }, }); -export const ANALYTICS_MESSAGES: ILogRetentionMessages = { +export const ANALYTICS_MESSAGES: LogRetentionMessages = { noLogging: (_, logRetentionSettings) => `${ANALYTICS_NO_LOGGING} ${ logRetentionSettings.disabledAt @@ -105,7 +105,7 @@ export const ANALYTICS_MESSAGES: ILogRetentionMessages = { ANALYTICS_STORED(logRetentionSettings.retentionPolicy?.minAgeDays), }; -export const API_MESSAGES: ILogRetentionMessages = { +export const API_MESSAGES: LogRetentionMessages = { noLogging: (_, logRetentionSettings) => `${API_NO_LOGGING} ${ logRetentionSettings.disabledAt diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/messaging/determine_tooltip_content.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/messaging/determine_tooltip_content.ts index e361e28490a83..385831dc511da 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/messaging/determine_tooltip_content.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/messaging/determine_tooltip_content.ts @@ -4,13 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILogRetentionSettings } from '../types'; -import { TMessageStringOrFunction, ILogRetentionMessages } from './types'; +import { LogRetentionSettings } from '../types'; +import { TMessageStringOrFunction, LogRetentionMessages } from './types'; export const determineTooltipContent = ( - messages: ILogRetentionMessages, + messages: LogRetentionMessages, ilmEnabled: boolean, - logRetentionSettings?: ILogRetentionSettings + logRetentionSettings?: LogRetentionSettings ) => { if (typeof logRetentionSettings === 'undefined') { return; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/messaging/index.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/messaging/index.tsx index 9fe2e8dc72ed6..21267738f61ad 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/messaging/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/messaging/index.tsx @@ -11,7 +11,7 @@ import moment from 'moment'; import { AppLogic } from '../../../../app_logic'; import { LogRetentionLogic } from '../log_retention_logic'; -import { ELogRetentionOptions } from '../types'; +import { LogRetentionOptions } from '../types'; import { determineTooltipContent } from './determine_tooltip_content'; import { ANALYTICS_MESSAGES, API_MESSAGES } from './constants'; @@ -29,7 +29,7 @@ export const AnalyticsLogRetentionMessage: React.FC = () => { {determineTooltipContent( ANALYTICS_MESSAGES, ilmEnabled, - logRetention[ELogRetentionOptions.Analytics] + logRetention[LogRetentionOptions.Analytics] )} ); @@ -41,6 +41,6 @@ export const ApiLogRetentionMessage: React.FC = () => { if (!logRetention) return null; return ( - <>{determineTooltipContent(API_MESSAGES, ilmEnabled, logRetention[ELogRetentionOptions.API])} + <>{determineTooltipContent(API_MESSAGES, ilmEnabled, logRetention[LogRetentionOptions.API])} ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/messaging/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/messaging/types.ts index a9546343d9aa7..8f86e397dfe26 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/messaging/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/messaging/types.ts @@ -4,13 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILogRetentionSettings } from '../types'; +import { LogRetentionSettings } from '../types'; export type TMessageStringOrFunction = | string - | ((ilmEnabled: boolean, logRetentionSettings: ILogRetentionSettings) => string); + | ((ilmEnabled: boolean, logRetentionSettings: LogRetentionSettings) => string); -export interface ILogRetentionMessages { +export interface LogRetentionMessages { noLogging: TMessageStringOrFunction; ilmDisabled: TMessageStringOrFunction; customPolicy: TMessageStringOrFunction; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/types.ts index 7d4f30f88f38f..f802efc72ac4d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/types.ts @@ -4,39 +4,39 @@ * you may not use this file except in compliance with the Elastic License. */ -export enum ELogRetentionOptions { +export enum LogRetentionOptions { Analytics = 'analytics', API = 'api', } -export interface ILogRetention { - [ELogRetentionOptions.Analytics]: ILogRetentionSettings; - [ELogRetentionOptions.API]: ILogRetentionSettings; +export interface LogRetention { + [LogRetentionOptions.Analytics]: LogRetentionSettings; + [LogRetentionOptions.API]: LogRetentionSettings; } -export interface ILogRetentionPolicy { +export interface LogRetentionPolicy { isDefault: boolean; minAgeDays: number | null; } -export interface ILogRetentionSettings { +export interface LogRetentionSettings { disabledAt?: string | null; enabled?: boolean; - retentionPolicy?: ILogRetentionPolicy | null; + retentionPolicy?: LogRetentionPolicy | null; } -export interface ILogRetentionServer { - [ELogRetentionOptions.Analytics]: ILogRetentionServerSettings; - [ELogRetentionOptions.API]: ILogRetentionServerSettings; +export interface LogRetentionServer { + [LogRetentionOptions.Analytics]: LogRetentionServerSettings; + [LogRetentionOptions.API]: LogRetentionServerSettings; } -export interface ILogRetentionServerPolicy { +export interface LogRetentionServerPolicy { is_default: boolean; min_age_days: number | null; } -export interface ILogRetentionServerSettings { +export interface LogRetentionServerSettings { disabled_at: string | null; enabled: boolean; - retention_policy: ILogRetentionServerPolicy | null; + retention_policy: LogRetentionServerPolicy | null; } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/utils/convert_log_retention.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/utils/convert_log_retention.ts index 1c0818fc286f2..f960bb8f193b4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/utils/convert_log_retention.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/utils/convert_log_retention.ts @@ -5,23 +5,23 @@ */ import { - ELogRetentionOptions, - ILogRetention, - ILogRetentionPolicy, - ILogRetentionServer, - ILogRetentionServerPolicy, - ILogRetentionServerSettings, - ILogRetentionSettings, + LogRetentionOptions, + LogRetention, + LogRetentionPolicy, + LogRetentionServer, + LogRetentionServerPolicy, + LogRetentionServerSettings, + LogRetentionSettings, } from '../types'; export const convertLogRetentionFromServerToClient = ( - logRetention: ILogRetentionServer -): ILogRetention => ({ - [ELogRetentionOptions.Analytics]: convertLogRetentionSettingsFromServerToClient( - logRetention[ELogRetentionOptions.Analytics] + logRetention: LogRetentionServer +): LogRetention => ({ + [LogRetentionOptions.Analytics]: convertLogRetentionSettingsFromServerToClient( + logRetention[LogRetentionOptions.Analytics] ), - [ELogRetentionOptions.API]: convertLogRetentionSettingsFromServerToClient( - logRetention[ELogRetentionOptions.API] + [LogRetentionOptions.API]: convertLogRetentionSettingsFromServerToClient( + logRetention[LogRetentionOptions.API] ), }); @@ -29,7 +29,7 @@ const convertLogRetentionSettingsFromServerToClient = ({ disabled_at: disabledAt, enabled, retention_policy: retentionPolicy, -}: ILogRetentionServerSettings): ILogRetentionSettings => ({ +}: LogRetentionServerSettings): LogRetentionSettings => ({ disabledAt, enabled, retentionPolicy: @@ -39,7 +39,7 @@ const convertLogRetentionSettingsFromServerToClient = ({ const convertLogRetentionPolicyFromServerToClient = ({ min_age_days: minAgeDays, is_default: isDefault, -}: ILogRetentionServerPolicy): ILogRetentionPolicy => ({ +}: LogRetentionServerPolicy): LogRetentionPolicy => ({ isDefault, minAgeDays, }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx index 4571ef10286e4..743cf63fb4bc3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx @@ -12,7 +12,7 @@ import { getAppSearchUrl } from '../shared/enterprise_search_url'; import { KibanaLogic } from '../shared/kibana'; import { HttpLogic } from '../shared/http'; import { AppLogic } from './app_logic'; -import { IInitialAppData } from '../../../common/types'; +import { InitialAppData } from '../../../common/types'; import { APP_SEARCH_PLUGIN } from '../../../common/constants'; import { Layout, SideNav, SideNavLink } from '../shared/layout'; @@ -36,7 +36,7 @@ import { Settings, SETTINGS_TITLE } from './components/settings'; import { Credentials, CREDENTIALS_TITLE } from './components/credentials'; import { ROLE_MAPPINGS_TITLE } from './components/role_mappings'; -export const AppSearch: React.FC = (props) => { +export const AppSearch: React.FC = (props) => { const { config } = useValues(KibanaLogic); return !config.host ? : ; }; @@ -52,7 +52,7 @@ export const AppSearchUnconfigured: React.FC = () => ( ); -export const AppSearchConfigured: React.FC = (props) => { +export const AppSearchConfigured: React.FC = (props) => { const { initializeAppData } = useActions(AppLogic); const { hasInitialized } = useValues(AppLogic); const { errorConnecting, readOnlyMode } = useValues(HttpLogic); @@ -100,11 +100,11 @@ export const AppSearchConfigured: React.FC = (props) => { ); }; -interface IAppSearchNavProps { +interface AppSearchNavProps { subNav?: React.ReactNode; } -export const AppSearchNav: React.FC = ({ subNav }) => { +export const AppSearchNav: React.FC = ({ subNav }) => { const { myRole: { canViewSettings, canViewAccountCredentials, canViewRoleMappings }, } = useValues(AppLogic); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/types.ts index 568a0a3365982..7c22a45757a93 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/types.ts @@ -5,11 +5,5 @@ */ export * from '../../../common/types/app_search'; -export { IRole, TRole, TAbility } from './utils/role'; - -export interface IEngine { - name: string; - type: string; - language: string; - result_fields: object[]; -} +export { Role, RoleTypes, AbilityTypes } from './utils/role'; +export { Engine } from './components/engine/types'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/index.ts index a935fa657738c..21a80bc0c208f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/index.ts @@ -4,18 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IAccount } from '../../types'; +import { Account } from '../../types'; -export type TRole = 'owner' | 'admin' | 'dev' | 'editor' | 'analyst'; -export type TAbility = 'manage' | 'edit' | 'view'; +export type RoleTypes = 'owner' | 'admin' | 'dev' | 'editor' | 'analyst'; +export type AbilityTypes = 'manage' | 'edit' | 'view'; -export interface IRole { +export interface Role { id: string; - roleType: TRole; - availableRoleTypes: TRole[]; + roleType: RoleTypes; + availableRoleTypes: RoleTypes[]; credentialTypes: string[]; canAccessAllEngines: boolean; - can(action: TAbility, subject: string): boolean; + can(action: AbilityTypes, subject: string): boolean; canViewMetaEngines: boolean; canViewAccountCredentials: boolean; canViewEngineAnalytics: boolean; @@ -48,10 +48,10 @@ export interface IRole { * Transforms the `role` data we receive from the Enterprise Search * server into a more convenient format for front-end use */ -export const getRoleAbilities = (role: IAccount['role']): IRole => { +export const getRoleAbilities = (role: Account['role']): Role => { // Role ability function helpers const myRole = { - can: (action: TAbility, subject: string): boolean => { + can: (action: AbilityTypes, subject: string): boolean => { return ( role?.ability?.manage?.includes(subject) || (Array.isArray(role.ability[action]) && role.ability[action].includes(subject)) @@ -63,8 +63,8 @@ export const getRoleAbilities = (role: IAccount['role']): IRole => { // Clone top-level role props, and move some props out of `ability` and into the top-level for convenience const topLevelProps = { id: role.id, - roleType: role.roleType as TRole, - availableRoleTypes: role.ability.availableRoleTypes as TRole[], + roleType: role.roleType as RoleTypes, + availableRoleTypes: role.ability.availableRoleTypes as RoleTypes[], credentialTypes: role.ability.credentialTypes, }; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_card/product_card.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_card/product_card.tsx index ee778f49ef5b6..de553acf11f7b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_card/product_card.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_card/product_card.tsx @@ -16,7 +16,7 @@ import { KibanaLogic } from '../../../shared/kibana'; import './product_card.scss'; -interface IProductCard { +interface ProductCardProps { // Expects product plugin constants (@see common/constants.ts) product: { ID: string; @@ -27,7 +27,7 @@ interface IProductCard { image: string; } -export const ProductCard: React.FC = ({ product, image }) => { +export const ProductCard: React.FC = ({ product, image }) => { const { sendEnterpriseSearchTelemetry } = useActions(TelemetryLogic); const { config } = useValues(KibanaLogic); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_selector/product_selector.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_selector/product_selector.tsx index 235ececd8b6fc..579baf41f2ce3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_selector/product_selector.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_selector/product_selector.tsx @@ -30,14 +30,14 @@ import { SetupGuideCta } from '../setup_guide'; import AppSearchImage from '../../assets/app_search.png'; import WorkplaceSearchImage from '../../assets/workplace_search.png'; -interface IProductSelectorProps { +interface ProductSelectorProps { access: { hasAppSearchAccess?: boolean; hasWorkplaceSearchAccess?: boolean; }; } -export const ProductSelector: React.FC = ({ access }) => { +export const ProductSelector: React.FC = ({ access }) => { const { hasAppSearchAccess, hasWorkplaceSearchAccess } = access; const { config } = useValues(KibanaLogic); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.tsx index 048baabe6a1dd..b562cd577c56b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.tsx @@ -9,7 +9,7 @@ import { Route, Switch } from 'react-router-dom'; import { useValues } from 'kea'; import { KibanaLogic } from '../shared/kibana'; -import { IInitialAppData } from '../../../common/types'; +import { InitialAppData } from '../../../common/types'; import { HttpLogic } from '../shared/http'; @@ -21,7 +21,7 @@ import { SetupGuide } from './components/setup_guide'; import './index.scss'; -export const EnterpriseSearch: React.FC = ({ access = {} }) => { +export const EnterpriseSearch: React.FC = ({ access = {} }) => { const { errorConnecting } = useValues(HttpLogic); const { config } = useValues(KibanaLogic); diff --git a/x-pack/plugins/enterprise_search/public/applications/index.tsx b/x-pack/plugins/enterprise_search/public/applications/index.tsx index b9c94e351089d..3436df851c8d8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/index.tsx @@ -14,7 +14,7 @@ import { I18nProvider } from '@kbn/i18n/react'; import { AppMountParameters, CoreStart } from 'src/core/public'; import { PluginsStart, ClientConfigType, ClientData } from '../plugin'; -import { IInitialAppData } from '../../common/types'; +import { InitialAppData } from '../../common/types'; import { mountKibanaLogic } from './shared/kibana'; import { mountLicensingLogic } from './shared/licensing'; @@ -29,7 +29,7 @@ import { externalUrl } from './shared/enterprise_search_url'; */ export const renderApp = ( - App: React.FC, + App: React.FC, { params, core, plugins }: { params: AppMountParameters; core: CoreStart; plugins: PluginsStart }, { config, data }: { config: ClientConfigType; data: ClientData } ) => { diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages_logic.ts b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages_logic.ts index 5a05a03adeb6b..7271a1dbbea39 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages_logic.ts @@ -15,12 +15,12 @@ export interface IFlashMessage { description?: ReactNode; } -export interface IFlashMessagesValues { +interface FlashMessagesValues { messages: IFlashMessage[]; queuedMessages: IFlashMessage[]; historyListener: Function | null; } -export interface IFlashMessagesActions { +interface FlashMessagesActions { setFlashMessages(messages: IFlashMessage | IFlashMessage[]): { messages: IFlashMessage[] }; clearFlashMessages(): void; setQueuedMessages(messages: IFlashMessage | IFlashMessage[]): { messages: IFlashMessage[] }; @@ -31,7 +31,7 @@ export interface IFlashMessagesActions { const convertToArray = (messages: IFlashMessage | IFlashMessage[]) => !Array.isArray(messages) ? [messages] : messages; -export const FlashMessagesLogic = kea>({ +export const FlashMessagesLogic = kea>({ path: ['enterprise_search', 'flash_messages_logic'], actions: { setFlashMessages: (messages) => ({ messages: convertToArray(messages) }), diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/handle_api_errors.ts b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/handle_api_errors.ts index 2bd04d1d87f7d..c4b287ee08354 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/handle_api_errors.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/handle_api_errors.ts @@ -17,7 +17,7 @@ import { FlashMessagesLogic, IFlashMessage } from './'; * `errors` property in the response's data, which will contain messages we can * display to the user. */ -interface IErrorResponse { +interface ErrorResponse { statusCode: number; error: string; message: string; @@ -25,17 +25,14 @@ interface IErrorResponse { errors: string[]; }; } -interface IOptions { +interface Options { isQueued?: boolean; } /** * Converts API/HTTP errors into user-facing Flash Messages */ -export const flashAPIErrors = ( - error: HttpResponse, - { isQueued }: IOptions = {} -) => { +export const flashAPIErrors = (error: HttpResponse, { isQueued }: Options = {}) => { const defaultErrorMessage = 'An unexpected error occurred'; const errorFlashMessages: IFlashMessage[] = Array.isArray(error?.body?.attributes?.errors) diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/index.ts index 21c1a60efa6b7..8792c26f9bad4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/index.ts @@ -5,12 +5,6 @@ */ export { FlashMessages } from './flash_messages'; -export { - FlashMessagesLogic, - IFlashMessage, - IFlashMessagesValues, - IFlashMessagesActions, - mountFlashMessagesLogic, -} from './flash_messages_logic'; +export { FlashMessagesLogic, IFlashMessage, mountFlashMessagesLogic } from './flash_messages_logic'; export { flashAPIErrors } from './handle_api_errors'; export { setSuccessMessage, setErrorMessage, setQueuedSuccessMessage } from './set_message_helpers'; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/hidden_text/hidden_text.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/hidden_text/hidden_text.tsx index 9b0833dfce541..69176b8a139e7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/hidden_text/hidden_text.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/hidden_text/hidden_text.tsx @@ -7,18 +7,18 @@ import React, { useState, ReactElement } from 'react'; import { i18n } from '@kbn/i18n'; -interface IChildrenProps { +interface ChildrenProps { toggle: () => void; isHidden: boolean; hiddenText: React.ReactNode; } -interface IProps { +interface Props { text: string; - children(props: IChildrenProps): ReactElement; + children(props: ChildrenProps): ReactElement; } -export const HiddenText: React.FC = ({ text, children }) => { +export const HiddenText: React.FC = ({ text, children }) => { const [isHidden, toggleIsHidden] = useState(true); const hiddenLabel = i18n.translate('xpack.enterpriseSearch.hiddenText', { diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/http/http_logic.ts b/x-pack/plugins/enterprise_search/public/applications/shared/http/http_logic.ts index d16e507bfb3bc..76cefa4bc5a2c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/http/http_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/http/http_logic.ts @@ -10,20 +10,20 @@ import { HttpSetup, HttpInterceptorResponseError, HttpResponse } from 'src/core/ import { READ_ONLY_MODE_HEADER } from '../../../../common/constants'; -export interface IHttpValues { +interface HttpValues { http: HttpSetup; httpInterceptors: Function[]; errorConnecting: boolean; readOnlyMode: boolean; } -export interface IHttpActions { +interface HttpActions { initializeHttpInterceptors(): void; setHttpInterceptors(httpInterceptors: Function[]): { httpInterceptors: Function[] }; setErrorConnecting(errorConnecting: boolean): { errorConnecting: boolean }; setReadOnlyMode(readOnlyMode: boolean): { readOnlyMode: boolean }; } -export const HttpLogic = kea>({ +export const HttpLogic = kea>({ path: ['enterprise_search', 'http_logic'], actions: { initializeHttpInterceptors: () => null, @@ -108,12 +108,12 @@ export const HttpLogic = kea>({ /** * Mount/props helper */ -interface IHttpLogicProps { +interface HttpLogicProps { http: HttpSetup; errorConnecting?: boolean; readOnlyMode?: boolean; } -export const mountHttpLogic = (props: IHttpLogicProps) => { +export const mountHttpLogic = (props: HttpLogicProps) => { HttpLogic(props); const unmount = HttpLogic.mount(); return unmount; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/http/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/http/index.ts index 46a52415f8564..5e41ea032d503 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/http/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/http/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { HttpLogic, IHttpValues, IHttpActions, mountHttpLogic } from './http_logic'; +export { HttpLogic, mountHttpLogic } from './http_logic'; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts b/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts index 89ed07f302b03..28f500a2c8a39 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts @@ -11,9 +11,9 @@ import { History } from 'history'; import { ApplicationStart, ChromeBreadcrumb } from 'src/core/public'; import { HttpLogic } from '../http'; -import { createHref, ICreateHrefOptions } from '../react_router_helpers'; +import { createHref, CreateHrefOptions } from '../react_router_helpers'; -interface IKibanaLogicProps { +interface KibanaLogicProps { config: { host?: string }; history: History; navigateToUrl: ApplicationStart['navigateToUrl']; @@ -21,17 +21,17 @@ interface IKibanaLogicProps { setDocTitle(title: string): void; renderHeaderActions(HeaderActions: FC): void; } -export interface IKibanaValues extends IKibanaLogicProps { - navigateToUrl(path: string, options?: ICreateHrefOptions): Promise; +export interface KibanaValues extends KibanaLogicProps { + navigateToUrl(path: string, options?: CreateHrefOptions): Promise; } -export const KibanaLogic = kea>({ +export const KibanaLogic = kea>({ path: ['enterprise_search', 'kibana_logic'], reducers: ({ props }) => ({ config: [props.config || {}, {}], history: [props.history, {}], navigateToUrl: [ - (url: string, options?: ICreateHrefOptions) => { + (url: string, options?: CreateHrefOptions) => { const deps = { history: props.history, http: HttpLogic.values.http }; const href = createHref(url, deps, options); return props.navigateToUrl(href); @@ -44,7 +44,7 @@ export const KibanaLogic = kea>({ }), }); -export const mountKibanaLogic = (props: IKibanaLogicProps) => { +export const mountKibanaLogic = (props: KibanaLogicProps) => { KibanaLogic(props); const unmount = KibanaLogic.mount(); return unmount; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.ts b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.ts index e22334aeea371..25d4850425a6e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.ts @@ -23,15 +23,15 @@ import { letBrowserHandleEvent, createHref } from '../react_router_helpers'; * Types */ -interface IBreadcrumb { +interface Breadcrumb { text: string; path?: string; // Used to navigate outside of the React Router basename, // i.e. if we need to go from App Search to Enterprise Search shouldNotCreateHref?: boolean; } -export type TBreadcrumbs = IBreadcrumb[]; -export type TBreadcrumbTrail = string[]; // A trail of breadcrumb text +export type Breadcrumbs = Breadcrumb[]; +export type BreadcrumbTrail = string[]; // A trail of breadcrumb text /** * Generate an array of breadcrumbs based on: @@ -50,7 +50,7 @@ export type TBreadcrumbTrail = string[]; // A trail of breadcrumb text * > Source Prioritization (linked to `/groups/{example-group-id}/source_prioritization`) */ -export const useGenerateBreadcrumbs = (trail: TBreadcrumbTrail): TBreadcrumbs => { +export const useGenerateBreadcrumbs = (trail: BreadcrumbTrail): Breadcrumbs => { const { history } = useValues(KibanaLogic); const pathArray = stripLeadingSlash(history.location.pathname).split('/'); @@ -65,7 +65,7 @@ export const useGenerateBreadcrumbs = (trail: TBreadcrumbTrail): TBreadcrumbs => * https://elastic.github.io/eui/#/navigation/breadcrumbs */ -export const useEuiBreadcrumbs = (breadcrumbs: TBreadcrumbs): EuiBreadcrumb[] => { +export const useEuiBreadcrumbs = (breadcrumbs: Breadcrumbs): EuiBreadcrumb[] => { const { navigateToUrl, history } = useValues(KibanaLogic); const { http } = useValues(HttpLogic); @@ -89,7 +89,7 @@ export const useEuiBreadcrumbs = (breadcrumbs: TBreadcrumbs): EuiBreadcrumb[] => * Product-specific breadcrumb helpers */ -export const useEnterpriseSearchBreadcrumbs = (breadcrumbs: TBreadcrumbs = []) => +export const useEnterpriseSearchBreadcrumbs = (breadcrumbs: Breadcrumbs = []) => useEuiBreadcrumbs([ { text: ENTERPRISE_SEARCH_PLUGIN.NAME, @@ -99,10 +99,10 @@ export const useEnterpriseSearchBreadcrumbs = (breadcrumbs: TBreadcrumbs = []) = ...breadcrumbs, ]); -export const useAppSearchBreadcrumbs = (breadcrumbs: TBreadcrumbs = []) => +export const useAppSearchBreadcrumbs = (breadcrumbs: Breadcrumbs = []) => useEnterpriseSearchBreadcrumbs([{ text: APP_SEARCH_PLUGIN.NAME, path: '/' }, ...breadcrumbs]); -export const useWorkplaceSearchBreadcrumbs = (breadcrumbs: TBreadcrumbs = []) => +export const useWorkplaceSearchBreadcrumbs = (breadcrumbs: Breadcrumbs = []) => useEnterpriseSearchBreadcrumbs([ { text: WORKPLACE_SEARCH_PLUGIN.NAME, path: '/' }, ...breadcrumbs, diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_title.ts b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_title.ts index de5f72de79192..a0e34106fe2a2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_title.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_title.ts @@ -15,24 +15,24 @@ import { * https://github.com/elastic/kibana/blob/master/docs/development/core/public/kibana-plugin-core-public.chromedoctitle.md */ -export type TTitle = string[]; +type Title = string[]; /** * Given an array of page titles, return a final formatted document title * @param pages - e.g., ['Curations', 'some Engine', 'App Search'] * @returns - e.g., 'Curations - some Engine - App Search' */ -export const generateTitle = (pages: TTitle) => pages.join(' - '); +export const generateTitle = (pages: Title) => pages.join(' - '); /** * Product-specific helpers */ -export const enterpriseSearchTitle = (page: TTitle = []) => +export const enterpriseSearchTitle = (page: Title = []) => generateTitle([...page, ENTERPRISE_SEARCH_PLUGIN.NAME]); -export const appSearchTitle = (page: TTitle = []) => +export const appSearchTitle = (page: Title = []) => generateTitle([...page, APP_SEARCH_PLUGIN.NAME]); -export const workplaceSearchTitle = (page: TTitle = []) => +export const workplaceSearchTitle = (page: Title = []) => generateTitle([...page, WORKPLACE_SEARCH_PLUGIN.NAME]); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/set_chrome.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/set_chrome.tsx index a43e7053bb1e1..0e694a3d2bbc8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/set_chrome.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/set_chrome.tsx @@ -14,7 +14,7 @@ import { useEnterpriseSearchBreadcrumbs, useAppSearchBreadcrumbs, useWorkplaceSearchBreadcrumbs, - TBreadcrumbTrail, + BreadcrumbTrail, } from './generate_breadcrumbs'; import { enterpriseSearchTitle, appSearchTitle, workplaceSearchTitle } from './generate_title'; @@ -33,11 +33,11 @@ import { enterpriseSearchTitle, appSearchTitle, workplaceSearchTitle } from './g * Title output: Workplace Search - Elastic */ -interface ISetChromeProps { - trail?: TBreadcrumbTrail; +interface SetChromeProps { + trail?: BreadcrumbTrail; } -export const SetEnterpriseSearchChrome: React.FC = ({ trail = [] }) => { +export const SetEnterpriseSearchChrome: React.FC = ({ trail = [] }) => { const { setBreadcrumbs, setDocTitle } = useValues(KibanaLogic); const title = reverseArray(trail); @@ -54,7 +54,7 @@ export const SetEnterpriseSearchChrome: React.FC = ({ trail = [ return null; }; -export const SetAppSearchChrome: React.FC = ({ trail = [] }) => { +export const SetAppSearchChrome: React.FC = ({ trail = [] }) => { const { setBreadcrumbs, setDocTitle } = useValues(KibanaLogic); const title = reverseArray(trail); @@ -71,7 +71,7 @@ export const SetAppSearchChrome: React.FC = ({ trail = [] }) => return null; }; -export const SetWorkplaceSearchChrome: React.FC = ({ trail = [] }) => { +export const SetWorkplaceSearchChrome: React.FC = ({ trail = [] }) => { const { setBreadcrumbs, setDocTitle } = useValues(KibanaLogic); const title = reverseArray(trail); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/layout.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/layout.tsx index ef8216e8b6711..0ee7de242dfe2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/layout.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/layout.tsx @@ -12,7 +12,7 @@ import { i18n } from '@kbn/i18n'; import './layout.scss'; -interface ILayoutProps { +interface LayoutProps { navigation: React.ReactNode; restrictWidth?: boolean; readOnlyMode?: boolean; @@ -23,7 +23,7 @@ export interface INavContext { } export const NavContext = React.createContext({}); -export const Layout: React.FC = ({ +export const Layout: React.FC = ({ children, navigation, restrictWidth, diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/side_nav.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/side_nav.tsx index facfd0bfcb16d..6c4e1d084c16d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/side_nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/side_nav.tsx @@ -23,7 +23,7 @@ import './side_nav.scss'; * Side navigation - product & icon + links wrapper */ -interface ISideNavProps { +interface SideNavProps { // Expects product plugin constants (@see common/constants.ts) product: { NAME: string; @@ -31,7 +31,7 @@ interface ISideNavProps { }; } -export const SideNav: React.FC = ({ product, children }) => { +export const SideNav: React.FC = ({ product, children }) => { return (