From 1868e6ba9c52d945ee2cfaddb0270ac7e2e75b84 Mon Sep 17 00:00:00 2001 From: Michail Yasonik Date: Tue, 15 Sep 2020 12:37:16 -0400 Subject: [PATCH] [7.x] Stacked headers and navigational search (#72331) (#77407) Co-authored-by: Poff Poffenberger Co-authored-by: Ryan Keairns Co-authored-by: pgayvallet Co-authored-by: cchaos --- .github/CODEOWNERS | 304 + .../architecture/code-exploration.asciidoc | 598 + docs/developer/plugin-list.asciidoc | 4 + ...na-plugin-core-public.chromenavcontrols.md | 5 +- ...public.chromenavcontrols.registercenter.md | 24 + ...e-public.chromenavcontrols.registerleft.md | 2 +- ...-public.chromenavcontrols.registerright.md | 2 +- ...gin-core-public.chromestart.getnavtype_.md | 17 - .../kibana-plugin-core-public.chromestart.md | 1 - .../lib/config/schema.ts | 2 +- src/core/public/_variables.scss | 3 + .../public/application/ui/app_container.tsx | 6 +- src/core/public/chrome/chrome_service.mock.ts | 13 +- src/core/public/chrome/chrome_service.tsx | 16 +- .../nav_controls/nav_controls_service.ts | 17 +- .../collapsible_nav.test.tsx.snap | 464 +- .../header/__snapshots__/header.test.tsx.snap | 15154 ++++------------ src/core/public/chrome/ui/header/_index.scss | 9 - .../chrome/ui/header/collapsible_nav.test.tsx | 18 +- .../chrome/ui/header/collapsible_nav.tsx | 6 +- .../public/chrome/ui/header/header.test.tsx | 15 +- src/core/public/chrome/ui/header/header.tsx | 188 +- .../ui/header/header_action_menu.test.tsx | 139 + .../chrome/ui/header/header_action_menu.tsx | 64 + .../public/chrome/ui/header/header_logo.tsx | 4 +- .../chrome/ui/header/header_nav_controls.tsx | 7 +- .../public/chrome/ui/header/nav_drawer.tsx | 83 - src/core/public/index.scss | 2 +- src/core/public/public.api.md | 4 +- src/core/public/rendering/_base.scss | 70 +- src/core/public/styles/_base.scss | 11 - .../management_app/advanced_settings.tsx | 2 +- .../management_app/components/_index.scss | 1 - .../management_app/components/form/_form.scss | 15 - .../components/form/_index.scss | 1 - .../management_app/components/form/form.tsx | 19 +- .../public/management_app/index.scss | 2 - src/plugins/console/public/styles/_app.scss | 5 - .../public/application/_dashboard_app.scss | 1 - .../public/application/application.ts | 2 + .../application/dashboard_app_controller.tsx | 9 +- src/plugins/dashboard/public/plugin.tsx | 3 +- src/plugins/dev_tools/public/index.scss | 4 - src/plugins/dev_tools/public/plugin.ts | 2 +- .../public/application/angular/discover.html | 1 + .../public/application/angular/discover.js | 2 + .../discover/public/kibana_services.ts | 6 +- src/plugins/discover/public/plugin.ts | 5 +- .../public/angular/kbn_top_nav.js | 1 + src/plugins/kibana_react/public/util/index.ts | 1 + .../public/util/mount_point_portal.tsx | 23 +- src/plugins/kibana_react/public/util/utils.ts | 38 + src/plugins/management/public/plugin.ts | 2 +- .../public/top_nav_menu/_index.scss | 17 +- .../public/top_nav_menu/top_nav_menu.test.tsx | 5 +- .../public/top_nav_menu/top_nav_menu.tsx | 23 +- .../public/top_nav_menu/top_nav_menu_item.tsx | 10 +- .../components/newsfeed_header_nav_button.tsx | 2 +- src/plugins/timelion/public/_app.scss | 3 - src/plugins/timelion/public/plugin.ts | 2 +- .../components/visualize_top_nav.tsx | 3 + .../visualize/public/application/types.ts | 2 + src/plugins/visualize/public/plugin.ts | 4 +- test/accessibility/apps/management.ts | 3 +- test/functional/page_objects/time_picker.ts | 4 +- test/functional/services/listing_table.ts | 2 +- x-pack/.i18nrc.json | 6 +- x-pack/plugins/apm/public/plugin.ts | 3 +- .../__snapshots__/asset.stories.storyshot | 178 - .../components/fullscreen/fullscreen.scss | 13 +- .../plugins/canvas/public/lib/fullscreen.js | 4 + x-pack/plugins/canvas/public/plugin.tsx | 2 +- .../canvas/public/services/platform.ts | 3 + .../canvas/public/services/stubs/platform.ts | 1 + .../enterprise_search/common/constants.ts | 1 + .../enterprise_search/public/plugin.ts | 15 +- x-pack/plugins/global_search_bar/README.md | 3 + x-pack/plugins/global_search_bar/kibana.json | 10 + .../__snapshots__/search_bar.test.tsx.snap | 75 + .../public/components/search_bar.test.tsx | 97 + .../public/components/search_bar.tsx | 217 + .../plugins/global_search_bar/public/index.ts | 10 + .../global_search_bar/public/plugin.tsx | 46 + .../graph/public/angular/templates/index.html | 2 +- x-pack/plugins/graph/public/app.js | 2 + x-pack/plugins/graph/public/application.ts | 2 + .../plugins/graph/public/components/_app.scss | 2 +- x-pack/plugins/graph/public/plugin.ts | 2 +- x-pack/plugins/infra/public/plugin.ts | 4 +- .../ingest_manager/layouts/default.tsx | 10 +- .../create_package_policy_page/index.tsx | 22 +- .../components/settings/index.tsx | 29 +- .../edit_package_policy_page/index.tsx | 26 +- .../plugins/ingest_manager/public/plugin.ts | 2 +- .../lens/public/app_plugin/app.test.tsx | 3 + x-pack/plugins/lens/public/app_plugin/app.tsx | 3 + .../lens/public/app_plugin/mounter.tsx | 1 + x-pack/plugins/maps/common/constants.ts | 1 + x-pack/plugins/maps/public/_main.scss | 4 +- x-pack/plugins/maps/public/plugin.ts | 4 +- .../maps/public/routing/maps_router.tsx | 17 +- .../routes/maps_app/load_map_and_render.tsx | 2 + .../routing/routes/maps_app/maps_app_view.tsx | 2 + x-pack/plugins/ml/common/constants/app.ts | 1 + x-pack/plugins/ml/public/plugin.ts | 4 +- x-pack/plugins/observability/public/plugin.ts | 1 + .../public/application/components/main.tsx | 27 +- .../application/components/main_controls.tsx | 24 +- .../painless_lab/public/styles/_index.scss | 4 +- .../searchprofiler/public/styles/_index.scss | 4 +- .../security_solution/common/constants.ts | 1 + .../security_solution/cypress/tasks/login.ts | 4 +- .../security_solution/public/plugin.tsx | 16 +- .../translations/translations/ja-JP.json | 2 - .../translations/translations/zh-CN.json | 2 - x-pack/plugins/uptime/public/apps/plugin.ts | 2 +- .../apps/dashboard/reporting/screenshots.ts | 3 + .../functional/page_objects/graph_page.ts | 2 +- .../global_search/global_search_bar.ts | 39 + .../test_suites/global_search/index.ts | 1 + 120 files changed, 5508 insertions(+), 12930 deletions(-) create mode 100644 .github/CODEOWNERS create mode 100644 docs/developer/architecture/code-exploration.asciidoc create mode 100644 docs/development/core/public/kibana-plugin-core-public.chromenavcontrols.registercenter.md delete mode 100644 docs/development/core/public/kibana-plugin-core-public.chromestart.getnavtype_.md create mode 100644 src/core/public/_variables.scss create mode 100644 src/core/public/chrome/ui/header/header_action_menu.test.tsx create mode 100644 src/core/public/chrome/ui/header/header_action_menu.tsx delete mode 100644 src/core/public/chrome/ui/header/nav_drawer.tsx delete mode 100644 src/plugins/advanced_settings/public/management_app/components/_index.scss delete mode 100644 src/plugins/advanced_settings/public/management_app/components/form/_form.scss delete mode 100644 src/plugins/advanced_settings/public/management_app/components/form/_index.scss create mode 100644 src/plugins/kibana_react/public/util/utils.ts create mode 100644 x-pack/plugins/global_search_bar/README.md create mode 100644 x-pack/plugins/global_search_bar/kibana.json create mode 100644 x-pack/plugins/global_search_bar/public/components/__snapshots__/search_bar.test.tsx.snap create mode 100644 x-pack/plugins/global_search_bar/public/components/search_bar.test.tsx create mode 100644 x-pack/plugins/global_search_bar/public/components/search_bar.tsx create mode 100644 x-pack/plugins/global_search_bar/public/index.ts create mode 100644 x-pack/plugins/global_search_bar/public/plugin.tsx create mode 100644 x-pack/test/plugin_functional/test_suites/global_search/global_search_bar.ts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000000000..03a4f9520c2ba --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,304 @@ +# GitHub CODEOWNERS definition +# Identify which groups will be pinged by changes to different parts of the codebase. +# For more info, see https://help.github.com/articles/about-codeowners/ + +# App +/x-pack/plugins/dashboard_enhanced/ @elastic/kibana-app +/x-pack/plugins/discover_enhanced/ @elastic/kibana-app +/x-pack/plugins/lens/ @elastic/kibana-app +/x-pack/plugins/graph/ @elastic/kibana-app +/src/plugins/dashboard/ @elastic/kibana-app +/src/plugins/discover/ @elastic/kibana-app +/src/plugins/input_control_vis/ @elastic/kibana-app +/src/plugins/kibana_legacy/ @elastic/kibana-app +/src/plugins/vis_default_editor/ @elastic/kibana-app +/src/plugins/vis_type_markdown/ @elastic/kibana-app +/src/plugins/vis_type_metric/ @elastic/kibana-app +/src/plugins/vis_type_table/ @elastic/kibana-app +/src/plugins/vis_type_tagcloud/ @elastic/kibana-app +/src/plugins/vis_type_timelion/ @elastic/kibana-app +/src/plugins/vis_type_timeseries/ @elastic/kibana-app +/src/plugins/vis_type_vega/ @elastic/kibana-app +/src/plugins/vis_type_vislib/ @elastic/kibana-app +/src/plugins/vis_type_xy/ @elastic/kibana-app +/src/plugins/visualize/ @elastic/kibana-app + +# App Architecture +/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/ui_actions_explorer/ @elastic/kibana-app-arch +/examples/url_generators_examples/ @elastic/kibana-app-arch +/examples/url_generators_explorer/ @elastic/kibana-app-arch +/packages/elastic-datemath/ @elastic/kibana-app-arch +/packages/kbn-interpreter/ @elastic/kibana-app-arch +/src/plugins/advanced_settings/ @elastic/kibana-app-arch +/src/plugins/bfetch/ @elastic/kibana-app-arch +/src/plugins/data/ @elastic/kibana-app-arch +/src/plugins/embeddable/ @elastic/kibana-app-arch +/src/plugins/expressions/ @elastic/kibana-app-arch +/src/plugins/inspector/ @elastic/kibana-app-arch +/src/plugins/kibana_react/ @elastic/kibana-app-arch +/src/plugins/kibana_react/public/code_editor @elastic/kibana-canvas +/src/plugins/kibana_utils/ @elastic/kibana-app-arch +/src/plugins/management/ @elastic/kibana-app-arch +/src/plugins/navigation/ @elastic/kibana-app-arch +/src/plugins/share/ @elastic/kibana-app-arch +/src/plugins/ui_actions/ @elastic/kibana-app-arch +/src/plugins/visualizations/ @elastic/kibana-app-arch +/x-pack/examples/ui_actions_enhanced_examples/ @elastic/kibana-app-arch +/x-pack/plugins/data_enhanced/ @elastic/kibana-app-arch +/x-pack/plugins/embeddable_enhanced/ @elastic/kibana-app-arch +/x-pack/plugins/ui_actions_enhanced/ @elastic/kibana-app-arch + +# APM +/x-pack/plugins/apm/ @elastic/apm-ui +/x-pack/test/functional/apps/apm/ @elastic/apm-ui +/src/legacy/core_plugins/apm_oss/ @elastic/apm-ui +/src/plugins/apm_oss/ @elastic/apm-ui +/src/apm.js @watson @vigneshshanmugam + +# Client Side Monitoring (lives in APM directories but owned by Uptime) +/x-pack/plugins/apm/e2e/cypress/support/step_definitions/rum @elastic/uptime +/x-pack/plugins/apm/public/application/csmApp.tsx @elastic/uptime +/x-pack/plugins/apm/public/components/app/RumDashboard @elastic/uptime +/x-pack/plugins/apm/server/lib/rum_client @elastic/uptime +/x-pack/plugins/apm/server/routes/rum_client.ts @elastic/uptime + +# Beats +/x-pack/legacy/plugins/beats_management/ @elastic/beats + +# Canvas +/x-pack/plugins/canvas/ @elastic/kibana-canvas +/x-pack/test/functional/apps/canvas/ @elastic/kibana-canvas + +# Core UI +# Exclude tutorials folder for now because they are not owned by Kibana app and most will move out soon +/src/plugins/home/public @elastic/kibana-core-ui +/src/plugins/home/server/*.ts @elastic/kibana-core-ui +/src/plugins/home/server/services/ @elastic/kibana-core-ui +# Exclude tutorial resources folder for now because they are not owned by Kibana app and most will move out soon +/src/legacy/core_plugins/kibana/public/home/*.ts @elastic/kibana-core-ui +/src/legacy/core_plugins/kibana/public/home/np_ready/ @elastic/kibana-core-ui +/x-pack/plugins/global_search_bar/ @elastic/kibana-core-ui + +# Observability UIs +/x-pack/legacy/plugins/infra/ @elastic/logs-metrics-ui +/x-pack/plugins/infra/ @elastic/logs-metrics-ui +/x-pack/plugins/ingest_manager/ @elastic/ingest-management +/x-pack/legacy/plugins/ingest_manager/ @elastic/ingest-management +/x-pack/plugins/observability/ @elastic/observability-ui +/x-pack/plugins/monitoring/ @elastic/stack-monitoring-ui +/x-pack/plugins/uptime @elastic/uptime + +# Machine Learning +/x-pack/legacy/plugins/ml/ @elastic/ml-ui +/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 +# 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 + +# Maps +/x-pack/legacy/plugins/maps/ @elastic/kibana-gis +/x-pack/plugins/maps/ @elastic/kibana-gis +/x-pack/test/api_integration/apis/maps/ @elastic/kibana-gis +/x-pack/test/functional/apps/maps/ @elastic/kibana-gis +/x-pack/test/functional/es_archives/maps/ @elastic/kibana-gis +/x-pack/test/visual_regression/tests/maps/index.js @elastic/kibana-gis + +# Operations +/src/dev/ @elastic/kibana-operations +/src/setup_node_env/ @elastic/kibana-operations +/src/optimize/ @elastic/kibana-operations +/packages/*eslint*/ @elastic/kibana-operations +/packages/*babel*/ @elastic/kibana-operations +/packages/kbn-dev-utils*/ @elastic/kibana-operations +/packages/kbn-es/ @elastic/kibana-operations +/packages/kbn-optimizer/ @elastic/kibana-operations +/packages/kbn-pm/ @elastic/kibana-operations +/packages/kbn-test/ @elastic/kibana-operations +/packages/kbn-ui-shared-deps/ @elastic/kibana-operations +/packages/kbn-es-archiver/ @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 + +# Quality Assurance +/src/dev/code_coverage @elastic/kibana-qa +/vars/*Coverage.groovy @elastic/kibana-qa +/test/functional/services/common @elastic/kibana-qa +/test/functional/services/lib @elastic/kibana-qa +/test/functional/services/remote @elastic/kibana-qa + +# Platform +/src/core/ @elastic/kibana-platform +/config/kibana.yml @elastic/kibana-platform +/x-pack/plugins/features/ @elastic/kibana-platform +/x-pack/plugins/licensing/ @elastic/kibana-platform +/x-pack/plugins/global_search/ @elastic/kibana-platform +/x-pack/plugins/cloud/ @elastic/kibana-platform +/x-pack/test/saved_objects_field_count/ @elastic/kibana-platform +/packages/kbn-config-schema/ @elastic/kibana-platform +/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 + +# Security +/src/core/server/csp/ @elastic/kibana-security @elastic/kibana-platform +/x-pack/legacy/plugins/security/ @elastic/kibana-security +/x-pack/legacy/plugins/spaces/ @elastic/kibana-security +/x-pack/plugins/spaces/ @elastic/kibana-security +/x-pack/plugins/encrypted_saved_objects/ @elastic/kibana-security +/x-pack/plugins/security/ @elastic/kibana-security +/x-pack/test/api_integration/apis/security/ @elastic/kibana-security +/x-pack/test/ui_capabilities/ @elastic/kibana-security +/x-pack/test/encrypted_saved_objects_api_integration/ @elastic/kibana-security +/x-pack/test/functional/apps/security/ @elastic/kibana-security +/x-pack/test/kerberos_api_integration/ @elastic/kibana-security +/x-pack/test/login_selector_api_integration/ @elastic/kibana-security +/x-pack/test/oidc_api_integration/ @elastic/kibana-security +/x-pack/test/pki_api_integration/ @elastic/kibana-security +/x-pack/test/saml_api_integration/ @elastic/kibana-security +/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 + +# 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 + +# Kibana Telemetry +/packages/kbn-analytics/ @elastic/kibana-telemetry +/packages/kbn-telemetry-tools/ @elastic/kibana-telemetry +/src/plugins/kibana_usage_collection/ @elastic/kibana-telemetry +/src/plugins/newsfeed/ @elastic/kibana-telemetry +/src/plugins/telemetry/ @elastic/kibana-telemetry +/src/plugins/telemetry_collection_manager/ @elastic/kibana-telemetry +/src/plugins/telemetry_management_section/ @elastic/kibana-telemetry +/src/plugins/usage_collection/ @elastic/kibana-telemetry +/x-pack/plugins/telemetry_collection_xpack/ @elastic/kibana-telemetry +/.telemetryrc.json @elastic/kibana-telemetry +/x-pack/.telemetryrc.json @elastic/kibana-telemetry +src/plugins/telemetry/schema/legacy_oss_plugins.json @elastic/kibana-telemetry +src/plugins/telemetry/schema/oss_plugins.json @elastic/kibana-telemetry +x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @elastic/kibana-telemetry + +# Kibana Alerting Services +/x-pack/plugins/alerts/ @elastic/kibana-alerting-services +/x-pack/plugins/actions/ @elastic/kibana-alerting-services +/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 + +# Enterprise Search +# Shared +/x-pack/plugins/enterprise_search/ @elastic/enterprise-search-frontend +/x-pack/test/functional_enterprise_search/ @elastic/enterprise-search-frontend +# App Search +/x-pack/plugins/enterprise_search/public/applications/app_search @elastic/app-search-frontend +/x-pack/plugins/enterprise_search/server/routes/app_search @elastic/app-search-frontend +/x-pack/plugins/enterprise_search/server/collectors/app_search @elastic/app-search-frontend +/x-pack/plugins/enterprise_search/server/saved_objects/app_search @elastic/app-search-frontend +# Workplace Search +/x-pack/plugins/enterprise_search/public/applications/workplace_search @elastic/workplace-search-frontend +/x-pack/plugins/enterprise_search/server/routes/workplace_search @elastic/workplace-search-frontend +/x-pack/plugins/enterprise_search/server/collectors/workplace_search @elastic/workplace-search-frontend +/x-pack/plugins/enterprise_search/server/saved_objects/workplace_search @elastic/workplace-search-frontend + +# 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/legacy/plugins/cross_cluster_replication/ @elastic/es-ui +/x-pack/plugins/index_lifecycle_management/ @elastic/es-ui +/x-pack/legacy/plugins/index_management/ @elastic/es-ui +/x-pack/legacy/plugins/license_management/ @elastic/es-ui +/x-pack/legacy/plugins/rollup/ @elastic/es-ui +/x-pack/legacy/plugins/snapshot_restore/ @elastic/es-ui +/x-pack/legacy/plugins/upgrade_assistant/ @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 +/x-pack/plugins/painless_lab/ @elastic/es-ui +/x-pack/plugins/remote_clusters/ @elastic/es-ui +/x-pack/plugins/rollup/ @elastic/es-ui +/x-pack/plugins/searchprofiler/ @elastic/es-ui +/x-pack/plugins/snapshot_restore/ @elastic/es-ui +/x-pack/plugins/upgrade_assistant/ @elastic/es-ui +/x-pack/plugins/watcher/ @elastic/es-ui +/x-pack/plugins/ingest_pipelines/ @elastic/es-ui + +# 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 +/x-pack/test/plugin_functional/plugins/resolver_test/ @elastic/endpoint-app-team @elastic/siem +/x-pack/test/plugin_functional/test_suites/resolver/ @elastic/endpoint-app-team @elastic/siem + +# Security Solution +/x-pack/plugins/security_solution/ @elastic/siem @elastic/endpoint-app-team +/x-pack/test/detection_engine_api_integration @elastic/siem @elastic/endpoint-app-team +/x-pack/test/lists_api_integration @elastic/siem @elastic/endpoint-app-team +/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 + +# Security Intelligence And Analytics +/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules @elastic/security-intelligence-analytics + +# Design (at the bottom for specificity of SASS files) +**/*.scss @elastic/kibana-design + +# Core design +/src/plugins/dashboard/**/*.scss @elastic/kibana-core-ui-designers +/x-pack/plugins/canvas/**/*.scss @elastic/kibana-core-ui-designers +/src/legacy/core_plugins/kibana/public/home/**/*.scss @elastic/kibana-core-ui-designers +/x-pack/legacy/plugins/security/**/*.scss @elastic/kibana-core-ui-designers +/x-pack/legacy/plugins/spaces/**/*.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 +/x-pack/plugins/ingest_manager/**/*.scss @elastic/observability-design +/x-pack/plugins/observability/**/*.scss @elastic/observability-design + +# Ent. Search design +/x-pack/plugins/enterprise_search/**/*.scss @elastic/ent-search-design + +# Security design +/x-pack/plugins/endpoint/**/*.scss @elastic/security-design +/x-pack/plugins/security_solution/**/*.scss @elastic/security-design + diff --git a/docs/developer/architecture/code-exploration.asciidoc b/docs/developer/architecture/code-exploration.asciidoc new file mode 100644 index 0000000000000..4a390336da34f --- /dev/null +++ b/docs/developer/architecture/code-exploration.asciidoc @@ -0,0 +1,598 @@ +//// + +NOTE: + This is an automatically generated file. Please do not edit directly. Instead, run the + following from within the kibana repository: + + node scripts/build_plugin_list_docs + + You can update the template within packages/kbn-dev-utils/target/plugin_list/generate_plugin_list.js + +//// + +[[code-exploration]] +== Exploring Kibana code + +The goals of our folder heirarchy are: + +- Easy for developers to know where to add new services, plugins and applications. +- Easy for developers to know where to find the code from services, plugins and applications. +- Easy to browse and understand our folder structure. + +To that aim, we strive to: + +- Avoid too many files in any given folder. +- Choose clear, unambigious folder names. +- Organize by domain. +- Every folder should contain a README that describes the contents of that folder. + +[discrete] +[[kibana-services-applications]] +=== Services and Applications + +[discrete] +==== src/plugins + +- {kib-repo}blob/{branch}/src/plugins/advanced_settings[advancedSettings] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/src/plugins/apm_oss[apmOss] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/src/plugins/bfetch/README.md[bfetch] + +bfetch allows to batch HTTP requests and streams responses back. + + +- {kib-repo}blob/{branch}/src/plugins/charts/README.md[charts] + +The Charts plugin is a way to create easier integration of shared colors, themes, types and other utilities across all Kibana charts and visualizations. + + +- {kib-repo}blob/{branch}/src/plugins/console[console] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/src/plugins/dashboard/README.md[dashboard] + +Contains the dashboard application. + + +- {kib-repo}blob/{branch}/src/plugins/data/README.md[data] + +data plugin provides common data access services. + + +- {kib-repo}blob/{branch}/src/plugins/dev_tools/README.md[devTools] + +The ui/registry/dev_tools is removed in favor of the devTools plugin which exposes a register method in the setup contract. +Registering app works mostly the same as registering apps in core.application.register. +Routing will be handled by the id of the dev tool - your dev tool will be mounted when the URL matches /app/dev_tools#/. +This API doesn't support angular, for registering angular dev tools, bootstrap a local module on mount into the given HTML element. + + +- {kib-repo}blob/{branch}/src/plugins/discover/README.md[discover] + +Contains the Discover application and the saved search embeddable. + + +- {kib-repo}blob/{branch}/src/plugins/embeddable/README.md[embeddable] + +Embeddables are re-usable widgets that can be rendered in any environment or plugin. Developers can embed them directly in their plugin. End users can dynamically add them to any embeddable containers. + + +- {kib-repo}blob/{branch}/src/plugins/es_ui_shared/README.md[esUiShared] + +This plugin contains reusable code in the form of self-contained modules (or libraries). Each of these modules exports a set of functionality relevant to the domain of the module. + + +- {kib-repo}blob/{branch}/src/plugins/expressions/README.md[expressions] + +This plugin provides methods which will parse & execute an expression pipeline +string for you, as well as a series of registries for advanced users who might +want to incorporate their own functions, types, and renderers into the service +for use in their own application. + + +- {kib-repo}blob/{branch}/src/plugins/home/README.md[home] + +Moves the legacy ui/registry/feature_catalogue module for registering "features" that should be shown in the home page's feature catalogue to a service within a "home" plugin. The feature catalogue refered to here should not be confused with the "feature" plugin for registering features used to derive UI capabilities for feature controls. + + +- {kib-repo}blob/{branch}/src/plugins/index_pattern_management[indexPatternManagement] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/src/plugins/input_control_vis/README.md[inputControlVis] + +Contains the input control visualization allowing to place custom filter controls on a dashboard. + + +- {kib-repo}blob/{branch}/src/plugins/inspector/README.md[inspector] + +The inspector is a contextual tool to gain insights into different elements +in Kibana, e.g. visualizations. It has the form of a flyout panel. + + +- {kib-repo}blob/{branch}/src/plugins/kibana_legacy/README.md[kibanaLegacy] + +This plugin will contain several helpers and services to integrate pieces of the legacy Kibana app with the new Kibana platform. + + +- {kib-repo}blob/{branch}/src/plugins/kibana_react/README.md[kibanaReact] + +Tools for building React applications in Kibana. + + +- {kib-repo}blob/{branch}/src/plugins/kibana_usage_collection/README.md[kibanaUsageCollection] + +This plugin registers the basic usage collectors from Kibana: + + +- {kib-repo}blob/{branch}/src/plugins/kibana_utils/README.md[kibanaUtils] + +Utilities for building Kibana plugins. + + +- {kib-repo}blob/{branch}/src/plugins/legacy_export[legacyExport] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/src/plugins/management[management] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/src/plugins/maps_legacy[mapsLegacy] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/src/plugins/navigation/README.md[navigation] + +The navigation plugins exports the TopNavMenu component. +It also provides a stateful version of it on the start contract. + + +- {kib-repo}blob/{branch}/src/plugins/newsfeed[newsfeed] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/src/plugins/region_map[regionMap] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/src/plugins/saved_objects[savedObjects] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/src/plugins/saved_objects_management[savedObjectsManagement] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/src/plugins/share/README.md[share] + +Replaces the legacy ui/share module for registering share context menus. + + +- {kib-repo}blob/{branch}/src/plugins/telemetry/README.md[telemetry] + +Telemetry allows Kibana features to have usage tracked in the wild. The general term "telemetry" refers to multiple things: + + +- {kib-repo}blob/{branch}/src/plugins/telemetry_collection_manager/README.md[telemetryCollectionManager] + +Telemetry's collection manager to go through all the telemetry sources when fetching it before reporting. + + +- {kib-repo}blob/{branch}/src/plugins/telemetry_management_section/README.md[telemetryManagementSection] + +This plugin adds the Advanced Settings section for the Usage Data collection (aka Telemetry). + + +- {kib-repo}blob/{branch}/src/plugins/tile_map[tileMap] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/src/plugins/timelion/README.md[timelion] + +Contains the deprecated timelion application. For the timelion visualization, +which also contains the timelion APIs and backend, look at the vis_type_timelion plugin. + + +- {kib-repo}blob/{branch}/src/plugins/ui_actions/README.md[uiActions] + +An API for: + + +- {kib-repo}blob/{branch}/src/plugins/usage_collection/README.md[usageCollection] + +Usage Collection allows collecting usage data for other services to consume (telemetry and monitoring). +To integrate with the telemetry services for usage collection of your feature, there are 2 steps: + + +- {kib-repo}blob/{branch}/src/plugins/vis_type_markdown/README.md[visTypeMarkdown] + +The markdown visualization that can be used to place text panels on dashboards. + + +- {kib-repo}blob/{branch}/src/plugins/vis_type_metric/README.md[visTypeMetric] + +Contains the metric visualization. + + +- {kib-repo}blob/{branch}/src/plugins/vis_type_table/README.md[visTypeTable] + +Contains the data table visualization, that allows presenting data in a simple table format. + + +- {kib-repo}blob/{branch}/src/plugins/vis_type_tagcloud/README.md[visTypeTagcloud] + +Contains the tagcloud visualization. + + +- {kib-repo}blob/{branch}/src/plugins/vis_type_timelion/README.md[visTypeTimelion] + +Contains the timelion visualization and the timelion backend. + + +- {kib-repo}blob/{branch}/src/plugins/vis_type_timeseries/README.md[visTypeTimeseries] + +Contains everything around TSVB (the editor, visualizatin implementations and backends). + + +- {kib-repo}blob/{branch}/src/plugins/vis_type_vega/README.md[visTypeVega] + +Contains the Vega visualization. + + +- {kib-repo}blob/{branch}/src/plugins/vis_type_vislib/README.md[visTypeVislib] + +Contains the vislib visualizations. These are the classical area/line/bar, pie, gauge/goal and +heatmap charts. + + +- {kib-repo}blob/{branch}/src/plugins/vis_type_xy/README.md[visTypeXy] + +Contains the new xy-axis chart using the elastic-charts library, which will eventually +replace the vislib xy-axis (bar, area, line) charts. + + +- {kib-repo}blob/{branch}/src/plugins/visualizations/README.md[visualizations] + +Contains most of the visualization infrastructure, e.g. the visualization type registry or the +visualization embeddable. + + +- {kib-repo}blob/{branch}/src/plugins/visualize/README.md[visualize] + +Contains the visualize application which includes the listing page and the app frame, +which will load the visualization's editor. + + +[discrete] +==== x-pack/plugins + +- {kib-repo}blob/{branch}/x-pack/plugins/actions/README.md[actions] + +The Kibana actions plugin provides a framework to create executable actions. You can: + + +- {kib-repo}blob/{branch}/x-pack/plugins/alerting_builtins/README.md[alertingBuiltins] + +This plugin provides alertTypes shipped with Kibana for use with the +the alerts plugin. When enabled, it will register +the built-in alertTypes with the alerting plugin, register associated HTTP +routes, etc. + + +- {kib-repo}blob/{branch}/x-pack/plugins/alerts/README.md[alerts] + +The Kibana alerting plugin provides a common place to set up alerts. You can: + + +- {kib-repo}blob/{branch}/x-pack/plugins/apm/readme.md[apm] + +To access an elasticsearch instance that has live data you have two options: + + +- {kib-repo}blob/{branch}/x-pack/plugins/audit_trail[auditTrail] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/x-pack/plugins/beats_management/readme.md[beatsManagement] + +Notes: +Failure to have auth enabled in Kibana will make for a broken UI. UI-based errors not yet in place + + +- {kib-repo}blob/{branch}/x-pack/plugins/canvas/README.md[canvas] + +"Never look back. The past is done. The future is a blank canvas." ― Suzy Kassem, Rise Up and Salute the Sun + + +- {kib-repo}blob/{branch}/x-pack/plugins/case/README.md[case] + +Experimental Feature + + +- {kib-repo}blob/{branch}/x-pack/plugins/cloud[cloud] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/x-pack/plugins/code[code] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/x-pack/plugins/console_extensions[consoleExtensions] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/x-pack/plugins/cross_cluster_replication/README.md[crossClusterReplication] + +You can run a local cluster and simulate a remote cluster within a single Kibana directory. + + +- {kib-repo}blob/{branch}/x-pack/plugins/dashboard_enhanced/README.md[dashboardEnhanced] + +Contains the enhancements to the OSS dashboard app. + + +- {kib-repo}blob/{branch}/x-pack/plugins/dashboard_mode/README.md[dashboardMode] + +The deprecated dashboard only mode. + + +- {kib-repo}blob/{branch}/x-pack/plugins/data_enhanced[dataEnhanced] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/x-pack/plugins/discover_enhanced/README.md[discoverEnhanced] + +Contains the enhancements to the OSS discover app. + + +- {kib-repo}blob/{branch}/x-pack/plugins/embeddable_enhanced[embeddableEnhanced] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/x-pack/plugins/encrypted_saved_objects/README.md[encryptedSavedObjects] + +The purpose of this plugin is to provide a way to encrypt/decrypt attributes on the custom Saved Objects that works with +security and spaces filtering as well as performing audit logging. + + +- {kib-repo}blob/{branch}/x-pack/plugins/enterprise_search/README.md[enterpriseSearch] + +This plugin's goal is to provide a Kibana user interface to the Enterprise Search solution's products (App Search and Workplace Search). In it's current MVP state, the plugin provides the following with the goal of gathering user feedback and raising product awareness: + + +- {kib-repo}blob/{branch}/x-pack/plugins/event_log/README.md[eventLog] + +The purpose of this plugin is to provide a way to persist a history of events +occuring in Kibana, initially just for the Make It Action project - alerts +and actions. + + +- {kib-repo}blob/{branch}/x-pack/plugins/features[features] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/x-pack/plugins/file_upload[fileUpload] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/x-pack/plugins/global_search/README.md[globalSearch] + +The GlobalSearch plugin provides an easy way to search for various objects, such as applications +or dashboards from the Kibana instance, from both server and client-side plugins + + +- {kib-repo}blob/{branch}/x-pack/plugins/global_search_bar/README.md[globalSearchBar] + +The GlobalSearchBar plugin provides a search interface for navigating Kibana. (It is the UI to the GlobalSearch plugin.) + + +- {kib-repo}blob/{branch}/x-pack/plugins/global_search_providers[globalSearchProviders] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/x-pack/plugins/graph/README.md[graph] + +This is the main source folder of the Graph plugin. It contains all of the Kibana server and client source code. x-pack/test/functional/apps/graph contains additional functional tests. + + +- {kib-repo}blob/{branch}/x-pack/plugins/grokdebugger/README.md[grokdebugger] + +- {kib-repo}blob/{branch}/x-pack/plugins/index_lifecycle_management/README.md[indexLifecycleManagement] + +You can test that the Frozen badge, phase filtering, and lifecycle information is surfaced in +Index Management by running this series of requests in Console: + + +- {kib-repo}blob/{branch}/x-pack/plugins/index_management[indexManagement] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/x-pack/plugins/infra/README.md[infra] + +This is the home of the infra plugin, which aims to provide a solution for +the infrastructure monitoring use-case within Kibana. + + +- {kib-repo}blob/{branch}/x-pack/plugins/ingest_manager/README.md[ingestManager] + +Fleet needs to have Elasticsearch API keys enabled, and also to have TLS enabled on kibana, (if you want to run Kibana without TLS you can provide the following config flag --xpack.ingestManager.fleet.tlsCheckDisabled=false) + + +- {kib-repo}blob/{branch}/x-pack/plugins/ingest_pipelines/README.md[ingestPipelines] + +The ingest_pipelines plugin provides Kibana support for Elasticsearch's ingest nodes. Please refer to the Elasticsearch documentation for more details. + + +- {kib-repo}blob/{branch}/x-pack/plugins/lens/readme.md[lens] + +Run all tests from the x-pack root directory + + +- {kib-repo}blob/{branch}/x-pack/plugins/license_management[licenseManagement] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/x-pack/plugins/licensing/README.md[licensing] + +The licensing plugin retrieves license data from Elasticsearch at regular configurable intervals. + + +- {kib-repo}blob/{branch}/x-pack/plugins/lists/README.md[lists] + +README.md for developers working on the backend lists on how to get started +using the CURL scripts in the scripts folder. + + +- {kib-repo}blob/{branch}/x-pack/plugins/logstash[logstash] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/x-pack/plugins/maps/README.md[maps] + +Visualize geo data from Elasticsearch or 3rd party geo-services. + + +- {kib-repo}blob/{branch}/x-pack/plugins/maps_legacy_licensing/README.md[mapsLegacyLicensing] + +This plugin provides access to the detailed tile map services from Elastic. + + +- {kib-repo}blob/{branch}/x-pack/plugins/ml[ml] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/x-pack/plugins/monitoring[monitoring] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/x-pack/plugins/observability/README.md[observability] + +This plugin provides shared components and services for use across observability solutions, as well as the observability landing page UI. + + +- {kib-repo}blob/{branch}/x-pack/plugins/oss_telemetry[ossTelemetry] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/x-pack/plugins/painless_lab[painlessLab] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/x-pack/plugins/remote_clusters[remoteClusters] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/x-pack/plugins/reporting/README.md[reporting] + +An awesome Kibana reporting plugin + + +- {kib-repo}blob/{branch}/x-pack/plugins/rollup/README.md[rollup] + +Welcome to the Kibana rollup plugin! This plugin provides Kibana support for Elasticsearch's rollup feature. Please refer to the Elasticsearch documentation to understand rollup indices and how to create rollup jobs. + + +- {kib-repo}blob/{branch}/x-pack/plugins/searchprofiler[searchprofiler] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/x-pack/plugins/security/README.md[security] + +See Configuring security in Kibana. + + +- {kib-repo}blob/{branch}/x-pack/plugins/security_solution/README.md[securitySolution] + +Welcome to the Kibana Security Solution plugin! This README will go over getting started with development and testing. + + +- {kib-repo}blob/{branch}/x-pack/plugins/snapshot_restore/README.md[snapshotRestore] + +or + + +- {kib-repo}blob/{branch}/x-pack/plugins/spaces[spaces] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/x-pack/plugins/task_manager[taskManager] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/x-pack/plugins/telemetry_collection_xpack/README.md[telemetryCollectionXpack] + +Gathers all usage collection, retrieving them from both: OSS and X-Pack plugins. + + +- {kib-repo}blob/{branch}/x-pack/plugins/transform[transform] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/x-pack/plugins/translations[translations] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/x-pack/plugins/triggers_actions_ui/README.md[triggers_actions_ui] + +The Kibana alerts and actions UI plugin provides a user interface for managing alerts and actions. +As a developer you can reuse and extend built-in alerts and actions UI functionality: + + +- {kib-repo}blob/{branch}/x-pack/plugins/ui_actions_enhanced/README.md[uiActionsEnhanced] + +- {kib-repo}blob/{branch}/x-pack/plugins/upgrade_assistant[upgradeAssistant] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/x-pack/plugins/uptime/README.md[uptime] + +The purpose of this plugin is to provide users of Heartbeat more visibility of what's happening +in their infrastructure. + + +- {kib-repo}blob/{branch}/x-pack/plugins/watcher/README.md[watcher] + +This plugins adopts some conventions in addition to or in place of conventions in Kibana (at the time of the plugin's creation): + diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index abab2de6ea8e4..cec8de9990b1f 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -341,6 +341,10 @@ and actions. or dashboards from the Kibana instance, from both server and client-side plugins +|{kib-repo}blob/{branch}/x-pack/plugins/global_search_bar/README.md[globalSearchBar] +|The GlobalSearchBar plugin provides a search interface for navigating Kibana. (It is the UI to the GlobalSearch plugin.) + + |{kib-repo}blob/{branch}/x-pack/plugins/global_search_providers[globalSearchProviders] |WARNING: Missing README. diff --git a/docs/development/core/public/kibana-plugin-core-public.chromenavcontrols.md b/docs/development/core/public/kibana-plugin-core-public.chromenavcontrols.md index bca69adeef66b..47365782599ed 100644 --- a/docs/development/core/public/kibana-plugin-core-public.chromenavcontrols.md +++ b/docs/development/core/public/kibana-plugin-core-public.chromenavcontrols.md @@ -30,6 +30,7 @@ chrome.navControls.registerLeft({ | Method | Description | | --- | --- | -| [registerLeft(navControl)](./kibana-plugin-core-public.chromenavcontrols.registerleft.md) | Register a nav control to be presented on the left side of the chrome header. | -| [registerRight(navControl)](./kibana-plugin-core-public.chromenavcontrols.registerright.md) | Register a nav control to be presented on the right side of the chrome header. | +| [registerCenter(navControl)](./kibana-plugin-core-public.chromenavcontrols.registercenter.md) | Register a nav control to be presented on the top-center side of the chrome header. | +| [registerLeft(navControl)](./kibana-plugin-core-public.chromenavcontrols.registerleft.md) | Register a nav control to be presented on the bottom-left side of the chrome header. | +| [registerRight(navControl)](./kibana-plugin-core-public.chromenavcontrols.registerright.md) | Register a nav control to be presented on the top-right side of the chrome header. | diff --git a/docs/development/core/public/kibana-plugin-core-public.chromenavcontrols.registercenter.md b/docs/development/core/public/kibana-plugin-core-public.chromenavcontrols.registercenter.md new file mode 100644 index 0000000000000..2f921050e58dd --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.chromenavcontrols.registercenter.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ChromeNavControls](./kibana-plugin-core-public.chromenavcontrols.md) > [registerCenter](./kibana-plugin-core-public.chromenavcontrols.registercenter.md) + +## ChromeNavControls.registerCenter() method + +Register a nav control to be presented on the top-center side of the chrome header. + +Signature: + +```typescript +registerCenter(navControl: ChromeNavControl): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| navControl | ChromeNavControl | | + +Returns: + +`void` + diff --git a/docs/development/core/public/kibana-plugin-core-public.chromenavcontrols.registerleft.md b/docs/development/core/public/kibana-plugin-core-public.chromenavcontrols.registerleft.md index c5c78bf9fb1da..514c44bd9d710 100644 --- a/docs/development/core/public/kibana-plugin-core-public.chromenavcontrols.registerleft.md +++ b/docs/development/core/public/kibana-plugin-core-public.chromenavcontrols.registerleft.md @@ -4,7 +4,7 @@ ## ChromeNavControls.registerLeft() method -Register a nav control to be presented on the left side of the chrome header. +Register a nav control to be presented on the bottom-left side of the chrome header. Signature: diff --git a/docs/development/core/public/kibana-plugin-core-public.chromenavcontrols.registerright.md b/docs/development/core/public/kibana-plugin-core-public.chromenavcontrols.registerright.md index 12058f1d16ab9..eb56e0e38c6c9 100644 --- a/docs/development/core/public/kibana-plugin-core-public.chromenavcontrols.registerright.md +++ b/docs/development/core/public/kibana-plugin-core-public.chromenavcontrols.registerright.md @@ -4,7 +4,7 @@ ## ChromeNavControls.registerRight() method -Register a nav control to be presented on the right side of the chrome header. +Register a nav control to be presented on the top-right side of the chrome header. Signature: diff --git a/docs/development/core/public/kibana-plugin-core-public.chromestart.getnavtype_.md b/docs/development/core/public/kibana-plugin-core-public.chromestart.getnavtype_.md deleted file mode 100644 index 09864be43996d..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.chromestart.getnavtype_.md +++ /dev/null @@ -1,17 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ChromeStart](./kibana-plugin-core-public.chromestart.md) > [getNavType$](./kibana-plugin-core-public.chromestart.getnavtype_.md) - -## ChromeStart.getNavType$() method - -Get the navigation type TODO \#64541 Can delete - -Signature: - -```typescript -getNavType$(): Observable; -``` -Returns: - -`Observable` - diff --git a/docs/development/core/public/kibana-plugin-core-public.chromestart.md b/docs/development/core/public/kibana-plugin-core-public.chromestart.md index e983ad50d2afe..2594848ef0847 100644 --- a/docs/development/core/public/kibana-plugin-core-public.chromestart.md +++ b/docs/development/core/public/kibana-plugin-core-public.chromestart.md @@ -59,7 +59,6 @@ core.chrome.setHelpExtension(elem => { | [getHelpExtension$()](./kibana-plugin-core-public.chromestart.gethelpextension_.md) | Get an observable of the current custom help conttent | | [getIsNavDrawerLocked$()](./kibana-plugin-core-public.chromestart.getisnavdrawerlocked_.md) | Get an observable of the current locked state of the nav drawer. | | [getIsVisible$()](./kibana-plugin-core-public.chromestart.getisvisible_.md) | Get an observable of the current visibility state of the chrome. | -| [getNavType$()](./kibana-plugin-core-public.chromestart.getnavtype_.md) | Get the navigation type TODO \#64541 Can delete | | [removeApplicationClass(className)](./kibana-plugin-core-public.chromestart.removeapplicationclass.md) | Remove a className added with addApplicationClass(). If className is unknown it is ignored. | | [setAppTitle(appTitle)](./kibana-plugin-core-public.chromestart.setapptitle.md) | Sets the current app's title | | [setBadge(badge)](./kibana-plugin-core-public.chromestart.setbadge.md) | Override the current badge | diff --git a/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts b/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts index e1d3bf1a8d901..701171876ad2c 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts @@ -262,7 +262,7 @@ export const schema = Joi.object() // settings for the find service layout: Joi.object() .keys({ - fixedHeaderHeight: Joi.number().default(50), + fixedHeaderHeight: Joi.number().default(100), }) .default(), diff --git a/src/core/public/_variables.scss b/src/core/public/_variables.scss new file mode 100644 index 0000000000000..8c054e770bd4b --- /dev/null +++ b/src/core/public/_variables.scss @@ -0,0 +1,3 @@ +@import '@elastic/eui/src/global_styling/variables/header'; + +$kbnHeaderOffset: $euiHeaderHeightCompensation * 2; diff --git a/src/core/public/application/ui/app_container.tsx b/src/core/public/application/ui/app_container.tsx index f668cf851da55..089d1cf3f3ced 100644 --- a/src/core/public/application/ui/app_container.tsx +++ b/src/core/public/application/ui/app_container.tsx @@ -94,8 +94,10 @@ export const AppContainer: FunctionComponent = ({ // eslint-disable-next-line no-console console.error(e); } finally { - setShowSpinner(false); - setIsMounting(false); + if (elementRef.current) { + setShowSpinner(false); + setIsMounting(false); + } } }; diff --git a/src/core/public/chrome/chrome_service.mock.ts b/src/core/public/chrome/chrome_service.mock.ts index 5862ee7175f71..0ae8b132f1d86 100644 --- a/src/core/public/chrome/chrome_service.mock.ts +++ b/src/core/public/chrome/chrome_service.mock.ts @@ -17,14 +17,7 @@ * under the License. */ import { BehaviorSubject } from 'rxjs'; -import { - ChromeBadge, - ChromeBrand, - ChromeBreadcrumb, - ChromeService, - InternalChromeStart, - NavType, -} from './'; +import { ChromeBadge, ChromeBrand, ChromeBreadcrumb, ChromeService, InternalChromeStart } from './'; const createStartContractMock = () => { const startContract: DeeplyMockedKeys = { @@ -50,8 +43,10 @@ const createStartContractMock = () => { }, navControls: { registerLeft: jest.fn(), + registerCenter: jest.fn(), registerRight: jest.fn(), getLeft$: jest.fn(), + getCenter$: jest.fn(), getRight$: jest.fn(), }, setAppTitle: jest.fn(), @@ -70,7 +65,6 @@ const createStartContractMock = () => { setHelpExtension: jest.fn(), setHelpSupportUrl: jest.fn(), getIsNavDrawerLocked$: jest.fn(), - getNavType$: jest.fn(), getCustomNavLink$: jest.fn(), setCustomNavLink: jest.fn(), }; @@ -83,7 +77,6 @@ const createStartContractMock = () => { startContract.getCustomNavLink$.mockReturnValue(new BehaviorSubject(undefined)); startContract.getHelpExtension$.mockReturnValue(new BehaviorSubject(undefined)); startContract.getIsNavDrawerLocked$.mockReturnValue(new BehaviorSubject(false)); - startContract.getNavType$.mockReturnValue(new BehaviorSubject('modern' as NavType)); return startContract; }; diff --git a/src/core/public/chrome/chrome_service.tsx b/src/core/public/chrome/chrome_service.tsx index b96c34cd9fbe8..b01f120b81305 100644 --- a/src/core/public/chrome/chrome_service.tsx +++ b/src/core/public/chrome/chrome_service.tsx @@ -37,7 +37,6 @@ import { ChromeNavControls, NavControlsService } from './nav_controls'; import { ChromeNavLinks, NavLinksService, ChromeNavLink } from './nav_links'; import { ChromeRecentlyAccessed, RecentlyAccessedService } from './recently_accessed'; import { Header } from './ui'; -import { NavType } from './ui/header'; import { ChromeHelpExtensionMenuLink } from './ui/header/header_help_menu'; export { ChromeNavControls, ChromeRecentlyAccessed, ChromeDocTitle }; @@ -172,10 +171,6 @@ export class ChromeService { const getIsNavDrawerLocked$ = isNavDrawerLocked$.pipe(takeUntil(this.stop$)); - // TODO #64541 - // Can delete - const getNavType$ = uiSettings.get$('pageNavigation').pipe(takeUntil(this.stop$)); - const isIE = () => { const ua = window.navigator.userAgent; const msie = ua.indexOf('MSIE '); // IE 10 or older @@ -241,10 +236,10 @@ export class ChromeService { navLinks$={navLinks.getNavLinks$()} recentlyAccessed$={recentlyAccessed.get$()} navControlsLeft$={navControls.getLeft$()} + navControlsCenter$={navControls.getCenter$()} navControlsRight$={navControls.getRight$()} onIsLockedUpdate={setIsNavDrawerLocked} isLocked$={getIsNavDrawerLocked$} - navType$={getNavType$} /> ), @@ -305,8 +300,6 @@ export class ChromeService { getIsNavDrawerLocked$: () => getIsNavDrawerLocked$, - getNavType$: () => getNavType$, - getCustomNavLink$: () => customNavLink$.pipe(takeUntil(this.stop$)), setCustomNavLink: (customNavLink?: ChromeNavLink) => { @@ -468,13 +461,6 @@ export interface ChromeStart { * Get an observable of the current locked state of the nav drawer. */ getIsNavDrawerLocked$(): Observable; - - /** - * Get the navigation type - * TODO #64541 - * Can delete - */ - getNavType$(): Observable; } /** @internal */ diff --git a/src/core/public/chrome/nav_controls/nav_controls_service.ts b/src/core/public/chrome/nav_controls/nav_controls_service.ts index 167948e01cb36..2638f40c77dc4 100644 --- a/src/core/public/chrome/nav_controls/nav_controls_service.ts +++ b/src/core/public/chrome/nav_controls/nav_controls_service.ts @@ -45,14 +45,18 @@ export interface ChromeNavControl { * @public */ export interface ChromeNavControls { - /** Register a nav control to be presented on the left side of the chrome header. */ + /** Register a nav control to be presented on the bottom-left side of the chrome header. */ registerLeft(navControl: ChromeNavControl): void; - /** Register a nav control to be presented on the right side of the chrome header. */ + /** Register a nav control to be presented on the top-right side of the chrome header. */ registerRight(navControl: ChromeNavControl): void; + /** Register a nav control to be presented on the top-center side of the chrome header. */ + registerCenter(navControl: ChromeNavControl): void; /** @internal */ getLeft$(): Observable; /** @internal */ getRight$(): Observable; + /** @internal */ + getCenter$(): Observable; } /** @internal */ @@ -62,6 +66,7 @@ export class NavControlsService { public start() { const navControlsLeft$ = new BehaviorSubject>(new Set()); const navControlsRight$ = new BehaviorSubject>(new Set()); + const navControlsCenter$ = new BehaviorSubject>(new Set()); return { // In the future, registration should be moved to the setup phase. This @@ -72,6 +77,9 @@ export class NavControlsService { registerRight: (navControl: ChromeNavControl) => navControlsRight$.next(new Set([...navControlsRight$.value.values(), navControl])), + registerCenter: (navControl: ChromeNavControl) => + navControlsCenter$.next(new Set([...navControlsCenter$.value.values(), navControl])), + getLeft$: () => navControlsLeft$.pipe( map((controls) => sortBy([...controls.values()], 'order')), @@ -82,6 +90,11 @@ export class NavControlsService { map((controls) => sortBy([...controls.values()], 'order')), takeUntil(this.stop$) ), + getCenter$: () => + navControlsCenter$.pipe( + map((controls) => sortBy([...controls.values()], 'order')), + takeUntil(this.stop$) + ), }; } diff --git a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap index a770ece8496e4..86cacfe98f767 100644 --- a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap +++ b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap @@ -121,7 +121,7 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` homeHref="/" id="collapsibe-nav" isLocked={false} - isOpen={true} + isNavOpen={true} navLinks$={ BehaviorSubject { "_isScalar": false, @@ -2105,7 +2105,7 @@ exports[`CollapsibleNav renders the default nav 1`] = ` homeHref="/" id="collapsibe-nav" isLocked={false} - isOpen={false} + isNavOpen={false} navLinks$={ BehaviorSubject { "_isScalar": false, @@ -2339,6 +2339,7 @@ exports[`CollapsibleNav renders the default nav 2`] = ` homeHref="/" id="collapsibe-nav" isLocked={false} + isNavOpen={false} isOpen={true} navLinks$={ BehaviorSubject { @@ -2454,461 +2455,9 @@ exports[`CollapsibleNav renders the default nav 2`] = ` data-test-subj="collapsibleNav" id="collapsibe-nav" isDocked={false} - isOpen={true} + isOpen={false} onClose={[Function]} - > - - - -
- -
-
- + /> `; @@ -3025,6 +2574,7 @@ exports[`CollapsibleNav renders the default nav 3`] = ` homeHref="/" id="collapsibe-nav" isLocked={true} + isNavOpen={false} isOpen={true} navLinks$={ BehaviorSubject { @@ -3140,7 +2690,7 @@ exports[`CollapsibleNav renders the default nav 3`] = ` data-test-subj="collapsibleNav" id="collapsibe-nav" isDocked={true} - isOpen={true} + isOpen={false} onClose={[Function]} > - -
-
-
- - -`; - -exports[`Header renders 2`] = ` -
+ -
- -
- -
- -
- - - -
-
- -
+ - - - -
- - - - -
- - , + ], + }, + Object { + "borders": "none", + "items": Array [ + -
- - - - - - - - - , ], - "thrownError": null, - } - } - /> - -
- -
+ }, + Object { + "borders": "none", + "items": Array [ , + , + ], + }, + ] + } + theme="dark" + > +
+ +
+ + + + +
+ +
+ +
+
+
+
+ +
+ +
+ - - - - } - closePopover={[Function]} - data-test-subj="helpMenuButton" - display="inlineBlock" - hasArrow={true} - id="headerHelpMenu" - isOpen={false} - ownFocus={true} - panelPaddingSize="m" - repositionOnScroll={true} - > - -
-
- - - -
-
-
-
- -
-
-
- -
-
-
- - - - -
-
-`; - -exports[`Header renders 3`] = ` -
- -
-
-
- -
- -
- -
- -
- - - -
-
- -
- - - - -
- - - - -
- - -
- - - - - - - - - - -
- -
- - - - - - } - closePopover={[Function]} - data-test-subj="helpMenuButton" - display="inlineBlock" - hasArrow={true} - id="headerHelpMenu" - isOpen={false} - ownFocus={true} - panelPaddingSize="m" - repositionOnScroll={true} - > - -
-
- - - -
-
-
-
-
-
-
-
- -
-
-
- - - - - -
- -
-
-
-
-
-
-`; - -exports[`Header renders 4`] = ` -
- -
-
-
- -
- -
- -
- - -
- - - -
-
-
- -
- - - - -
- - - - -
- - -
- - - - - - - - - - -
- -
- - + + + + } + closePopover={[Function]} + data-test-subj="helpMenuButton" + display="inlineBlock" + hasArrow={true} + id="headerHelpMenu" + isOpen={false} + ownFocus={true} + panelPaddingSize="m" + repositionOnScroll={true} + > + +
+
+ + + +
+
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+ + +
+ +
+ +
+ + + +
+
+ +
+ + +
+ + + + + + + + + + +
+ +
+ - - - - } - closePopover={[Function]} - data-test-subj="helpMenuButton" - display="inlineBlock" - hasArrow={true} - id="headerHelpMenu" - isOpen={false} - ownFocus={true} - panelPaddingSize="m" - repositionOnScroll={true} - > - -
-
- - - -
-
-
-
- - -
-
- -
-
-
- - + +
+ +
+ +
+ +
+ - - + - - - - + close + + + + + + + + + + + + `; diff --git a/src/core/public/chrome/ui/header/_index.scss b/src/core/public/chrome/ui/header/_index.scss index e3b73abbcabc2..44cd864278325 100644 --- a/src/core/public/chrome/ui/header/_index.scss +++ b/src/core/public/chrome/ui/header/_index.scss @@ -1,14 +1,5 @@ @include euiHeaderAffordForFixed; -// TODO #64541 -// Delete this block -.chrHeaderWrapper:not(.headerWrapper) { - width: 100%; - position: fixed; - top: 0; - z-index: 10; -} - .chrHeaderHelpMenu__version { text-transform: none; } diff --git a/src/core/public/chrome/ui/header/collapsible_nav.test.tsx b/src/core/public/chrome/ui/header/collapsible_nav.test.tsx index d4325e0caf88c..e33e76a45580e 100644 --- a/src/core/public/chrome/ui/header/collapsible_nav.test.tsx +++ b/src/core/public/chrome/ui/header/collapsible_nav.test.tsx @@ -59,7 +59,7 @@ function mockProps() { basePath: httpServiceMock.createSetupContract({ basePath: '/test' }).basePath, id: 'collapsibe-nav', isLocked: false, - isOpen: false, + isNavOpen: false, homeHref: '/', navLinks$: new BehaviorSubject([]), recentlyAccessed$: new BehaviorSubject([]), @@ -123,7 +123,7 @@ describe('CollapsibleNav', () => { const component = mount( { const component = mount( @@ -147,9 +147,9 @@ describe('CollapsibleNav', () => { clickGroup(component, 'kibana'); clickGroup(component, 'recentlyViewed'); expectShownNavLinksCount(component, 1); - component.setProps({ isOpen: false }); + component.setProps({ isNavOpen: false }); expectNavIsClosed(component); - component.setProps({ isOpen: true }); + component.setProps({ isNavOpen: true }); expectShownNavLinksCount(component, 1); }); @@ -160,14 +160,14 @@ describe('CollapsibleNav', () => { const component = mount( ); component.setProps({ closeNav: () => { - component.setProps({ isOpen: false }); + component.setProps({ isNavOpen: false }); onClose(); }, }); @@ -175,11 +175,11 @@ describe('CollapsibleNav', () => { component.find('[data-test-subj="collapsibleNavGroup-recentlyViewed"] a').simulate('click'); expect(onClose.callCount).toEqual(1); expectNavIsClosed(component); - component.setProps({ isOpen: true }); + component.setProps({ isNavOpen: true }); component.find('[data-test-subj="collapsibleNavGroup-kibana"] a').simulate('click'); expect(onClose.callCount).toEqual(2); expectNavIsClosed(component); - component.setProps({ isOpen: true }); + component.setProps({ isNavOpen: true }); component.find('[data-test-subj="collapsibleNavGroup-noCategory"] a').simulate('click'); expect(onClose.callCount).toEqual(3); expectNavIsClosed(component); diff --git a/src/core/public/chrome/ui/header/collapsible_nav.tsx b/src/core/public/chrome/ui/header/collapsible_nav.tsx index a5f42c0949562..01cdb9c38881a 100644 --- a/src/core/public/chrome/ui/header/collapsible_nav.tsx +++ b/src/core/public/chrome/ui/header/collapsible_nav.tsx @@ -79,7 +79,7 @@ interface Props { basePath: HttpStart['basePath']; id: string; isLocked: boolean; - isOpen: boolean; + isNavOpen: boolean; homeHref: string; navLinks$: Rx.Observable; recentlyAccessed$: Rx.Observable; @@ -94,7 +94,7 @@ export function CollapsibleNav({ basePath, id, isLocked, - isOpen, + isNavOpen, homeHref, storage = window.localStorage, onIsLockedUpdate, @@ -129,7 +129,7 @@ export function CollapsibleNav({ aria-label={i18n.translate('core.ui.primaryNav.screenReaderLabel', { defaultMessage: 'Primary', })} - isOpen={isOpen} + isOpen={isNavOpen} isDocked={isLocked} onClose={closeNav} > diff --git a/src/core/public/chrome/ui/header/header.test.tsx b/src/core/public/chrome/ui/header/header.test.tsx index 04eb256f30f37..7309b9af49388 100644 --- a/src/core/public/chrome/ui/header/header.test.tsx +++ b/src/core/public/chrome/ui/header/header.test.tsx @@ -21,7 +21,6 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; import { BehaviorSubject } from 'rxjs'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import { NavType } from '.'; import { httpServiceMock } from '../../../http/http_service.mock'; import { applicationServiceMock } from '../../../mocks'; import { Header } from './header'; @@ -51,10 +50,10 @@ function mockProps() { helpExtension$: new BehaviorSubject(undefined), helpSupportUrl$: new BehaviorSubject(''), navControlsLeft$: new BehaviorSubject([]), + navControlsCenter$: new BehaviorSubject([]), navControlsRight$: new BehaviorSubject([]), basePath: http.basePath, isLocked$: new BehaviorSubject(false), - navType$: new BehaviorSubject('modern' as NavType), loadingCount$: new BehaviorSubject(0), onIsLockedUpdate: () => {}, }; @@ -71,7 +70,6 @@ describe('Header', () => { const isVisible$ = new BehaviorSubject(false); const breadcrumbs$ = new BehaviorSubject([{ text: 'test' }]); const isLocked$ = new BehaviorSubject(false); - const navType$ = new BehaviorSubject('modern' as NavType); const navLinks$ = new BehaviorSubject([ { id: 'kibana', title: 'kibana', baseUrl: '', href: '' }, ]); @@ -92,22 +90,19 @@ describe('Header', () => { navLinks$={navLinks$} recentlyAccessed$={recentlyAccessed$} isLocked$={isLocked$} - navType$={navType$} customNavLink$={customNavLink$} /> ); - expect(component).toMatchSnapshot(); + expect(component.find('EuiHeader').exists()).toBeFalsy(); act(() => isVisible$.next(true)); component.update(); - expect(component).toMatchSnapshot(); + expect(component.find('EuiHeader').exists()).toBeTruthy(); + expect(component.find('nav[aria-label="Primary"]').exists()).toBeFalsy(); act(() => isLocked$.next(true)); component.update(); - expect(component).toMatchSnapshot(); - - act(() => navType$.next('legacy' as NavType)); - component.update(); + expect(component.find('nav[aria-label="Primary"]').exists()).toBeTruthy(); expect(component).toMatchSnapshot(); }); }); diff --git a/src/core/public/chrome/ui/header/header.tsx b/src/core/public/chrome/ui/header/header.tsx index c0b3fc72930dc..7ec03ea4c6da6 100644 --- a/src/core/public/chrome/ui/header/header.tsx +++ b/src/core/public/chrome/ui/header/header.tsx @@ -23,8 +23,6 @@ import { EuiHeaderSectionItem, EuiHeaderSectionItemButton, EuiIcon, - EuiNavDrawer, - EuiShowFor, htmlIdGenerator, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -43,14 +41,14 @@ import { import { InternalApplicationStart } from '../../../application/types'; import { HttpStart } from '../../../http'; import { ChromeHelpExtension } from '../../chrome_service'; -import { NavType, OnIsLockedUpdate } from './'; +import { OnIsLockedUpdate } from './'; import { CollapsibleNav } from './collapsible_nav'; import { HeaderBadge } from './header_badge'; import { HeaderBreadcrumbs } from './header_breadcrumbs'; import { HeaderHelpMenu } from './header_help_menu'; import { HeaderLogo } from './header_logo'; import { HeaderNavControls } from './header_nav_controls'; -import { NavDrawer } from './nav_drawer'; +import { HeaderActionMenu } from './header_action_menu'; export interface HeaderProps { kibanaVersion: string; @@ -68,27 +66,14 @@ export interface HeaderProps { helpExtension$: Observable; helpSupportUrl$: Observable; navControlsLeft$: Observable; + navControlsCenter$: Observable; navControlsRight$: Observable; basePath: HttpStart['basePath']; isLocked$: Observable; - navType$: Observable; loadingCount$: ReturnType; onIsLockedUpdate: OnIsLockedUpdate; } -function renderMenuTrigger(toggleOpen: () => void) { - return ( - - - - ); -} - export function Header({ kibanaVersion, kibanaDocLink, @@ -98,125 +83,116 @@ export function Header({ homeHref, ...observables }: HeaderProps) { - const isVisible = useObservable(observables.isVisible$, true); - const navType = useObservable(observables.navType$, 'modern'); + const isVisible = useObservable(observables.isVisible$, false); const isLocked = useObservable(observables.isLocked$, false); - const [isOpen, setIsOpen] = useState(false); + const [isNavOpen, setIsNavOpen] = useState(false); if (!isVisible) { return ; } - const navDrawerRef = createRef(); const toggleCollapsibleNavRef = createRef(); const navId = htmlIdGenerator()(); - const className = classnames( - 'chrHeaderWrapper', // TODO #64541 - delete this - 'hide-for-sharing', - { - 'chrHeaderWrapper--navIsLocked': isLocked, - headerWrapper: navType === 'modern', - } - ); + const className = classnames('hide-for-sharing', 'headerGlobalNav'); return ( <>
- - - {navType === 'modern' ? ( +
+ , + ], + borders: 'none', + }, + { + ...(observables.navControlsCenter$ && { + items: [], + }), + borders: 'none', + }, + { + items: [ + , + , + ], + borders: 'none', + }, + ]} + /> + + + setIsOpen(!isOpen)} - aria-expanded={isOpen} - aria-pressed={isOpen} + onClick={() => setIsNavOpen(!isNavOpen)} + aria-expanded={isNavOpen} + aria-pressed={isNavOpen} aria-controls={navId} ref={toggleCollapsibleNavRef} > - ) : ( - // TODO #64541 - // Delete this block - - - {renderMenuTrigger(() => navDrawerRef.current?.toggleOpen())} - - - )} - - - + - - + + - + - + - - - - + + + + + + +
- -
-
- {navType === 'modern' ? ( - { - setIsOpen(false); - if (toggleCollapsibleNavRef.current) { - toggleCollapsibleNavRef.current.focus(); - } - }} - customNavLink$={observables.customNavLink$} - /> - ) : ( - // TODO #64541 - // Delete this block - - )} + { + setIsNavOpen(false); + if (toggleCollapsibleNavRef.current) { + toggleCollapsibleNavRef.current.focus(); + } + }} + customNavLink$={observables.customNavLink$} + />
); diff --git a/src/core/public/chrome/ui/header/header_action_menu.test.tsx b/src/core/public/chrome/ui/header/header_action_menu.test.tsx new file mode 100644 index 0000000000000..a124c8ab66969 --- /dev/null +++ b/src/core/public/chrome/ui/header/header_action_menu.test.tsx @@ -0,0 +1,139 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { mount, ReactWrapper } from 'enzyme'; +import { act } from 'react-dom/test-utils'; +import { BehaviorSubject } from 'rxjs'; +import { HeaderActionMenu } from './header_action_menu'; +import { MountPoint, UnmountCallback } from '../../../types'; + +type MockedUnmount = jest.MockedFunction; + +describe('HeaderActionMenu', () => { + let component: ReactWrapper; + let menuMount$: BehaviorSubject; + let unmounts: Record; + + beforeEach(() => { + menuMount$ = new BehaviorSubject(undefined); + unmounts = {}; + }); + + const refresh = () => { + new Promise(async (resolve) => { + if (component) { + act(() => { + component.update(); + }); + } + setImmediate(() => resolve(component)); // flushes any pending promises + }); + }; + + const createMountPoint = (id: string, content: string = id): MountPoint => ( + root + ): MockedUnmount => { + const container = document.createElement('DIV'); + // eslint-disable-next-line no-unsanitized/property + container.innerHTML = content; + root.appendChild(container); + const unmount = jest.fn(() => container.remove()); + unmounts[id] = unmount; + return unmount; + }; + + it('mounts the current value of the provided observable', async () => { + component = mount(); + + act(() => { + menuMount$.next(createMountPoint('FOO')); + }); + await refresh(); + + expect(component.html()).toMatchInlineSnapshot( + `"
FOO
"` + ); + }); + + it('clears the content of the component when emitting undefined', async () => { + component = mount(); + + act(() => { + menuMount$.next(createMountPoint('FOO')); + }); + await refresh(); + + expect(component.html()).toMatchInlineSnapshot( + `"
FOO
"` + ); + + act(() => { + menuMount$.next(undefined); + }); + await refresh(); + + expect(component.html()).toMatchInlineSnapshot( + `"
"` + ); + }); + + it('updates the dom when a new mount point is emitted', async () => { + component = mount(); + + act(() => { + menuMount$.next(createMountPoint('FOO')); + }); + await refresh(); + + expect(component.html()).toMatchInlineSnapshot( + `"
FOO
"` + ); + + act(() => { + menuMount$.next(createMountPoint('BAR')); + }); + await refresh(); + + expect(component.html()).toMatchInlineSnapshot( + `"
BAR
"` + ); + }); + + it('calls the previous mount point `unmount` when mounting a new mount point', async () => { + component = mount(); + + act(() => { + menuMount$.next(createMountPoint('FOO')); + }); + await refresh(); + + expect(Object.keys(unmounts)).toEqual(['FOO']); + expect(unmounts.FOO).not.toHaveBeenCalled(); + + act(() => { + menuMount$.next(createMountPoint('BAR')); + }); + await refresh(); + + expect(Object.keys(unmounts)).toEqual(['FOO', 'BAR']); + expect(unmounts.FOO).toHaveBeenCalledTimes(1); + expect(unmounts.BAR).not.toHaveBeenCalled(); + }); +}); diff --git a/src/core/public/chrome/ui/header/header_action_menu.tsx b/src/core/public/chrome/ui/header/header_action_menu.tsx new file mode 100644 index 0000000000000..3a7a09608ba66 --- /dev/null +++ b/src/core/public/chrome/ui/header/header_action_menu.tsx @@ -0,0 +1,64 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { FC, useRef, useLayoutEffect, useState } from 'react'; +import { Observable } from 'rxjs'; +import { MountPoint, UnmountCallback } from '../../../types'; + +interface HeaderActionMenuProps { + actionMenu$: Observable; +} + +export const HeaderActionMenu: FC = ({ actionMenu$ }) => { + // useObservable relies on useState under the hood. The signature is type SetStateAction = S | ((prevState: S) => S); + // As we got a Observable here, React's setState setter assume he's getting a `(prevState: S) => S` signature, + // therefore executing the mount method, causing everything to crash. + // piping the observable before calling `useObservable` causes the effect to always having a new reference, as + // the piped observable is a new instance on every render, causing infinite loops. + // this is why we use `useLayoutEffect` manually here. + const [mounter, setMounter] = useState<{ mount: MountPoint | undefined }>({ mount: undefined }); + useLayoutEffect(() => { + const s = actionMenu$.subscribe((value) => { + setMounter({ mount: value }); + }); + return () => s.unsubscribe(); + }, [actionMenu$]); + + const elementRef = useRef(null); + const unmountRef = useRef(null); + + useLayoutEffect(() => { + if (unmountRef.current) { + unmountRef.current(); + unmountRef.current = null; + } + + if (mounter.mount && elementRef.current) { + try { + unmountRef.current = mounter.mount(elementRef.current); + } catch (e) { + // TODO: use client-side logger when feature is implemented + // eslint-disable-next-line no-console + console.error(e); + } + } + }, [mounter]); + + return
; +}; diff --git a/src/core/public/chrome/ui/header/header_logo.tsx b/src/core/public/chrome/ui/header/header_logo.tsx index 9bec946b6b76e..dee93ecb1a804 100644 --- a/src/core/public/chrome/ui/header/header_logo.tsx +++ b/src/core/public/chrome/ui/header/header_logo.tsx @@ -105,6 +105,8 @@ export function HeaderLogo({ href, navigateToApp, ...observables }: Props) { aria-label={i18n.translate('core.ui.chrome.headerGlobalNav.goHomePageIconAriaLabel', { defaultMessage: 'Go to home page', })} - /> + > + Elastic + ); } diff --git a/src/core/public/chrome/ui/header/header_nav_controls.tsx b/src/core/public/chrome/ui/header/header_nav_controls.tsx index 0941f7b27b662..8d9d8097fd8e3 100644 --- a/src/core/public/chrome/ui/header/header_nav_controls.tsx +++ b/src/core/public/chrome/ui/header/header_nav_controls.tsx @@ -26,7 +26,7 @@ import { HeaderExtension } from './header_extension'; interface Props { navControls$: Observable; - side: 'left' | 'right'; + side?: 'left' | 'right'; } export function HeaderNavControls({ navControls$, side }: Props) { @@ -41,7 +41,10 @@ export function HeaderNavControls({ navControls$, side }: Props) { return ( <> {navControls.map((navControl: ChromeNavControl, index: number) => ( - + ))} diff --git a/src/core/public/chrome/ui/header/nav_drawer.tsx b/src/core/public/chrome/ui/header/nav_drawer.tsx deleted file mode 100644 index fc080fbafc303..0000000000000 --- a/src/core/public/chrome/ui/header/nav_drawer.tsx +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { EuiHorizontalRule, EuiNavDrawer, EuiNavDrawerGroup } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import React from 'react'; -import { useObservable } from 'react-use'; -import { Observable } from 'rxjs'; -import { ChromeNavLink, ChromeRecentlyAccessedHistoryItem, CoreStart } from '../../..'; -import { InternalApplicationStart } from '../../../application/types'; -import { HttpStart } from '../../../http'; -import { OnIsLockedUpdate } from './'; -import { createEuiListItem, createRecentNavLink } from './nav_link'; -import { RecentLinks } from './recent_links'; - -export interface Props { - appId$: InternalApplicationStart['currentAppId$']; - basePath: HttpStart['basePath']; - isLocked?: boolean; - navLinks$: Observable; - recentlyAccessed$: Observable; - navigateToApp: CoreStart['application']['navigateToApp']; - onIsLockedUpdate?: OnIsLockedUpdate; -} - -function NavDrawerRenderer( - { isLocked, onIsLockedUpdate, basePath, navigateToApp, ...observables }: Props, - ref: React.Ref -) { - const appId = useObservable(observables.appId$, ''); - const navLinks = useObservable(observables.navLinks$, []).filter((link) => !link.hidden); - const recentNavLinks = useObservable(observables.recentlyAccessed$, []).map((link) => - createRecentNavLink(link, navLinks, basePath) - ); - - return ( - - {RecentLinks({ recentNavLinks })} - - - createEuiListItem({ - link, - appId, - basePath, - navigateToApp, - dataTestSubj: 'navDrawerAppsMenuLink', - }) - )} - aria-label={i18n.translate('core.ui.primaryNavList.screenReaderLabel', { - defaultMessage: 'Primary navigation links', - })} - /> - - ); -} - -export const NavDrawer = React.forwardRef(NavDrawerRenderer); diff --git a/src/core/public/index.scss b/src/core/public/index.scss index c2ad2841d5a77..6ba9254e5d381 100644 --- a/src/core/public/index.scss +++ b/src/core/public/index.scss @@ -1,6 +1,6 @@ +@import './variables'; @import './core'; @import './chrome/index'; @import './overlays/index'; @import './rendering/index'; @import './styles/index'; - diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 50d0767ca893b..e39aa0552a90a 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -268,10 +268,13 @@ export interface ChromeNavControl { // @public export interface ChromeNavControls { + // @internal (undocumented) + getCenter$(): Observable; // @internal (undocumented) getLeft$(): Observable; // @internal (undocumented) getRight$(): Observable; + registerCenter(navControl: ChromeNavControl): void; registerLeft(navControl: ChromeNavControl): void; registerRight(navControl: ChromeNavControl): void; } @@ -341,7 +344,6 @@ export interface ChromeStart { getHelpExtension$(): Observable; getIsNavDrawerLocked$(): Observable; getIsVisible$(): Observable; - getNavType$(): Observable; navControls: ChromeNavControls; navLinks: ChromeNavLinks; recentlyAccessed: ChromeRecentlyAccessed; diff --git a/src/core/public/rendering/_base.scss b/src/core/public/rendering/_base.scss index 211e9c03beea5..b806ac270331d 100644 --- a/src/core/public/rendering/_base.scss +++ b/src/core/public/rendering/_base.scss @@ -1,5 +1,4 @@ -@import '@elastic/eui/src/global_styling/variables/header'; -@import '@elastic/eui/src/components/nav_drawer/variables'; +@include euiHeaderAffordForFixed($kbnHeaderOffset); /** * stretch the root element of the Kibana application to set the base-size that @@ -12,74 +11,11 @@ min-height: 100%; } -// TODO #64541 -// Delete this block -.chrHeaderWrapper:not(.headerWrapper) ~ .app-wrapper { +.app-wrapper { display: flex; flex-flow: column nowrap; - position: absolute; - left: 0; - top: 0; - right: 0; - bottom: 0; - z-index: 5; margin: 0 auto; - - &:not(.hidden-chrome) { - top: $euiHeaderChildSize; - left: $euiHeaderChildSize; - - // HOTFIX: Temporary fix for flyouts not inside portals - // SASSTODO: Find an actual solution - .euiFlyout { - top: $euiHeaderChildSize; - height: calc(100% - #{$euiHeaderChildSize}); - } - - @include euiBreakpoint('xs', 's') { - left: 0; - } - } - - /** - * 1. Dirty, but we need to override the .kbnGlobalNav-isOpen state - * when we're looking at the log-in screen. - */ - &.hidden-chrome { - left: 0 !important; /* 1 */ - } - - .navbar-right { - margin-right: 0; - } -} - -// TODO #64541 -// Delete this block -@include euiBreakpoint('xl') { - .chrHeaderWrapper--navIsLocked:not(.headerWrapper) { - ~ .app-wrapper:not(.hidden-chrome) { - // Shrink the content from the left so it's no longer overlapped by the nav drawer (ALWAYS) - left: $euiNavDrawerWidthExpanded !important; // sass-lint:disable-line no-important - transition: left $euiAnimSpeedFast $euiAnimSlightResistance; - } - } -} - -// TODO #64541 -// Remove .headerWrapper and header conditionals -.headerWrapper ~ .app-wrapper, -:not(header) ~ .app-wrapper { - display: flex; - flex-flow: column nowrap; - margin: 0 auto; - min-height: calc(100vh - #{$euiHeaderHeightCompensation}); - - @include internetExplorerOnly { - // IE specific bug with min-height in flex container, described in the next link - // https://github.com/philipwalton/flexbugs#3-min-height-on-a-flex-container-wont-apply-to-its-flex-items - height: calc(100vh - #{$euiHeaderHeightCompensation}); - } + min-height: calc(100vh - #{$kbnHeaderOffset}); &.hidden-chrome { min-height: 100vh; diff --git a/src/core/public/styles/_base.scss b/src/core/public/styles/_base.scss index 427c6b7735435..bfb07c1b51427 100644 --- a/src/core/public/styles/_base.scss +++ b/src/core/public/styles/_base.scss @@ -7,17 +7,6 @@ // Application Layout -// chrome-context -// TODO #64541 -// Delete this block -.chrHeaderWrapper:not(.headerWrapper) .content { - display: flex; - flex-flow: row nowrap; - width: 100%; - height: 100%; - overflow: hidden; -} - .application, .app-container { > * { diff --git a/src/plugins/advanced_settings/public/management_app/advanced_settings.tsx b/src/plugins/advanced_settings/public/management_app/advanced_settings.tsx index bbc3f3632bf64..8c9e3847844d9 100644 --- a/src/plugins/advanced_settings/public/management_app/advanced_settings.tsx +++ b/src/plugins/advanced_settings/public/management_app/advanced_settings.tsx @@ -121,7 +121,7 @@ export class AdvancedSettingsComponent extends Component< setTimeout(() => { const id = hash.replace('#', ''); const element = document.getElementById(id); - const globalNavOffset = document.getElementById('headerGlobalNav')?.offsetHeight || 0; + const globalNavOffset = document.getElementById('globalHeaderBars')?.offsetHeight || 0; if (element) { element.scrollIntoView(); diff --git a/src/plugins/advanced_settings/public/management_app/components/_index.scss b/src/plugins/advanced_settings/public/management_app/components/_index.scss deleted file mode 100644 index d2d2e38947f76..0000000000000 --- a/src/plugins/advanced_settings/public/management_app/components/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './form/index'; diff --git a/src/plugins/advanced_settings/public/management_app/components/form/_form.scss b/src/plugins/advanced_settings/public/management_app/components/form/_form.scss deleted file mode 100644 index 8d768d200fdd2..0000000000000 --- a/src/plugins/advanced_settings/public/management_app/components/form/_form.scss +++ /dev/null @@ -1,15 +0,0 @@ -@import '@elastic/eui/src/global_styling/variables/header'; -@import '@elastic/eui/src/components/nav_drawer/variables'; - -// TODO #64541 -// Delete this whole file -.mgtAdvancedSettingsForm__bottomBar { - margin-left: $euiNavDrawerWidthCollapsed; - z-index: 9; // Puts it inuder the nav drawer when expanded - &--pushForNav { - margin-left: $euiNavDrawerWidthExpanded; - } - @include euiBreakpoint('xs', 's') { - margin-left: 0; - } -} diff --git a/src/plugins/advanced_settings/public/management_app/components/form/_index.scss b/src/plugins/advanced_settings/public/management_app/components/form/_index.scss deleted file mode 100644 index 2ef4ef1d20ce9..0000000000000 --- a/src/plugins/advanced_settings/public/management_app/components/form/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './form'; diff --git a/src/plugins/advanced_settings/public/management_app/components/form/form.tsx b/src/plugins/advanced_settings/public/management_app/components/form/form.tsx index 5533f684870d9..0378d816fd2c3 100644 --- a/src/plugins/advanced_settings/public/management_app/components/form/form.tsx +++ b/src/plugins/advanced_settings/public/management_app/components/form/form.tsx @@ -18,7 +18,6 @@ */ import React, { PureComponent, Fragment } from 'react'; -import classNames from 'classnames'; import { EuiFlexGroup, @@ -45,7 +44,6 @@ import { Field, getEditableValue } from '../field'; import { FieldSetting, SettingsChanges, FieldState } from '../../types'; type Category = string; -const NAV_IS_LOCKED_KEY = 'core.chrome.isLocked'; interface FormProps { settings: Record; @@ -326,23 +324,8 @@ export class Form extends PureComponent { renderBottomBar = () => { const areChangesInvalid = this.areChangesInvalid(); - - // TODO #64541 - // Delete these classes - let bottomBarClasses = ''; - const pageNav = this.props.settings.general.find( - (setting) => setting.name === 'pageNavigation' - ); - - if (pageNav?.value === 'legacy') { - bottomBarClasses = classNames('mgtAdvancedSettingsForm__bottomBar', { - // eslint-disable-next-line @typescript-eslint/naming-convention - 'mgtAdvancedSettingsForm__bottomBar--pushForNav': - localStorage.getItem(NAV_IS_LOCKED_KEY) === 'true', - }); - } return ( - + ScopedHistory; + setHeaderActionMenu: AppMountParameters['setHeaderActionMenu']; savedObjects: SavedObjectsStart; restorePreviousUrl: () => void; } diff --git a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx index 92d6f2ed91dde..dd5eb1ee5ccaa 100644 --- a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx +++ b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx @@ -153,6 +153,7 @@ export class DashboardAppController { i18n: i18nStart, }, history, + setHeaderActionMenu, kbnUrlStateStorage, usageCollection, navigation, @@ -709,7 +710,13 @@ export class DashboardAppController { }; const dashboardNavBar = document.getElementById('dashboardChrome'); const updateNavBar = () => { - ReactDOM.render(, dashboardNavBar); + ReactDOM.render( + , + dashboardNavBar + ); }; const unmountNavBar = () => { diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index 10fc2ed7d681e..2825720637902 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -310,7 +310,7 @@ export class DashboardPlugin id: DashboardConstants.DASHBOARDS_ID, title: 'Dashboard', order: -1001, - euiIconType: 'dashboardApp', + euiIconType: 'logoKibana', defaultPath: `#${DashboardConstants.LANDING_PAGE_PATH}`, updater$: this.appStateUpdater, category: DEFAULT_APP_CATEGORIES.kibana, @@ -352,6 +352,7 @@ export class DashboardPlugin localStorage: new Storage(localStorage), usageCollection, scopedHistory: () => this.currentHistory!, + setHeaderActionMenu: params.setHeaderActionMenu, savedObjects, restorePreviousUrl, }; diff --git a/src/plugins/dev_tools/public/index.scss b/src/plugins/dev_tools/public/index.scss index c9d8dc7470656..4bec602ea42db 100644 --- a/src/plugins/dev_tools/public/index.scss +++ b/src/plugins/dev_tools/public/index.scss @@ -16,10 +16,6 @@ } } -.devApp { - height: 100%; -} - .devAppWrapper { display: flex; flex-direction: column; diff --git a/src/plugins/dev_tools/public/plugin.ts b/src/plugins/dev_tools/public/plugin.ts index fcc6a57361a94..8c4743c93fab3 100644 --- a/src/plugins/dev_tools/public/plugin.ts +++ b/src/plugins/dev_tools/public/plugin.ts @@ -60,7 +60,7 @@ export class DevToolsPlugin implements Plugin { defaultMessage: 'Dev Tools', }), updater$: this.appStateUpdater, - euiIconType: 'devToolsApp', + euiIconType: 'logoElastic', order: 9010, category: DEFAULT_APP_CATEGORIES.management, mount: async (params: AppMountParameters) => { diff --git a/src/plugins/discover/public/application/angular/discover.html b/src/plugins/discover/public/application/angular/discover.html index 94f13e1cd8132..e0e452aaa41c5 100644 --- a/src/plugins/discover/public/application/angular/discover.html +++ b/src/plugins/discover/public/application/angular/discover.html @@ -5,6 +5,7 @@

{{screenTitle}}

(uiActions = pluginUiActions); export const getUiActions = () => uiActions; +export const [getHeaderActionMenuMounter, setHeaderActionMenuMounter] = createGetterSetter< + AppMountParameters['setHeaderActionMenu'] +>('headerActionMenuMounter'); + export const [getUrlTracker, setUrlTracker] = createGetterSetter<{ setTrackedUrl: (url: string) => void; restorePreviousUrl: () => void; diff --git a/src/plugins/discover/public/plugin.ts b/src/plugins/discover/public/plugin.ts index 63c943530c59b..0bdc5ad95b391 100644 --- a/src/plugins/discover/public/plugin.ts +++ b/src/plugins/discover/public/plugin.ts @@ -54,6 +54,7 @@ import { setUrlTracker, setAngularModule, setServices, + setHeaderActionMenuMounter, setUiActions, setScopedHistory, getScopedHistory, @@ -240,7 +241,7 @@ export class DiscoverPlugin title: 'Discover', updater$: this.appStateUpdater.asObservable(), order: -1004, - euiIconType: 'discoverApp', + euiIconType: 'logoKibana', defaultPath: '#/', category: DEFAULT_APP_CATEGORIES.kibana, mount: async (params: AppMountParameters) => { @@ -251,6 +252,7 @@ export class DiscoverPlugin throw Error('Discover plugin method initializeInnerAngular is undefined'); } setScopedHistory(params.history); + setHeaderActionMenuMounter(params.setHeaderActionMenu); syncHistoryLocations(); appMounted(); const { @@ -264,6 +266,7 @@ export class DiscoverPlugin params.element.classList.add('dscAppWrapper'); const unmount = await renderApp(innerAngularName, params.element); return () => { + params.element.classList.remove('dscAppWrapper'); unmount(); appUnMounted(); }; diff --git a/src/plugins/kibana_legacy/public/angular/kbn_top_nav.js b/src/plugins/kibana_legacy/public/angular/kbn_top_nav.js index b3fbe8baadec3..c34e2487b32d4 100644 --- a/src/plugins/kibana_legacy/public/angular/kbn_top_nav.js +++ b/src/plugins/kibana_legacy/public/angular/kbn_top_nav.js @@ -74,6 +74,7 @@ export function createTopNavDirective() { export const createTopNavHelper = ({ TopNavMenu }) => (reactDirective) => { return reactDirective(TopNavMenu, [ ['config', { watchDepth: 'value' }], + ['setMenuMountPoint', { watchDepth: 'reference' }], ['disabledButtons', { watchDepth: 'reference' }], ['query', { watchDepth: 'reference' }], diff --git a/src/plugins/kibana_react/public/util/index.ts b/src/plugins/kibana_react/public/util/index.ts index a6f3f87535f46..030dd4aec0f12 100644 --- a/src/plugins/kibana_react/public/util/index.ts +++ b/src/plugins/kibana_react/public/util/index.ts @@ -19,3 +19,4 @@ export { toMountPoint } from './to_mount_point'; export { MountPointPortal } from './mount_point_portal'; +export { useIfMounted } from './utils'; diff --git a/src/plugins/kibana_react/public/util/mount_point_portal.tsx b/src/plugins/kibana_react/public/util/mount_point_portal.tsx index b762fba88791e..0249302763772 100644 --- a/src/plugins/kibana_react/public/util/mount_point_portal.tsx +++ b/src/plugins/kibana_react/public/util/mount_point_portal.tsx @@ -21,6 +21,7 @@ import { i18n } from '@kbn/i18n'; import React, { useRef, useEffect, useState, Component } from 'react'; import ReactDOM from 'react-dom'; import { MountPoint } from 'kibana/public'; +import { useIfMounted } from './utils'; interface MountPointPortalProps { setMountPoint: (mountPoint: MountPoint) => void; @@ -33,20 +34,30 @@ export const MountPointPortal: React.FC = ({ children, se // state used to force re-renders when the element changes const [shouldRender, setShouldRender] = useState(false); const el = useRef(); + const ifMounted = useIfMounted(); useEffect(() => { setMountPoint((element) => { - el.current = element; - setShouldRender(true); + ifMounted(() => { + el.current = element; + setShouldRender(true); + }); return () => { - setShouldRender(false); - el.current = undefined; + // the component can be unmounted from the dom before the portal target actually + // calls the `unmount` function. This is a no-op but show a scary warning in the console + // so we use a ifMounted effect to avoid it. + ifMounted(() => { + setShouldRender(false); + el.current = undefined; + }); }; }); return () => { - setShouldRender(false); - el.current = undefined; + ifMounted(() => { + setShouldRender(false); + el.current = undefined; + }); }; }, [setMountPoint]); diff --git a/src/plugins/kibana_react/public/util/utils.ts b/src/plugins/kibana_react/public/util/utils.ts new file mode 100644 index 0000000000000..23d4ac2ec4851 --- /dev/null +++ b/src/plugins/kibana_react/public/util/utils.ts @@ -0,0 +1,38 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { useCallback, useEffect, useRef } from 'react'; + +export const useIfMounted = () => { + const isMounted = useRef(true); + useEffect( + () => () => { + isMounted.current = false; + }, + [] + ); + + const ifMounted = useCallback((func) => { + if (isMounted.current && func) { + func(); + } + }, []); + + return ifMounted; +}; diff --git a/src/plugins/management/public/plugin.ts b/src/plugins/management/public/plugin.ts index 794bbc0d0613b..fafedf46c2bda 100644 --- a/src/plugins/management/public/plugin.ts +++ b/src/plugins/management/public/plugin.ts @@ -74,7 +74,7 @@ export class ManagementPlugin implements Plugin * > * { + // TEMP fix to adjust spacing between EuiHeaderList__list items + margin: 0 $euiSizeXS; } diff --git a/src/plugins/navigation/public/top_nav_menu/top_nav_menu.test.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_menu.test.tsx index f21e5680e8f61..212bc19208ca8 100644 --- a/src/plugins/navigation/public/top_nav_menu/top_nav_menu.test.tsx +++ b/src/plugins/navigation/public/top_nav_menu/top_nav_menu.test.tsx @@ -164,7 +164,10 @@ describe('TopNavMenu', () => { // menu is rendered outside of the component expect(component.find(TOP_NAV_ITEM_SELECTOR).length).toBe(0); - expect(portalTarget.getElementsByTagName('BUTTON').length).toBe(menuItems.length); + + const buttons = portalTarget.querySelectorAll('button'); + expect(buttons.length).toBe(menuItems.length + 1); // should be n+1 buttons in mobile for popover button + expect(buttons[buttons.length - 1].getAttribute('aria-label')).toBe('Open navigation menu'); // last button should be mobile button }); }); }); diff --git a/src/plugins/navigation/public/top_nav_menu/top_nav_menu.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_menu.tsx index b284c60bac5de..a27addeb14393 100644 --- a/src/plugins/navigation/public/top_nav_menu/top_nav_menu.tsx +++ b/src/plugins/navigation/public/top_nav_menu/top_nav_menu.tsx @@ -18,7 +18,7 @@ */ import React, { ReactElement } from 'react'; -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { EuiHeaderLinks } from '@elastic/eui'; import classNames from 'classnames'; import { MountPoint } from '../../../../core/public'; @@ -81,31 +81,16 @@ export function TopNavMenu(props: TopNavMenuProps): ReactElement | null { function renderItems(): ReactElement[] | null { if (!config || config.length === 0) return null; return config.map((menuItem: TopNavMenuData, i: number) => { - return ( - - - - ); + return ; }); } function renderMenu(className: string): ReactElement | null { if (!config || config.length === 0) return null; return ( - + {renderItems()} - + ); } diff --git a/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.tsx index b058ef0de448b..96a205b737273 100644 --- a/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.tsx +++ b/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.tsx @@ -19,9 +19,7 @@ import { upperFirst, isFunction } from 'lodash'; import React, { MouseEvent } from 'react'; -import { EuiButtonEmpty, EuiToolTip } from '@elastic/eui'; - -import { EuiButton } from '@elastic/eui'; +import { EuiToolTip, EuiButton, EuiHeaderLink } from '@elastic/eui'; import { TopNavMenuData } from './top_nav_menu_data'; export function TopNavMenuItem(props: TopNavMenuData) { @@ -50,13 +48,13 @@ export function TopNavMenuItem(props: TopNavMenuData) { }; const btn = props.emphasize ? ( - + {upperFirst(props.label || props.id!)} ) : ( - + {upperFirst(props.label || props.id!)} - + ); const tooltip = getTooltip(); diff --git a/src/plugins/newsfeed/public/components/newsfeed_header_nav_button.tsx b/src/plugins/newsfeed/public/components/newsfeed_header_nav_button.tsx index 888b807b5296f..628cfde18b0d5 100644 --- a/src/plugins/newsfeed/public/components/newsfeed_header_nav_button.tsx +++ b/src/plugins/newsfeed/public/components/newsfeed_header_nav_button.tsx @@ -68,7 +68,7 @@ export const NewsfeedNavButton = ({ apiFetchResult }: Props) => { aria-label="Newsfeed menu" onClick={showFlyout} > - + {showBadge ? ( ▪ diff --git a/src/plugins/timelion/public/_app.scss b/src/plugins/timelion/public/_app.scss index 3142e1d23cf10..8b9078caba5a8 100644 --- a/src/plugins/timelion/public/_app.scss +++ b/src/plugins/timelion/public/_app.scss @@ -1,8 +1,5 @@ -@import '@elastic/eui/src/global_styling/variables/header'; - .timApp { position: relative; - min-height: calc(100vh - #{$euiHeaderChildSize}); background: $euiColorEmptyShade; [ng-click] { diff --git a/src/plugins/timelion/public/plugin.ts b/src/plugins/timelion/public/plugin.ts index a92ced20cb6d1..24d1b9eb3fb65 100644 --- a/src/plugins/timelion/public/plugin.ts +++ b/src/plugins/timelion/public/plugin.ts @@ -91,7 +91,7 @@ export class TimelionPlugin implements Plugin { title: 'Timelion', order: 8000, defaultPath: '#/', - euiIconType: 'timelionApp', + euiIconType: 'logoKibana', category: DEFAULT_APP_CATEGORIES.kibana, updater$: this.appStateUpdater.asObservable(), mount: async (params: AppMountParameters) => { diff --git a/src/plugins/visualize/public/application/components/visualize_top_nav.tsx b/src/plugins/visualize/public/application/components/visualize_top_nav.tsx index 130561b6245ae..dfd3c09f51ed5 100644 --- a/src/plugins/visualize/public/application/components/visualize_top_nav.tsx +++ b/src/plugins/visualize/public/application/components/visualize_top_nav.tsx @@ -61,6 +61,7 @@ const TopNav = ({ }: VisualizeTopNavProps) => { const { services } = useKibana(); const { TopNavMenu } = services.navigation.ui; + const { setHeaderActionMenu } = services; const { embeddableHandler, vis } = visInstance; const [inspectorSession, setInspectorSession] = useState(); const openInspector = useCallback(() => { @@ -151,6 +152,7 @@ const TopNav = ({ void; scopedHistory: ScopedHistory; dashboard: DashboardStart; + setHeaderActionMenu: AppMountParameters['setHeaderActionMenu']; } export interface SavedVisInstance { diff --git a/src/plugins/visualize/public/plugin.ts b/src/plugins/visualize/public/plugin.ts index 3e65ca49fa52a..38c376e01b5e4 100644 --- a/src/plugins/visualize/public/plugin.ts +++ b/src/plugins/visualize/public/plugin.ts @@ -140,7 +140,7 @@ export class VisualizePlugin id: 'visualize', title: 'Visualize', order: -1002, - euiIconType: 'visualizeApp', + euiIconType: 'logoKibana', defaultPath: '#/', category: DEFAULT_APP_CATEGORIES.kibana, updater$: this.appStateUpdater.asObservable(), @@ -196,12 +196,14 @@ export class VisualizePlugin scopedHistory: params.history, restorePreviousUrl, dashboard: pluginsStart.dashboard, + setHeaderActionMenu: params.setHeaderActionMenu, }; params.element.classList.add('visAppWrapper'); const { renderApp } = await import('./application'); const unmount = renderApp(params, services); return () => { + params.element.classList.remove('visAppWrapper'); unlistenParentHistory(); unmount(); appUnMounted(); diff --git a/test/accessibility/apps/management.ts b/test/accessibility/apps/management.ts index b37500f5b15dd..08177f54ee881 100644 --- a/test/accessibility/apps/management.ts +++ b/test/accessibility/apps/management.ts @@ -64,8 +64,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await a11y.testAppSnapshot(); }); - // Will be enabling this and field formatters after this issue is addressed: https://github.com/elastic/kibana/issues/60030 - it.skip('Edit field type', async () => { + it('Edit field type', async () => { await PageObjects.settings.clickEditFieldFormat(); await a11y.testAppSnapshot(); }); diff --git a/test/functional/page_objects/time_picker.ts b/test/functional/page_objects/time_picker.ts index 8a726cee444c1..237dc8946ae0e 100644 --- a/test/functional/page_objects/time_picker.ts +++ b/test/functional/page_objects/time_picker.ts @@ -39,7 +39,7 @@ export function TimePickerProvider({ getService, getPageObjects }: FtrProviderCo const find = getService('find'); const browser = getService('browser'); const testSubjects = getService('testSubjects'); - const { header, common } = getPageObjects(['header', 'common']); + const { header } = getPageObjects(['header']); const kibanaServer = getService('kibanaServer'); class TimePicker { @@ -127,7 +127,7 @@ export function TimePickerProvider({ getService, getPageObjects }: FtrProviderCo await testSubjects.click('superDatePickerAbsoluteTab'); await testSubjects.click('superDatePickerAbsoluteDateInput'); await this.inputValue('superDatePickerAbsoluteDateInput', toTime); - await common.sleep(500); + await browser.pressKeys(browser.keys.ESCAPE); // close popover because sometimes browser can't find start input // set from time await testSubjects.click('superDatePickerstartDatePopoverButton'); diff --git a/test/functional/services/listing_table.ts b/test/functional/services/listing_table.ts index 3f9775d7a75f3..aebdc734cfb39 100644 --- a/test/functional/services/listing_table.ts +++ b/test/functional/services/listing_table.ts @@ -33,7 +33,7 @@ export function ListingTableProvider({ getService, getPageObjects }: FtrProvider */ class ListingTable { private async getSearchFilter() { - const searchFilter = await find.allByCssSelector('.euiFieldSearch'); + const searchFilter = await find.allByCssSelector('main .euiFieldSearch'); return searchFilter[0]; } diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index 90e3673477062..60fdc8205f509 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -2,7 +2,10 @@ "prefix": "xpack", "paths": { "xpack.actions": "plugins/actions", - "xpack.uiActionsEnhanced": ["plugins/ui_actions_enhanced", "examples/ui_actions_enhanced_examples"], + "xpack.uiActionsEnhanced": [ + "plugins/ui_actions_enhanced", + "examples/ui_actions_enhanced_examples" + ], "xpack.alerts": "plugins/alerts", "xpack.eventLog": "plugins/event_log", "xpack.alertingBuiltins": "plugins/alerting_builtins", @@ -21,6 +24,7 @@ "xpack.features": "plugins/features", "xpack.fileUpload": "plugins/file_upload", "xpack.globalSearch": ["plugins/global_search"], + "xpack.globalSearchBar": ["plugins/global_search_bar"], "xpack.graph": ["plugins/graph"], "xpack.grokDebugger": "plugins/grokdebugger", "xpack.idxMgmt": "plugins/index_management", diff --git a/x-pack/plugins/apm/public/plugin.ts b/x-pack/plugins/apm/public/plugin.ts index 33e6a4b50a742..51ac6673251fb 100644 --- a/x-pack/plugins/apm/public/plugin.ts +++ b/x-pack/plugins/apm/public/plugin.ts @@ -105,7 +105,7 @@ export class ApmPlugin implements Plugin { id: 'apm', title: 'APM', order: 8300, - euiIconType: 'apmApp', + euiIconType: 'logoObservability', appRoute: '/app/apm', icon: 'plugins/apm/public/icon.svg', category: DEFAULT_APP_CATEGORIES.observability, @@ -125,6 +125,7 @@ export class ApmPlugin implements Plugin { id: 'csm', title: 'Client Side Monitoring', order: 8500, + euiIconType: 'logoObservability', category: DEFAULT_APP_CATEGORIES.observability, async mount(params: AppMountParameters) { diff --git a/x-pack/plugins/canvas/public/components/asset_manager/__stories__/__snapshots__/asset.stories.storyshot b/x-pack/plugins/canvas/public/components/asset_manager/__stories__/__snapshots__/asset.stories.storyshot index 87205b363e697..14791cd3d8b25 100644 --- a/x-pack/plugins/canvas/public/components/asset_manager/__stories__/__snapshots__/asset.stories.storyshot +++ b/x-pack/plugins/canvas/public/components/asset_manager/__stories__/__snapshots__/asset.stories.storyshot @@ -355,181 +355,3 @@ exports[`Storyshots components/Assets/Asset marker 1`] = `
`; - -exports[`Storyshots components/Assets/Asset redux 1`] = ` -
-
-
-
-
- Asset thumbnail -
-
-
-
-

- - airplane - -
- - - ( - 1 - kb) - - -

-
-
-
-
- - - -
-
- -
- -
-
-
-
- -
- -
-
-
-
- - - -
-
-
-
-
-`; diff --git a/x-pack/plugins/canvas/public/components/fullscreen/fullscreen.scss b/x-pack/plugins/canvas/public/components/fullscreen/fullscreen.scss index 7110a22408fe2..edd681a2c33e8 100644 --- a/x-pack/plugins/canvas/public/components/fullscreen/fullscreen.scss +++ b/x-pack/plugins/canvas/public/components/fullscreen/fullscreen.scss @@ -1,5 +1,5 @@ body.canvas-isFullscreen { // sass-lint:disable-line no-qualifying-elements - // following two rules are for overriding the header bar padding + // following two rules are for overriding the header bar padding &.euiBody--headerIsFixed { padding-top: 0; } @@ -13,28 +13,17 @@ body.canvas-isFullscreen { // sass-lint:disable-line no-qualifying-elements padding-left: 0 !important; // sass-lint:disable-line no-important } - // hide global loading indicator .kbnLoadingIndicator { display: none; } - // remove space for global nav elements - // TODO #64541 - // Can delete this block - .chrHeaderWrapper ~ .app-wrapper { - // Override locked nav at all breakpoints - left: 0 !important; // sass-lint:disable-line no-important - top: 0; - } - // set the background color .canvasLayout { background: $euiColorInk; } // hide all the interface parts - .chrHeaderWrapper, // K7 global top nav .canvasLayout__stageHeader, .canvasLayout__sidebar, .canvasLayout__footer, diff --git a/x-pack/plugins/canvas/public/lib/fullscreen.js b/x-pack/plugins/canvas/public/lib/fullscreen.js index bb9990d4f5457..6ad52d591c9c4 100644 --- a/x-pack/plugins/canvas/public/lib/fullscreen.js +++ b/x-pack/plugins/canvas/public/lib/fullscreen.js @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { platformService } from '../services'; + export const fullscreenClass = 'canvas-isFullscreen'; export function setFullscreen(fullscreen, doc = document) { @@ -13,8 +15,10 @@ export function setFullscreen(fullscreen, doc = document) { const isFullscreen = bodyClassList.contains(fullscreenClass); if (enabled && !isFullscreen) { + platformService.getService().setFullscreen(false); bodyClassList.add(fullscreenClass); } else if (!enabled && isFullscreen) { bodyClassList.remove(fullscreenClass); + platformService.getService().setFullscreen(true); } } diff --git a/x-pack/plugins/canvas/public/plugin.tsx b/x-pack/plugins/canvas/public/plugin.tsx index b02fb9db28612..fbca1e51bd5c6 100644 --- a/x-pack/plugins/canvas/public/plugin.tsx +++ b/x-pack/plugins/canvas/public/plugin.tsx @@ -88,7 +88,7 @@ export class CanvasPlugin category: DEFAULT_APP_CATEGORIES.kibana, id: 'canvas', title: 'Canvas', - euiIconType: 'canvasApp', + euiIconType: 'logoKibana', order: 3000, updater$: this.appUpdater, mount: async (params: AppMountParameters) => { diff --git a/x-pack/plugins/canvas/public/services/platform.ts b/x-pack/plugins/canvas/public/services/platform.ts index 92c378e9aa597..a27c57f32cf9f 100644 --- a/x-pack/plugins/canvas/public/services/platform.ts +++ b/x-pack/plugins/canvas/public/services/platform.ts @@ -10,6 +10,7 @@ import { IUiSettingsClient, ChromeBreadcrumb, IBasePath, + ChromeStart, } from '../../../../../src/core/public'; import { CanvasServiceFactory } from '.'; @@ -22,6 +23,7 @@ export interface PlatformService { getUISetting: (key: string, defaultValue?: any) => any; setBreadcrumbs: (newBreadcrumbs: ChromeBreadcrumb[]) => void; setRecentlyAccessed: (link: string, label: string, id: string) => void; + setFullscreen: ChromeStart['setIsVisible']; // TODO: these should go away. We want thin accessors, not entire objects. // Entire objects are hard to mock, and hide our dependency on the external service. @@ -45,6 +47,7 @@ export const platformServiceFactory: CanvasServiceFactory = ( getUISetting: coreStart.uiSettings.get.bind(coreStart.uiSettings), setBreadcrumbs: coreStart.chrome.setBreadcrumbs, setRecentlyAccessed: coreStart.chrome.recentlyAccessed.add, + setFullscreen: coreStart.chrome.setIsVisible, // TODO: these should go away. We want thin accessors, not entire objects. // Entire objects are hard to mock, and hide our dependency on the external service. diff --git a/x-pack/plugins/canvas/public/services/stubs/platform.ts b/x-pack/plugins/canvas/public/services/stubs/platform.ts index 9ada579573502..bef3b2609537c 100644 --- a/x-pack/plugins/canvas/public/services/stubs/platform.ts +++ b/x-pack/plugins/canvas/public/services/stubs/platform.ts @@ -20,4 +20,5 @@ export const platformService: PlatformService = { getSavedObjects: noop, getSavedObjectsClient: noop, getUISettings: noop, + setFullscreen: noop, }; diff --git a/x-pack/plugins/enterprise_search/common/constants.ts b/x-pack/plugins/enterprise_search/common/constants.ts index c6ca0d532ce07..d6a51e8b482d0 100644 --- a/x-pack/plugins/enterprise_search/common/constants.ts +++ b/x-pack/plugins/enterprise_search/common/constants.ts @@ -29,6 +29,7 @@ export const ENTERPRISE_SEARCH_PLUGIN = { }), ], URL: '/app/enterprise_search/overview', + LOGO: 'logoEnterpriseSearch', }; export const APP_SEARCH_PLUGIN = { diff --git a/x-pack/plugins/enterprise_search/public/plugin.ts b/x-pack/plugins/enterprise_search/public/plugin.ts index b735db7c49520..63f334811ce31 100644 --- a/x-pack/plugins/enterprise_search/public/plugin.ts +++ b/x-pack/plugins/enterprise_search/public/plugin.ts @@ -5,26 +5,25 @@ */ import { - Plugin, - PluginInitializerContext, + AppMountParameters, CoreSetup, CoreStart, - AppMountParameters, HttpSetup, + Plugin, + PluginInitializerContext, } from 'src/core/public'; +import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/public'; import { FeatureCatalogueCategory, HomePublicPluginSetup, } from '../../../../src/plugins/home/public'; -import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/public'; import { LicensingPluginSetup } from '../../licensing/public'; - -import { IInitialAppData } from '../common/types'; import { - ENTERPRISE_SEARCH_PLUGIN, APP_SEARCH_PLUGIN, + ENTERPRISE_SEARCH_PLUGIN, WORKPLACE_SEARCH_PLUGIN, } from '../common/constants'; +import { IInitialAppData } from '../common/types'; import { ExternalUrl, IExternalUrl } from './applications/shared/enterprise_search_url'; export interface ClientConfigType { @@ -73,6 +72,7 @@ export class EnterpriseSearchPlugin implements Plugin { core.application.register({ id: APP_SEARCH_PLUGIN.ID, title: APP_SEARCH_PLUGIN.NAME, + euiIconType: ENTERPRISE_SEARCH_PLUGIN.LOGO, appRoute: APP_SEARCH_PLUGIN.URL, category: DEFAULT_APP_CATEGORIES.enterpriseSearch, mount: async (params: AppMountParameters) => { @@ -92,6 +92,7 @@ export class EnterpriseSearchPlugin implements Plugin { core.application.register({ id: WORKPLACE_SEARCH_PLUGIN.ID, title: WORKPLACE_SEARCH_PLUGIN.NAME, + euiIconType: ENTERPRISE_SEARCH_PLUGIN.LOGO, appRoute: WORKPLACE_SEARCH_PLUGIN.URL, category: DEFAULT_APP_CATEGORIES.enterpriseSearch, mount: async (params: AppMountParameters) => { diff --git a/x-pack/plugins/global_search_bar/README.md b/x-pack/plugins/global_search_bar/README.md new file mode 100644 index 0000000000000..e16aac39e3f4e --- /dev/null +++ b/x-pack/plugins/global_search_bar/README.md @@ -0,0 +1,3 @@ +# Kibana GlobalSearchBar plugin + +The GlobalSearchBar plugin provides a search interface for navigating Kibana. (It is the UI to the GlobalSearch plugin.) diff --git a/x-pack/plugins/global_search_bar/kibana.json b/x-pack/plugins/global_search_bar/kibana.json new file mode 100644 index 0000000000000..2d4ffa34d346f --- /dev/null +++ b/x-pack/plugins/global_search_bar/kibana.json @@ -0,0 +1,10 @@ +{ + "id": "globalSearchBar", + "version": "8.0.0", + "kibanaVersion": "kibana", + "server": false, + "ui": true, + "requiredPlugins": ["globalSearch"], + "optionalPlugins": [], + "configPath": ["xpack", "global_search_bar"] +} diff --git a/x-pack/plugins/global_search_bar/public/components/__snapshots__/search_bar.test.tsx.snap b/x-pack/plugins/global_search_bar/public/components/__snapshots__/search_bar.test.tsx.snap new file mode 100644 index 0000000000000..f7e4bfd1c961c --- /dev/null +++ b/x-pack/plugins/global_search_bar/public/components/__snapshots__/search_bar.test.tsx.snap @@ -0,0 +1,75 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`SearchBar correctly filters and sorts results 1`] = ` +Array [ + Object { + "append": undefined, + "className": "euiSelectableTemplateSitewide__listItem", + "key": "Canvas", + "label": "Canvas", + "prepend": undefined, + "title": "Canvasundefinedundefined", + "url": "/app/test/Canvas", + }, + Object { + "append": undefined, + "className": "euiSelectableTemplateSitewide__listItem", + "key": "Discover", + "label": "Discover", + "prepend": undefined, + "title": "Discoverundefinedundefined", + "url": "/app/test/Discover", + }, + Object { + "append": undefined, + "className": "euiSelectableTemplateSitewide__listItem", + "key": "Graph", + "label": "Graph", + "prepend": undefined, + "title": "Graphundefinedundefined", + "url": "/app/test/Graph", + }, +] +`; + +exports[`SearchBar correctly filters and sorts results 2`] = ` +Array [ + Object { + "append": undefined, + "className": "euiSelectableTemplateSitewide__listItem", + "key": "Discover", + "label": "Discover", + "prepend": undefined, + "title": "Discoverundefinedundefined", + "url": "/app/test/Discover", + }, + Object { + "append": undefined, + "className": "euiSelectableTemplateSitewide__listItem", + "key": "My Dashboard", + "label": "My Dashboard", + "meta": Array [ + Object { + "text": "Test", + }, + ], + "prepend": undefined, + "title": "My Dashboard • Test", + "url": "/app/test/My Dashboard", + }, +] +`; + +exports[`SearchBar supports keyboard shortcuts 1`] = ` + +`; diff --git a/x-pack/plugins/global_search_bar/public/components/search_bar.test.tsx b/x-pack/plugins/global_search_bar/public/components/search_bar.test.tsx new file mode 100644 index 0000000000000..0d1e8725b4911 --- /dev/null +++ b/x-pack/plugins/global_search_bar/public/components/search_bar.test.tsx @@ -0,0 +1,97 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { wait } from '@testing-library/react'; +import { of } from 'rxjs'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { + GlobalSearchBatchedResults, + GlobalSearchPluginStart, + GlobalSearchResult, +} from '../../../global_search/public'; +import { globalSearchPluginMock } from '../../../global_search/public/mocks'; +import { SearchBar } from '../components/search_bar'; + +type Result = { id: string; type: string } | string; + +const createResult = (result: Result): GlobalSearchResult => { + const id = typeof result === 'string' ? result : result.id; + const type = typeof result === 'string' ? 'application' : result.type; + + return { + id, + type, + title: id, + url: `/app/test/${id}`, + score: 42, + }; +}; + +const createBatch = (...results: Result[]): GlobalSearchBatchedResults => ({ + results: results.map(createResult), +}); + +jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({ + htmlIdGenerator: () => () => 'mockId', +})); + +const getSelectableProps: any = (component: any) => component.find('EuiSelectable').props(); +const getSearchProps: any = (component: any) => component.find('EuiFieldSearch').props(); + +describe('SearchBar', () => { + let searchService: GlobalSearchPluginStart; + let findSpy: jest.SpyInstance; + + beforeEach(() => { + searchService = globalSearchPluginMock.createStartContract(); + findSpy = jest.spyOn(searchService, 'find'); + jest.useFakeTimers(); + }); + + it('correctly filters and sorts results', async () => { + const navigate = jest.fn(); + findSpy + .mockReturnValueOnce( + of( + createBatch('Discover', 'Canvas'), + createBatch({ id: 'Visualize', type: 'test' }, 'Graph') + ) + ) + .mockReturnValueOnce(of(createBatch('Discover', { id: 'My Dashboard', type: 'test' }))); + + const component = mountWithIntl( + + ); + + expect(findSpy).toHaveBeenCalledTimes(0); + component.find('input[data-test-subj="header-search"]').simulate('focus'); + jest.runAllTimers(); + component.update(); + expect(findSpy).toHaveBeenCalledTimes(1); + expect(findSpy).toHaveBeenCalledWith('', {}); + expect(getSelectableProps(component).options).toMatchSnapshot(); + await wait(() => getSearchProps(component).onSearch('d')); + jest.runAllTimers(); + component.update(); + expect(getSelectableProps(component).options).toMatchSnapshot(); + expect(findSpy).toHaveBeenCalledTimes(2); + expect(findSpy).toHaveBeenCalledWith('d', {}); + }); + + it('supports keyboard shortcuts', () => { + mountWithIntl(); + + const searchEvent = new KeyboardEvent('keydown', { + key: '/', + ctrlKey: true, + metaKey: true, + } as any); + window.dispatchEvent(searchEvent); + + expect(document.activeElement).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/global_search_bar/public/components/search_bar.tsx b/x-pack/plugins/global_search_bar/public/components/search_bar.tsx new file mode 100644 index 0000000000000..25ca1b07321c7 --- /dev/null +++ b/x-pack/plugins/global_search_bar/public/components/search_bar.tsx @@ -0,0 +1,217 @@ +/* + * 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 { + EuiBadge, + EuiFlexGroup, + EuiFlexItem, + EuiSelectableTemplateSitewide, + EuiSelectableTemplateSitewideOption, + EuiText, + EuiSelectableMessage, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { ApplicationStart } from 'kibana/public'; +import React, { useCallback, useState } from 'react'; +import useDebounce from 'react-use/lib/useDebounce'; +import useEvent from 'react-use/lib/useEvent'; +import useMountedState from 'react-use/lib/useMountedState'; +import { GlobalSearchPluginStart, GlobalSearchResult } from '../../../global_search/public'; + +interface Props { + globalSearch: GlobalSearchPluginStart['find']; + navigateToUrl: ApplicationStart['navigateToUrl']; +} + +const clearField = (field: HTMLInputElement) => { + const nativeInputValue = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value'); + const nativeInputValueSetter = nativeInputValue ? nativeInputValue.set : undefined; + if (nativeInputValueSetter) { + nativeInputValueSetter.call(field, ''); + } + + field.dispatchEvent(new Event('change')); +}; + +const cleanMeta = (str: string) => (str.charAt(0).toUpperCase() + str.slice(1)).replace(/-/g, ' '); +const blurEvent = new FocusEvent('blur'); + +export function SearchBar({ globalSearch, navigateToUrl }: Props) { + const isMounted = useMountedState(); + const [searchValue, setSearchValue] = useState(''); + const [searchRef, setSearchRef] = useState(null); + const [options, _setOptions] = useState([] as EuiSelectableTemplateSitewideOption[]); + const isMac = navigator.platform.toLowerCase().indexOf('mac') >= 0; + + const setOptions = useCallback( + (_options: GlobalSearchResult[]) => { + if (!isMounted()) return; + + _setOptions([ + ..._options.map((option) => ({ + key: option.id, + label: option.title, + url: option.url, + ...(option.icon && { icon: { type: option.icon } }), + ...(option.type && + option.type !== 'application' && { meta: [{ text: cleanMeta(option.type) }] }), + })), + ]); + }, + [isMounted, _setOptions] + ); + + useDebounce( + () => { + let arr: GlobalSearchResult[] = []; + globalSearch(searchValue, {}).subscribe({ + next: ({ results }) => { + if (searchValue.length > 0) { + arr = [...results, ...arr].sort((a, b) => { + if (a.score < b.score) return 1; + if (a.score > b.score) return -1; + return 0; + }); + setOptions(arr); + return; + } + + // if searchbar is empty, filter to only applications and sort alphabetically + results = results.filter(({ type }: GlobalSearchResult) => type === 'application'); + + arr = [...results, ...arr].sort((a, b) => { + const titleA = a.title.toUpperCase(); // ignore upper and lowercase + const titleB = b.title.toUpperCase(); // ignore upper and lowercase + if (titleA < titleB) return -1; + if (titleA > titleB) return 1; + return 0; + }); + + setOptions(arr); + }, + error: () => { + // TODO #74430 - add telemetry to see if errors are happening + // Not doing anything on error right now because it'll either just show the previous + // results or empty results which is basically what we want anyways + }, + complete: () => {}, + }); + }, + 250, + [searchValue] + ); + + const onKeyDown = (event: KeyboardEvent) => { + if (event.key === '/' && (isMac ? event.metaKey : event.ctrlKey)) { + if (searchRef) { + event.preventDefault(); + searchRef.focus(); + } + } + }; + + const onChange = (selected: EuiSelectableTemplateSitewideOption[]) => { + // @ts-ignore - ts error is "union type is too complex to express" + const { url } = selected.find(({ checked }) => checked === 'on'); + + navigateToUrl(url); + (document.activeElement as HTMLElement).blur(); + if (searchRef) { + clearField(searchRef); + searchRef.dispatchEvent(blurEvent); + } + }; + + useEvent('keydown', onKeyDown); + + return ( + +

+ +

+

+ +

+ + } + popoverFooter={ + + + + + + ), + commandDescription: ( + + + {isMac ? ( + + ) : ( + + )} + + + ), + }} + /> + Shortcut, + how: ( + + {isMac ? 'Command + /' : 'Control + /'} + + ), + }} + /> + + + } + /> + ); +} diff --git a/x-pack/plugins/global_search_bar/public/index.ts b/x-pack/plugins/global_search_bar/public/index.ts new file mode 100644 index 0000000000000..9e11c43ce872c --- /dev/null +++ b/x-pack/plugins/global_search_bar/public/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { PluginInitializer } from 'src/core/public'; +import { GlobalSearchBarPlugin } from './plugin'; + +export const plugin: PluginInitializer<{}, {}, {}, {}> = () => new GlobalSearchBarPlugin(); diff --git a/x-pack/plugins/global_search_bar/public/plugin.tsx b/x-pack/plugins/global_search_bar/public/plugin.tsx new file mode 100644 index 0000000000000..0c8cc2e64726a --- /dev/null +++ b/x-pack/plugins/global_search_bar/public/plugin.tsx @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CoreStart, Plugin } from 'src/core/public'; +import React from 'react'; +import { I18nProvider } from '@kbn/i18n/react'; +import ReactDOM from 'react-dom'; +import { ApplicationStart } from 'kibana/public'; +import { SearchBar } from '../public/components/search_bar'; +import { GlobalSearchPluginStart } from '../../global_search/public'; + +export interface GlobalSearchBarPluginStartDeps { + globalSearch: GlobalSearchPluginStart; +} + +export class GlobalSearchBarPlugin implements Plugin<{}, {}> { + public async setup() { + return {}; + } + + public start(core: CoreStart, { globalSearch }: GlobalSearchBarPluginStartDeps) { + core.chrome.navControls.registerCenter({ + order: 1000, + mount: (target) => this.mount(target, globalSearch, core.application.navigateToUrl), + }); + return {}; + } + + private mount( + targetDomElement: HTMLElement, + globalSearch: GlobalSearchPluginStart, + navigateToUrl: ApplicationStart['navigateToUrl'] + ) { + ReactDOM.render( + + + , + targetDomElement + ); + + return () => ReactDOM.unmountComponentAtNode(targetDomElement); + } +} diff --git a/x-pack/plugins/graph/public/angular/templates/index.html b/x-pack/plugins/graph/public/angular/templates/index.html index 50385008d7b2b..10bbb2e8ec6c7 100644 --- a/x-pack/plugins/graph/public/angular/templates/index.html +++ b/x-pack/plugins/graph/public/angular/templates/index.html @@ -1,6 +1,6 @@
- +
diff --git a/x-pack/plugins/graph/public/app.js b/x-pack/plugins/graph/public/app.js index fd2b96e0570f6..183f8bddead11 100644 --- a/x-pack/plugins/graph/public/app.js +++ b/x-pack/plugins/graph/public/app.js @@ -53,6 +53,7 @@ export function initGraphApp(angularModule, deps) { graphSavePolicy, overlays, savedObjects, + setHeaderActionMenu, } = deps; const app = angularModule; @@ -465,6 +466,7 @@ export function initGraphApp(angularModule, deps) { }; // ===== Menubar configuration ========= + $scope.setHeaderActionMenu = setHeaderActionMenu; $scope.topNavMenu = []; $scope.topNavMenu.push({ key: 'new', diff --git a/x-pack/plugins/graph/public/application.ts b/x-pack/plugins/graph/public/application.ts index a9ba464016157..90e87ff4ec85e 100644 --- a/x-pack/plugins/graph/public/application.ts +++ b/x-pack/plugins/graph/public/application.ts @@ -27,6 +27,7 @@ import { SavedObjectsClientContract, ToastsStart, OverlayStart, + AppMountParameters, } from 'kibana/public'; // @ts-ignore import { initGraphApp } from './app'; @@ -73,6 +74,7 @@ export interface GraphDependencies { overlays: OverlayStart; savedObjects: SavedObjectsStart; kibanaLegacy: KibanaLegacyStart; + setHeaderActionMenu: AppMountParameters['setHeaderActionMenu']; } export const renderApp = ({ appBasePath, element, kibanaLegacy, ...deps }: GraphDependencies) => { diff --git a/x-pack/plugins/graph/public/components/_app.scss b/x-pack/plugins/graph/public/components/_app.scss index f6984f5369a30..b9b23e596a05e 100644 --- a/x-pack/plugins/graph/public/components/_app.scss +++ b/x-pack/plugins/graph/public/components/_app.scss @@ -1,3 +1,3 @@ .gphGraph__bar { - margin: 0px $euiSizeS $euiSizeS $euiSizeS; + margin: $euiSizeS; } diff --git a/x-pack/plugins/graph/public/plugin.ts b/x-pack/plugins/graph/public/plugin.ts index e452b74ab35e8..6cf9d8be07bc9 100644 --- a/x-pack/plugins/graph/public/plugin.ts +++ b/x-pack/plugins/graph/public/plugin.ts @@ -74,7 +74,7 @@ export class GraphPlugin title: 'Graph', order: 6000, appRoute: '/app/graph', - euiIconType: 'graphApp', + euiIconType: 'logoKibana', category: DEFAULT_APP_CATEGORIES.kibana, mount: async (params: AppMountParameters) => { const [coreStart, pluginsStart] = await core.getStartServices(); diff --git a/x-pack/plugins/infra/public/plugin.ts b/x-pack/plugins/infra/public/plugin.ts index 8a1264d254e40..66715b3fee28b 100644 --- a/x-pack/plugins/infra/public/plugin.ts +++ b/x-pack/plugins/infra/public/plugin.ts @@ -52,7 +52,7 @@ export class Plugin implements InfraClientPluginClass { title: i18n.translate('xpack.infra.logs.pluginTitle', { defaultMessage: 'Logs', }), - euiIconType: 'logsApp', + euiIconType: 'logoObservability', order: 8100, appRoute: '/app/logs', category: DEFAULT_APP_CATEGORIES.observability, @@ -70,7 +70,7 @@ export class Plugin implements InfraClientPluginClass { title: i18n.translate('xpack.infra.metrics.pluginTitle', { defaultMessage: 'Metrics', }), - euiIconType: 'metricsApp', + euiIconType: 'logoObservability', order: 8200, appRoute: '/app/metrics', category: DEFAULT_APP_CATEGORIES.observability, diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/default.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/default.tsx index 30294779d1a3d..7da8330740532 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/default.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/default.tsx @@ -5,11 +5,11 @@ */ import React from 'react'; import styled from 'styled-components'; -import { EuiTabs, EuiTab, EuiFlexGroup, EuiFlexItem, EuiIcon, EuiButtonEmpty } from '@elastic/eui'; +import { EuiTabs, EuiTab, EuiFlexGroup, EuiFlexItem, EuiButtonEmpty } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { Section } from '../sections'; import { AlphaMessaging, SettingFlyout } from '../components'; -import { useLink, useConfig, useCore } from '../hooks'; +import { useLink, useConfig } from '../hooks'; interface Props { showSettings?: boolean; @@ -42,7 +42,6 @@ export const DefaultLayout: React.FunctionComponent = ({ }) => { const { getHref } = useLink(); const { fleet } = useConfig(); - const { uiSettings } = useCore(); const [isSettingsFlyoutOpen, setIsSettingsFlyoutOpen] = React.useState(false); return ( @@ -58,11 +57,6 @@ export const DefaultLayout: React.FunctionComponent = ({