From 138f2cda0dd791c9c7495e97d50ffda4b7af7354 Mon Sep 17 00:00:00 2001 From: Hannah Mudge Date: Wed, 14 Sep 2022 08:09:45 -0600 Subject: [PATCH 1/4] [Dashboard] Services abstraction (#139145) * Service abstraction for data service * Remove unnecessary files * Fix jest tests for new data service * Service abstraction for HTTP service + fix Jest tests * Services abstraction for visualizations service * Services abstraction for data views editor service * Services abstraction for spaces service * Services abstraction for settings service * Remove more unnecessary files + clean up imports * Add theme to new settings service + fix imports * Services abstraction for overlays service * Services abstraction for navigation service * Services abstraction for chrome service * Services abstraction for embeddable service + fix Jest tests * Services abstraction for notifications service * Services abstraction for dashboard capabilities service * Remove unecessary context provider * Services abstraction for screenshot mode service * Services abstraction for url forwarding service * Services abstraction for share service * Services abstraction for usage collection service * Another round of code cleanup * Services abstraction for initializer context service * Clean up more unecessary files * Services abstraction for application service * Remove duplicated saved object client service * Services abstraction for saved objects service * Services abstraction for saved objects tagging service * Services abstraction for dashboard session storage service * Services abstraction for core context service * Clean up initializer context code * Refactor to create singular no data page context service * Create and use dashboard mount context provider * Clean up + abstraction for docs link service * Remove dashboard container services * Temporarily revert back to deprecated fullscreen button * Remove PlaceholderEmbeddableServices + more code clean up * More code clean up * Clean up plugin code * Investigated possibly flaky test - doesn't actually seem to be flaky * Update snapshot for new services * Clean up actions code * Clean up dashboard container code * Add to documentation links service to remove type casting * Address feedback * Undo move of services in dashboard container factory --- src/plugins/dashboard/jest.config.js | 1 + src/plugins/dashboard/jest_setup.ts | 12 + .../public/application/_dashboard_app.scss | 13 - .../actions/add_to_library_action.test.tsx | 105 +++---- .../actions/add_to_library_action.tsx | 41 +-- .../actions/clone_panel_action.test.tsx | 52 ++-- .../actions/clone_panel_action.tsx | 28 +- .../actions/copy_to_dashboard_action.tsx | 46 +-- .../actions/copy_to_dashboard_modal.tsx | 25 +- .../actions/expand_panel_action.test.tsx | 45 +-- .../actions/expand_panel_action.tsx | 9 +- .../actions/export_csv_action.test.tsx | 53 ++-- .../application/actions/export_csv_action.tsx | 36 +-- .../filters_notification_badge.test.tsx | 51 +--- .../actions/filters_notification_badge.tsx | 56 ++-- .../filters_notification_modal.test.tsx | 45 +-- .../library_notification_action.test.tsx | 49 +--- .../actions/library_notification_action.tsx | 24 +- .../library_notification_popover.test.tsx | 46 +-- .../actions/open_replace_panel_flyout.tsx | 37 ++- .../actions/replace_panel_action.test.tsx | 85 ++---- .../actions/replace_panel_action.tsx | 17 +- .../actions/replace_panel_flyout.tsx | 24 +- .../unlink_from_library_action.test.tsx | 65 ++--- .../actions/unlink_from_library_action.tsx | 26 +- .../public/application/dashboard_app.tsx | 60 ++-- .../application/dashboard_app_no_data.tsx | 36 ++- .../public/application/dashboard_router.tsx | 134 +++------ .../embeddable/dashboard_container.test.tsx | 102 +++---- .../embeddable/dashboard_container.tsx | 124 +++----- .../dashboard_container_by_value_renderer.tsx | 2 +- .../dashboard_container_factory.tsx | 25 +- .../dashboard_empty_screen.test.tsx | 17 +- .../empty_screen/dashboard_empty_screen.tsx | 21 +- .../embeddable/grid/dashboard_grid.test.tsx | 109 +++---- .../embeddable/grid/dashboard_grid.tsx | 49 ++-- .../embeddable/grid/dashboard_grid_item.tsx | 23 +- .../panel/create_panel_state.test.ts | 2 +- .../embeddable/panel/create_panel_state.ts | 2 +- .../panel/dashboard_panel_placement.ts | 2 +- .../placeholder/placeholder_embeddable.tsx | 27 +- .../placeholder_embeddable_factory.ts | 13 +- .../viewport/dashboard_viewport.test.tsx | 117 +++----- .../viewport/dashboard_viewport.tsx | 44 +-- .../hooks/dashboard_mount_context.ts | 23 ++ .../hooks/use_dashboard_app_state.test.tsx | 120 ++++---- .../hooks/use_dashboard_app_state.ts | 84 ++---- .../lib/build_dashboard_container.ts | 46 ++- .../lib/convert_dashboard_panels.ts | 9 +- .../lib/convert_dashboard_state.ts | 76 +++-- .../lib/dashboard_session_restoration.ts | 33 +-- .../application/lib/dashboard_tagging.ts | 23 +- .../lib/diff_dashboard_state.test.ts | 2 +- .../application/lib/diff_dashboard_state.ts | 2 +- .../public/application/lib/filter_utils.ts | 6 +- .../public/application/lib/help_menu_util.ts | 13 +- .../dashboard/public/application/lib/index.ts | 1 - .../lib/load_dashboard_by_title.ts | 11 +- .../lib/load_saved_dashboard_state.ts | 29 +- .../application/lib/migrate_app_state.test.ts | 14 +- .../application/lib/migrate_app_state.ts | 26 +- .../application/lib/migrate_legacy_query.ts | 3 +- .../public/application/lib/save_dashboard.ts | 52 ++-- .../lib/session_restoration.test.ts | 28 +- .../lib/sync_dashboard_container_input.ts | 24 +- .../lib/sync_dashboard_data_views.ts | 13 +- .../lib/sync_dashboard_filter_state.ts | 33 ++- .../lib/sync_dashboard_url_state.ts | 6 +- .../dashboard_listing.test.tsx.snap | 232 ++------------- .../application/listing/confirm_overlays.tsx | 51 ++-- .../listing/dashboard_listing.test.tsx | 49 ++-- .../application/listing/dashboard_listing.tsx | 113 ++++---- .../listing/dashboard_no_match.tsx | 31 +- .../dashboard_unsaved_listing.test.tsx | 33 ++- .../listing/dashboard_unsaved_listing.tsx | 25 +- .../get_dashboard_list_item_link.test.ts | 59 +--- .../listing/get_dashboard_list_item_link.ts | 13 +- .../state/dashboard_state_slice.ts | 6 +- .../get_sample_dashboard_input.ts | 2 +- .../test_helpers/make_default_services.ts | 70 +---- .../application/top_nav/dashboard_top_nav.tsx | 206 ++++++-------- .../application/top_nav/editor_menu.tsx | 58 ++-- .../application/top_nav/get_top_nav_config.ts | 2 +- .../public/application/top_nav/save_modal.tsx | 16 +- .../application/top_nav/show_clone_modal.tsx | 15 +- .../top_nav/show_options_popover.tsx | 14 +- .../top_nav/show_share_modal.test.tsx | 56 ++-- .../application/top_nav/show_share_modal.tsx | 57 ++-- .../dashboard/public/dashboard_strings.ts | 7 +- src/plugins/dashboard/public/plugin.tsx | 267 +++++++----------- .../saved_dashboards/saved_dashboard.ts | 8 +- .../saved_dashboards/saved_dashboards.ts | 6 +- .../services/analytics/analytics.stub.ts | 21 ++ .../services/analytics/analytics_service.ts | 26 ++ .../public/services/analytics/types.ts | 13 + .../services/application/application.stub.ts | 30 ++ .../application/application_service.ts | 41 +++ .../public/services/application/types.ts | 22 ++ .../public/services/chrome/chrome.stub.ts | 27 ++ .../public/services/chrome/chrome_service.ts | 40 +++ .../dashboard/public/services/chrome/types.ts | 19 ++ .../core_context/core_context.stub.ts | 21 ++ .../core_context/core_context_service.ts | 28 ++ .../public/services/core_context/types.ts | 14 + .../dashboard_capabilities.stub.ts | 29 ++ .../dashboard_capabilities_service.ts | 36 +++ .../services/dashboard_capabilities/types.ts | 18 ++ .../dashboard_session_storage.stub.ts | 25 ++ .../dashboard_session_storage_service.ts} | 57 +++- .../dashboard_session_storage/types.ts | 17 ++ .../public/services/data/data.stub.ts | 18 ++ .../public/services/data/data_service.ts | 29 ++ .../services/{home.ts => data/types.ts} | 10 +- .../data_view_editor/data_view_editor.stub.ts | 22 ++ .../data_view_editor_service.ts | 27 ++ .../{core.ts => data_view_editor/types.ts} | 14 +- .../documentation_links.stub.ts | 22 ++ .../documentation_links_service.ts | 34 +++ .../types.ts} | 12 +- .../services/embeddable/embeddable.stub.ts | 24 ++ .../services/embeddable/embeddable_service.ts | 28 ++ .../public/services/embeddable/types.ts | 16 ++ .../public/services/http/http.stub.ts | 21 ++ .../public/services/http/http_service.ts | 25 ++ .../services/{data.ts => http/types.ts} | 7 +- .../initializer_context.stub.ts | 23 ++ .../initializer_context_service.ts | 31 ++ .../types.ts} | 5 +- .../dashboard/public/services/kibana_react.ts | 24 -- .../dashboard/public/services/kibana_utils.ts | 26 -- .../services/navigation/navigation.stub.ts | 21 ++ .../services/navigation/navigation_service.ts | 27 ++ .../{navigation.ts => navigation/types.ts} | 6 +- .../notifications/notifications.stub.ts | 21 ++ .../notifications/notifications_service.ts | 26 ++ .../public/services/notifications/types.ts | 13 + .../public/services/overlays/overlays.stub.ts | 24 ++ .../services/overlays/overlays_service.ts | 29 ++ .../public/services/overlays/types.ts | 16 ++ .../public/services/plugin_services.stub.ts | 69 +++++ .../public/services/plugin_services.ts | 77 +++++ .../public/services/presentation_util.ts | 10 - .../public/services/saved_object_loader.ts | 2 +- .../public/services/saved_objects.ts | 21 -- .../saved_objects/saved_objects.stub.ts | 21 ++ .../saved_objects/saved_objects_service.ts | 26 ++ .../{data_views.ts => saved_objects/types.ts} | 6 +- .../saved_objects_tagging.stub.ts | 28 ++ .../saved_objects_tagging_service.ts | 45 +++ .../services/saved_objects_tagging/types.ts | 19 ++ .../services/saved_objects_tagging_oss.ts | 14 - .../public/services/screenshot_mode.ts | 12 - .../screenshot_mode/screenshot_mode.stub.ts | 22 ++ .../screenshot_mode_service.ts | 26 ++ .../public/services/screenshot_mode/types.ts | 14 + .../public/services/settings/settings.stub.ts | 22 ++ .../services/settings/settings_service.ts | 32 +++ .../public/services/settings/types.ts | 16 ++ .../dashboard/public/services/share.ts | 10 - .../public/services/share/share.stub.ts | 20 ++ .../public/services/share/share_services.ts | 27 ++ .../dashboard/public/services/share/types.ts | 13 + .../public/services/spaces/spaces.stub.ts | 23 ++ .../public/services/spaces/spaces_service.ts | 33 +++ .../services/{spaces.ts => spaces/types.ts} | 8 +- .../public/services/string_utils.test.ts | 22 -- .../dashboard/public/services/types.ts | 66 +++++ .../dashboard/public/services/ui_actions.ts | 10 - .../public/services/url_forwarding/types.ts | 13 + .../url_forwarding/url_forwarding_service.ts | 25 ++ .../url_forwarding/url_fowarding.stub.ts | 21 ++ .../public/services/usage_collection.ts | 9 - .../public/services/usage_collection/types.ts | 13 + .../usage_collection/usage_collection.stub.ts | 21 ++ .../usage_collection_service.ts | 25 ++ .../public/services/visualizations/types.ts | 16 ++ .../visualizations/visualizations.stub.ts | 24 ++ .../visualizations/visualizations_service.ts | 29 ++ src/plugins/dashboard/public/types.ts | 104 ++----- .../public/data_views/mocks.ts} | 2 +- src/plugins/data/public/mocks.ts | 18 +- .../timefilter/timefilter_service.mock.ts | 8 +- src/plugins/data_views/public/mocks.ts | 1 + .../contact_card_embeddable_factory.tsx | 4 + ...act_card_exportable_embeddable_factory.tsx | 4 + .../apps/dashboard/group1/empty_dashboard.ts | 67 ----- .../functional/apps/dashboard/group1/index.ts | 1 - .../functional/page_objects/dashboard_page.ts | 2 + ...inment_alert_type_expression.test.tsx.snap | 30 ++ .../translations/translations/fr-FR.json | 1 - .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - 192 files changed, 3435 insertions(+), 2777 deletions(-) create mode 100644 src/plugins/dashboard/jest_setup.ts create mode 100644 src/plugins/dashboard/public/application/hooks/dashboard_mount_context.ts create mode 100644 src/plugins/dashboard/public/services/analytics/analytics.stub.ts create mode 100644 src/plugins/dashboard/public/services/analytics/analytics_service.ts create mode 100644 src/plugins/dashboard/public/services/analytics/types.ts create mode 100644 src/plugins/dashboard/public/services/application/application.stub.ts create mode 100644 src/plugins/dashboard/public/services/application/application_service.ts create mode 100644 src/plugins/dashboard/public/services/application/types.ts create mode 100644 src/plugins/dashboard/public/services/chrome/chrome.stub.ts create mode 100644 src/plugins/dashboard/public/services/chrome/chrome_service.ts create mode 100644 src/plugins/dashboard/public/services/chrome/types.ts create mode 100644 src/plugins/dashboard/public/services/core_context/core_context.stub.ts create mode 100644 src/plugins/dashboard/public/services/core_context/core_context_service.ts create mode 100644 src/plugins/dashboard/public/services/core_context/types.ts create mode 100644 src/plugins/dashboard/public/services/dashboard_capabilities/dashboard_capabilities.stub.ts create mode 100644 src/plugins/dashboard/public/services/dashboard_capabilities/dashboard_capabilities_service.ts create mode 100644 src/plugins/dashboard/public/services/dashboard_capabilities/types.ts create mode 100644 src/plugins/dashboard/public/services/dashboard_session_storage/dashboard_session_storage.stub.ts rename src/plugins/dashboard/public/{application/lib/dashboard_session_storage.ts => services/dashboard_session_storage/dashboard_session_storage_service.ts} (62%) create mode 100644 src/plugins/dashboard/public/services/dashboard_session_storage/types.ts create mode 100644 src/plugins/dashboard/public/services/data/data.stub.ts create mode 100644 src/plugins/dashboard/public/services/data/data_service.ts rename src/plugins/dashboard/public/services/{home.ts => data/types.ts} (55%) create mode 100644 src/plugins/dashboard/public/services/data_view_editor/data_view_editor.stub.ts create mode 100644 src/plugins/dashboard/public/services/data_view_editor/data_view_editor_service.ts rename src/plugins/dashboard/public/services/{core.ts => data_view_editor/types.ts} (60%) create mode 100644 src/plugins/dashboard/public/services/documentation_links/documentation_links.stub.ts create mode 100644 src/plugins/dashboard/public/services/documentation_links/documentation_links_service.ts rename src/plugins/dashboard/public/services/{string_utils.ts => documentation_links/types.ts} (57%) create mode 100644 src/plugins/dashboard/public/services/embeddable/embeddable.stub.ts create mode 100644 src/plugins/dashboard/public/services/embeddable/embeddable_service.ts create mode 100644 src/plugins/dashboard/public/services/embeddable/types.ts create mode 100644 src/plugins/dashboard/public/services/http/http.stub.ts create mode 100644 src/plugins/dashboard/public/services/http/http_service.ts rename src/plugins/dashboard/public/services/{data.ts => http/types.ts} (72%) create mode 100644 src/plugins/dashboard/public/services/initializer_context/initializer_context.stub.ts create mode 100644 src/plugins/dashboard/public/services/initializer_context/initializer_context_service.ts rename src/plugins/dashboard/public/services/{embeddable_test_samples.ts => initializer_context/types.ts} (75%) delete mode 100644 src/plugins/dashboard/public/services/kibana_react.ts delete mode 100644 src/plugins/dashboard/public/services/kibana_utils.ts create mode 100644 src/plugins/dashboard/public/services/navigation/navigation.stub.ts create mode 100644 src/plugins/dashboard/public/services/navigation/navigation_service.ts rename src/plugins/dashboard/public/services/{navigation.ts => navigation/types.ts} (67%) create mode 100644 src/plugins/dashboard/public/services/notifications/notifications.stub.ts create mode 100644 src/plugins/dashboard/public/services/notifications/notifications_service.ts create mode 100644 src/plugins/dashboard/public/services/notifications/types.ts create mode 100644 src/plugins/dashboard/public/services/overlays/overlays.stub.ts create mode 100644 src/plugins/dashboard/public/services/overlays/overlays_service.ts create mode 100644 src/plugins/dashboard/public/services/overlays/types.ts create mode 100644 src/plugins/dashboard/public/services/plugin_services.stub.ts create mode 100644 src/plugins/dashboard/public/services/plugin_services.ts delete mode 100644 src/plugins/dashboard/public/services/presentation_util.ts delete mode 100644 src/plugins/dashboard/public/services/saved_objects.ts create mode 100644 src/plugins/dashboard/public/services/saved_objects/saved_objects.stub.ts create mode 100644 src/plugins/dashboard/public/services/saved_objects/saved_objects_service.ts rename src/plugins/dashboard/public/services/{data_views.ts => saved_objects/types.ts} (70%) create mode 100644 src/plugins/dashboard/public/services/saved_objects_tagging/saved_objects_tagging.stub.ts create mode 100644 src/plugins/dashboard/public/services/saved_objects_tagging/saved_objects_tagging_service.ts create mode 100644 src/plugins/dashboard/public/services/saved_objects_tagging/types.ts delete mode 100644 src/plugins/dashboard/public/services/saved_objects_tagging_oss.ts delete mode 100644 src/plugins/dashboard/public/services/screenshot_mode.ts create mode 100644 src/plugins/dashboard/public/services/screenshot_mode/screenshot_mode.stub.ts create mode 100644 src/plugins/dashboard/public/services/screenshot_mode/screenshot_mode_service.ts create mode 100644 src/plugins/dashboard/public/services/screenshot_mode/types.ts create mode 100644 src/plugins/dashboard/public/services/settings/settings.stub.ts create mode 100644 src/plugins/dashboard/public/services/settings/settings_service.ts create mode 100644 src/plugins/dashboard/public/services/settings/types.ts delete mode 100644 src/plugins/dashboard/public/services/share.ts create mode 100644 src/plugins/dashboard/public/services/share/share.stub.ts create mode 100644 src/plugins/dashboard/public/services/share/share_services.ts create mode 100644 src/plugins/dashboard/public/services/share/types.ts create mode 100644 src/plugins/dashboard/public/services/spaces/spaces.stub.ts create mode 100644 src/plugins/dashboard/public/services/spaces/spaces_service.ts rename src/plugins/dashboard/public/services/{spaces.ts => spaces/types.ts} (52%) delete mode 100644 src/plugins/dashboard/public/services/string_utils.test.ts create mode 100644 src/plugins/dashboard/public/services/types.ts delete mode 100644 src/plugins/dashboard/public/services/ui_actions.ts create mode 100644 src/plugins/dashboard/public/services/url_forwarding/types.ts create mode 100644 src/plugins/dashboard/public/services/url_forwarding/url_forwarding_service.ts create mode 100644 src/plugins/dashboard/public/services/url_forwarding/url_fowarding.stub.ts delete mode 100644 src/plugins/dashboard/public/services/usage_collection.ts create mode 100644 src/plugins/dashboard/public/services/usage_collection/types.ts create mode 100644 src/plugins/dashboard/public/services/usage_collection/usage_collection.stub.ts create mode 100644 src/plugins/dashboard/public/services/usage_collection/usage_collection_service.ts create mode 100644 src/plugins/dashboard/public/services/visualizations/types.ts create mode 100644 src/plugins/dashboard/public/services/visualizations/visualizations.stub.ts create mode 100644 src/plugins/dashboard/public/services/visualizations/visualizations_service.ts rename src/plugins/{dashboard/public/services/embeddable.ts => data/public/data_views/mocks.ts} (87%) delete mode 100644 test/functional/apps/dashboard/group1/empty_dashboard.ts diff --git a/src/plugins/dashboard/jest.config.js b/src/plugins/dashboard/jest.config.js index d99cfe57fcd37..2295f64f04061 100644 --- a/src/plugins/dashboard/jest.config.js +++ b/src/plugins/dashboard/jest.config.js @@ -14,4 +14,5 @@ module.exports = { coverageDirectory: '/target/kibana-coverage/jest/src/plugins/dashboard', coverageReporters: ['text', 'html'], collectCoverageFrom: ['/src/plugins/dashboard/{common,public,server}/**/*.{ts,tsx}'], + setupFiles: ['/src/plugins/dashboard/jest_setup.ts'], }; diff --git a/src/plugins/dashboard/jest_setup.ts b/src/plugins/dashboard/jest_setup.ts new file mode 100644 index 0000000000000..5683ecd4e288b --- /dev/null +++ b/src/plugins/dashboard/jest_setup.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { pluginServices } from './public/services/plugin_services'; +import { registry } from './public/services/plugin_services.stub'; + +pluginServices.setRegistry(registry.start({})); diff --git a/src/plugins/dashboard/public/application/_dashboard_app.scss b/src/plugins/dashboard/public/application/_dashboard_app.scss index 435659b685280..aa5776c7e5332 100644 --- a/src/plugins/dashboard/public/application/_dashboard_app.scss +++ b/src/plugins/dashboard/public/application/_dashboard_app.scss @@ -61,16 +61,3 @@ flex-direction: column; } } - -// Temporary fix for two tone icons to make them monochrome -.dshSolutionToolbar__editorContextMenu--dark { - .euiIcon path { - fill: $euiColorGhost; - } -} - -.dshSolutionToolbar__editorContextMenu--light { - .euiIcon path { - fill: $euiColorInk; - } -} diff --git a/src/plugins/dashboard/public/application/actions/add_to_library_action.test.tsx b/src/plugins/dashboard/public/application/actions/add_to_library_action.test.tsx index b682dda9dc3dc..ac467e35729f8 100644 --- a/src/plugins/dashboard/public/application/actions/add_to_library_action.test.tsx +++ b/src/plugins/dashboard/public/application/actions/add_to_library_action.test.tsx @@ -9,13 +9,7 @@ import { AddToLibraryAction } from '.'; import { DashboardContainer } from '../embeddable/dashboard_container'; import { getSampleDashboardInput } from '../test_helpers'; - -import { CoreStart } from '@kbn/core/public'; - -import { coreMock, uiSettingsServiceMock } from '@kbn/core/public/mocks'; import { embeddablePluginMock } from '@kbn/embeddable-plugin/public/mocks'; -import { getStubPluginServices } from '@kbn/presentation-util-plugin/public'; -import { screenshotModePluginMock } from '@kbn/screenshot-mode-plugin/public/mocks'; import { EmbeddableInput, @@ -24,53 +18,38 @@ import { isErrorEmbeddable, ReferenceOrValueEmbeddable, ViewMode, -} from '../../services/embeddable'; +} from '@kbn/embeddable-plugin/public'; import { ContactCardEmbeddable, ContactCardEmbeddableFactory, ContactCardEmbeddableInput, ContactCardEmbeddableOutput, CONTACT_CARD_EMBEDDABLE, -} from '../../services/embeddable_test_samples'; - -const { setup, doStart } = embeddablePluginMock.createInstance(); -setup.registerEmbeddableFactory( - CONTACT_CARD_EMBEDDABLE, - new ContactCardEmbeddableFactory((() => null) as any, {} as any) -); -const start = doStart(); +} from '@kbn/embeddable-plugin/public/lib/test_samples/embeddables'; +import { pluginServices } from '../../services/plugin_services'; +const embeddableFactory = new ContactCardEmbeddableFactory((() => null) as any, {} as any); +pluginServices.getServices().embeddable.getEmbeddableFactory = jest + .fn() + .mockReturnValue(embeddableFactory); let container: DashboardContainer; let embeddable: ContactCardEmbeddable & ReferenceOrValueEmbeddable; -let coreStart: CoreStart; -let capabilities: CoreStart['application']['capabilities']; -beforeEach(async () => { - coreStart = coreMock.createStart(); - capabilities = { - ...coreStart.application.capabilities, - visualize: { save: true }, - maps: { save: true }, - }; +const defaultCapabilities = { + advancedSettings: {}, + visualize: { save: true }, + maps: { save: true }, + navLinks: {}, +}; - const containerOptions = { - ExitFullScreenButton: () => null, - SavedObjectFinder: () => null, - application: {} as any, - embeddable: start, - inspector: {} as any, - notifications: {} as any, - overlays: coreStart.overlays, - savedObjectMetaData: {} as any, - uiActions: {} as any, - uiSettings: uiSettingsServiceMock.createStartContract(), - http: coreStart.http, - theme: coreStart.theme, - presentationUtil: getStubPluginServices(), - screenshotMode: screenshotModePluginMock.createSetupContract(), - }; +Object.defineProperty(pluginServices.getServices().application, 'capabilities', { + value: defaultCapabilities, +}); + +beforeEach(async () => { + pluginServices.getServices().application.capabilities = defaultCapabilities; - container = new DashboardContainer(getSampleDashboardInput(), containerOptions); + container = new DashboardContainer(getSampleDashboardInput()); const contactCardEmbeddable = await container.addNewEmbeddable< ContactCardEmbeddableInput, @@ -95,10 +74,7 @@ beforeEach(async () => { }); test('Add to library is incompatible with Error Embeddables', async () => { - const action = new AddToLibraryAction({ - toasts: coreStart.notifications.toasts, - capabilities, - }); + const action = new AddToLibraryAction(); const errorEmbeddable = new ErrorEmbeddable( 'Wow what an awful error', { id: ' 404' }, @@ -108,36 +84,28 @@ test('Add to library is incompatible with Error Embeddables', async () => { }); test('Add to library is incompatible on visualize embeddable without visualize save permissions', async () => { - const action = new AddToLibraryAction({ - toasts: coreStart.notifications.toasts, - capabilities: { ...capabilities, visualize: { save: false } }, - }); + pluginServices.getServices().application.capabilities = { + ...defaultCapabilities, + visualize: { save: false }, + }; + const action = new AddToLibraryAction(); expect(await action.isCompatible({ embeddable })).toBe(false); }); test('Add to library is compatible when embeddable on dashboard has value type input', async () => { - const action = new AddToLibraryAction({ - toasts: coreStart.notifications.toasts, - capabilities, - }); + const action = new AddToLibraryAction(); embeddable.updateInput(await embeddable.getInputAsValueType()); expect(await action.isCompatible({ embeddable })).toBe(true); }); test('Add to library is not compatible when embeddable input is by reference', async () => { - const action = new AddToLibraryAction({ - toasts: coreStart.notifications.toasts, - capabilities, - }); + const action = new AddToLibraryAction(); embeddable.updateInput(await embeddable.getInputAsRefType()); expect(await action.isCompatible({ embeddable })).toBe(false); }); test('Add to library is not compatible when view mode is set to view', async () => { - const action = new AddToLibraryAction({ - toasts: coreStart.notifications.toasts, - capabilities, - }); + const action = new AddToLibraryAction(); embeddable.updateInput(await embeddable.getInputAsRefType()); embeddable.updateInput({ viewMode: ViewMode.VIEW }); expect(await action.isCompatible({ embeddable })).toBe(false); @@ -158,10 +126,7 @@ test('Add to library is not compatible when embeddable is not in a dashboard con mockedByReferenceInput: { savedObjectId: 'test', id: orphanContactCard.id }, mockedByValueInput: { firstName: 'Kibanana', id: orphanContactCard.id }, }); - const action = new AddToLibraryAction({ - toasts: coreStart.notifications.toasts, - capabilities, - }); + const action = new AddToLibraryAction(); expect(await action.isCompatible({ embeddable: orphanContactCard })).toBe(false); }); @@ -170,10 +135,7 @@ test('Add to library replaces embeddableId and retains panel count', async () => const originalPanelCount = Object.keys(dashboard.getInput().panels).length; const originalPanelKeySet = new Set(Object.keys(dashboard.getInput().panels)); - const action = new AddToLibraryAction({ - toasts: coreStart.notifications.toasts, - capabilities, - }); + const action = new AddToLibraryAction(); await action.execute({ embeddable }); expect(Object.keys(container.getInput().panels).length).toEqual(originalPanelCount); @@ -199,10 +161,7 @@ test('Add to library returns reference type input', async () => { }); const dashboard = embeddable.getRoot() as IContainer; const originalPanelKeySet = new Set(Object.keys(dashboard.getInput().panels)); - const action = new AddToLibraryAction({ - toasts: coreStart.notifications.toasts, - capabilities, - }); + const action = new AddToLibraryAction(); await action.execute({ embeddable }); const newPanelId = Object.keys(container.getInput().panels).find( (key) => !originalPanelKeySet.has(key) diff --git a/src/plugins/dashboard/public/application/actions/add_to_library_action.tsx b/src/plugins/dashboard/public/application/actions/add_to_library_action.tsx index 456af80c1bb4c..8c6577012161d 100644 --- a/src/plugins/dashboard/public/application/actions/add_to_library_action.tsx +++ b/src/plugins/dashboard/public/application/actions/add_to_library_action.tsx @@ -6,19 +6,20 @@ * Side Public License, v 1. */ -import { Action, IncompatibleActionError } from '../../services/ui_actions'; import { ViewMode, - PanelState, - IEmbeddable, + type PanelState, + type IEmbeddable, PanelNotFoundError, - EmbeddableInput, - isReferenceOrValueEmbeddable, + type EmbeddableInput, isErrorEmbeddable, -} from '../../services/embeddable'; -import { ApplicationStart, NotificationsStart } from '../../services/core'; + isReferenceOrValueEmbeddable, +} from '@kbn/embeddable-plugin/public'; +import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; + import { dashboardAddToLibraryAction } from '../../dashboard_strings'; -import { DashboardPanelState, DASHBOARD_CONTAINER_TYPE, DashboardContainer } from '..'; +import { type DashboardPanelState, DASHBOARD_CONTAINER_TYPE, type DashboardContainer } from '..'; +import { pluginServices } from '../../services/plugin_services'; export const ACTION_ADD_TO_LIBRARY = 'saveToLibrary'; @@ -31,12 +32,15 @@ export class AddToLibraryAction implements Action { public readonly id = ACTION_ADD_TO_LIBRARY; public order = 15; - constructor( - private deps: { - toasts: NotificationsStart['toasts']; - capabilities: ApplicationStart['capabilities']; - } - ) {} + private applicationCapabilities; + private toastsService; + + constructor() { + ({ + application: { capabilities: this.applicationCapabilities }, + notifications: { toasts: this.toastsService }, + } = pluginServices.getServices()); + } public getDisplayName({ embeddable }: AddToLibraryActionContext) { if (!embeddable.getRoot() || !embeddable.getRoot().isContainer) { @@ -54,10 +58,8 @@ export class AddToLibraryAction implements Action { public async isCompatible({ embeddable }: AddToLibraryActionContext) { // TODO: Fix this, potentially by adding a 'canSave' function to embeddable interface - const canSave = - embeddable.type === 'map' - ? this.deps.capabilities.maps?.save - : this.deps.capabilities.visualize.save; + const { maps, visualize } = this.applicationCapabilities; + const canSave = embeddable.type === 'map' ? maps.save : visualize.save; return Boolean( canSave && @@ -75,7 +77,6 @@ export class AddToLibraryAction implements Action { if (!isReferenceOrValueEmbeddable(embeddable)) { throw new IncompatibleActionError(); } - const newInput = await embeddable.getInputAsRefType(); embeddable.updateInput(newInput); @@ -95,7 +96,7 @@ export class AddToLibraryAction implements Action { const title = dashboardAddToLibraryAction.getSuccessMessage( embeddable.getTitle() ? `'${embeddable.getTitle()}'` : '' ); - this.deps.toasts.addSuccess({ + this.toastsService.addSuccess({ title, 'data-test-subj': 'addPanelToLibrarySuccess', }); diff --git a/src/plugins/dashboard/public/application/actions/clone_panel_action.test.tsx b/src/plugins/dashboard/public/application/actions/clone_panel_action.test.tsx index b79d663df3add..5bb331e58fe38 100644 --- a/src/plugins/dashboard/public/application/actions/clone_panel_action.test.tsx +++ b/src/plugins/dashboard/public/application/actions/clone_panel_action.test.tsx @@ -10,7 +10,7 @@ import { DashboardPanelState } from '../embeddable'; import { DashboardContainer } from '../embeddable/dashboard_container'; import { getSampleDashboardInput, getSampleDashboardPanel } from '../test_helpers'; -import { coreMock, uiSettingsServiceMock } from '@kbn/core/public/mocks'; +import { coreMock } from '@kbn/core/public/mocks'; import { CoreStart } from '@kbn/core/public'; import { ClonePanelAction } from '.'; import { embeddablePluginMock } from '@kbn/embeddable-plugin/public/mocks'; @@ -20,17 +20,9 @@ import { ContactCardEmbeddableInput, ContactCardEmbeddableOutput, CONTACT_CARD_EMBEDDABLE, -} from '../../services/embeddable_test_samples'; -import { ErrorEmbeddable, IContainer, isErrorEmbeddable } from '../../services/embeddable'; -import { getStubPluginServices } from '@kbn/presentation-util-plugin/public'; -import { screenshotModePluginMock } from '@kbn/screenshot-mode-plugin/public/mocks'; - -const { setup, doStart } = embeddablePluginMock.createInstance(); -setup.registerEmbeddableFactory( - CONTACT_CARD_EMBEDDABLE, - new ContactCardEmbeddableFactory((() => null) as any, {} as any) -); -const start = doStart(); +} from '@kbn/embeddable-plugin/public/lib/test_samples/embeddables'; +import { ErrorEmbeddable, IContainer, isErrorEmbeddable } from '@kbn/embeddable-plugin/public'; +import { pluginServices } from '../../services/plugin_services'; let container: DashboardContainer; let byRefOrValEmbeddable: ContactCardEmbeddable; @@ -45,22 +37,6 @@ beforeEach(async () => { create: jest.fn().mockImplementation(() => ({ id: 'brandNewSavedObject' })), }; - const options = { - ExitFullScreenButton: () => null, - SavedObjectFinder: () => null, - application: {} as any, - embeddable: start, - inspector: {} as any, - notifications: {} as any, - overlays: coreStart.overlays, - savedObjectMetaData: {} as any, - uiActions: {} as any, - uiSettings: uiSettingsServiceMock.createStartContract(), - http: coreStart.http, - theme: coreStart.theme, - presentationUtil: getStubPluginServices(), - screenshotMode: screenshotModePluginMock.createSetupContract(), - }; const input = getSampleDashboardInput({ panels: { '123': getSampleDashboardPanel({ @@ -69,7 +45,11 @@ beforeEach(async () => { }), }, }); - container = new DashboardContainer(input, options); + const mockEmbeddableFactory = new ContactCardEmbeddableFactory((() => null) as any, {} as any); + pluginServices.getServices().embeddable.getEmbeddableFactory = jest + .fn() + .mockReturnValue(mockEmbeddableFactory); + container = new DashboardContainer(input); const refOrValContactCardEmbeddable = await container.addNewEmbeddable< ContactCardEmbeddableInput, @@ -107,7 +87,7 @@ beforeEach(async () => { }); test('Clone is incompatible with Error Embeddables', async () => { - const action = new ClonePanelAction(coreStart); + const action = new ClonePanelAction(coreStart.savedObjects); const errorEmbeddable = new ErrorEmbeddable( 'Wow what an awful error', { id: ' 404' }, @@ -120,7 +100,7 @@ test('Clone adds a new embeddable', async () => { const dashboard = byRefOrValEmbeddable.getRoot() as IContainer; const originalPanelCount = Object.keys(dashboard.getInput().panels).length; const originalPanelKeySet = new Set(Object.keys(dashboard.getInput().panels)); - const action = new ClonePanelAction(coreStart); + const action = new ClonePanelAction(coreStart.savedObjects); await action.execute({ embeddable: byRefOrValEmbeddable }); expect(Object.keys(container.getInput().panels).length).toEqual(originalPanelCount + 1); const newPanelId = Object.keys(container.getInput().panels).find( @@ -140,7 +120,7 @@ test('Clone adds a new embeddable', async () => { test('Clones a RefOrVal embeddable by value', async () => { const dashboard = byRefOrValEmbeddable.getRoot() as IContainer; const panel = dashboard.getInput().panels[byRefOrValEmbeddable.id] as DashboardPanelState; - const action = new ClonePanelAction(coreStart); + const action = new ClonePanelAction(coreStart.savedObjects); // @ts-ignore const newPanel = await action.cloneEmbeddable(panel, byRefOrValEmbeddable); expect(coreStart.savedObjects.client.get).toHaveBeenCalledTimes(0); @@ -152,7 +132,7 @@ test('Clones a RefOrVal embeddable by value', async () => { test('Clones a non-RefOrVal embeddable by value if the panel does not have a savedObjectId', async () => { const dashboard = genericEmbeddable.getRoot() as IContainer; const panel = dashboard.getInput().panels[genericEmbeddable.id] as DashboardPanelState; - const action = new ClonePanelAction(coreStart); + const action = new ClonePanelAction(coreStart.savedObjects); // @ts-ignore const newPanelWithoutId = await action.cloneEmbeddable(panel, genericEmbeddable); expect(coreStart.savedObjects.client.get).toHaveBeenCalledTimes(0); @@ -165,7 +145,7 @@ test('Clones a non-RefOrVal embeddable by reference if the panel has a savedObje const dashboard = genericEmbeddable.getRoot() as IContainer; const panel = dashboard.getInput().panels[genericEmbeddable.id] as DashboardPanelState; panel.explicitInput.savedObjectId = 'holySavedObjectBatman'; - const action = new ClonePanelAction(coreStart); + const action = new ClonePanelAction(coreStart.savedObjects); // @ts-ignore const newPanel = await action.cloneEmbeddable(panel, genericEmbeddable); expect(coreStart.savedObjects.client.get).toHaveBeenCalledTimes(1); @@ -214,7 +194,7 @@ test('Gets a unique title from the saved objects library', async () => { } }); - const action = new ClonePanelAction(coreStart); + const action = new ClonePanelAction(coreStart.savedObjects); // @ts-ignore expect(await action.getCloneTitle(genericEmbeddable, 'testFirstClone')).toEqual( 'testFirstClone (copy)' @@ -247,7 +227,7 @@ test('Gets a unique title from the saved objects library', async () => { test('Gets a unique title from the dashboard', async () => { const dashboard = genericEmbeddable.getRoot() as DashboardContainer; - const action = new ClonePanelAction(coreStart); + const action = new ClonePanelAction(coreStart.savedObjects); // @ts-ignore expect(await action.getCloneTitle(byRefOrValEmbeddable, '')).toEqual(''); diff --git a/src/plugins/dashboard/public/application/actions/clone_panel_action.tsx b/src/plugins/dashboard/public/application/actions/clone_panel_action.tsx index 0ac6491425502..02862e7c75e86 100644 --- a/src/plugins/dashboard/public/application/actions/clone_panel_action.tsx +++ b/src/plugins/dashboard/public/application/actions/clone_panel_action.tsx @@ -9,9 +9,7 @@ import _ from 'lodash'; import uuid from 'uuid'; -import { CoreStart } from '@kbn/core/public'; -import { Action, IncompatibleActionError } from '../../services/ui_actions'; -import { SavedObject } from '../../services/saved_objects'; +import { SavedObjectsStart } from '@kbn/core/public'; import { ViewMode, PanelState, @@ -21,13 +19,17 @@ import { SavedObjectEmbeddableInput, isErrorEmbeddable, isReferenceOrValueEmbeddable, -} from '../../services/embeddable'; +} from '@kbn/embeddable-plugin/public'; +import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; +import type { SavedObject } from '@kbn/saved-objects-plugin/public'; + import { placePanelBeside, IPanelPlacementBesideArgs, } from '../embeddable/panel/dashboard_panel_placement'; import { dashboardClonePanelAction } from '../../dashboard_strings'; -import { DashboardPanelState, DASHBOARD_CONTAINER_TYPE, DashboardContainer } from '..'; +import { type DashboardPanelState, DASHBOARD_CONTAINER_TYPE, type DashboardContainer } from '..'; +import { pluginServices } from '../../services/plugin_services'; export const ACTION_CLONE_PANEL = 'clonePanel'; @@ -40,7 +42,13 @@ export class ClonePanelAction implements Action { public readonly id = ACTION_CLONE_PANEL; public order = 45; - constructor(private core: CoreStart) {} + private toastsService; + + constructor(private savedObjects: SavedObjectsStart) { + ({ + notifications: { toasts: this.toastsService }, + } = pluginServices.getServices()); + } public getDisplayName({ embeddable }: ClonePanelActionContext) { if (!embeddable.getRoot() || !embeddable.getRoot().isContainer) { @@ -108,7 +116,7 @@ export class ClonePanelAction implements Action { }); } else { const perPage = 10; - const similarSavedObjects = await this.core.savedObjects.client.find({ + const similarSavedObjects = await this.savedObjects.client.find({ type: embeddable.type, perPage, fields: ['title'], @@ -140,14 +148,14 @@ export class ClonePanelAction implements Action { embeddable: IEmbeddable, objectIdToClone: string ): Promise { - const savedObjectToClone = await this.core.savedObjects.client.get( + const savedObjectToClone = await this.savedObjects.client.get( embeddable.type, objectIdToClone ); // Clone the saved object const newTitle = await this.getCloneTitle(embeddable, savedObjectToClone.attributes.title); - const clonedSavedObject = await this.core.savedObjects.client.create( + const clonedSavedObject = await this.savedObjects.client.create( embeddable.type, { ..._.cloneDeep(savedObjectToClone.attributes), @@ -191,7 +199,7 @@ export class ClonePanelAction implements Action { clonedSavedObjectId; } } - this.core.notifications.toasts.addSuccess({ + this.toastsService.addSuccess({ title: dashboardClonePanelAction.getSuccessMessage(), 'data-test-subj': 'addObjectToContainerSuccess', }); diff --git a/src/plugins/dashboard/public/application/actions/copy_to_dashboard_action.tsx b/src/plugins/dashboard/public/application/actions/copy_to_dashboard_action.tsx index 835c80558c187..8f602db5e4529 100644 --- a/src/plugins/dashboard/public/application/actions/copy_to_dashboard_action.tsx +++ b/src/plugins/dashboard/public/application/actions/copy_to_dashboard_action.tsx @@ -7,14 +7,16 @@ */ import React from 'react'; -import { CoreStart, OverlayStart } from '@kbn/core/public'; + +import { toMountPoint } from '@kbn/kibana-react-plugin/public'; +import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; +import type { IEmbeddable } from '@kbn/embeddable-plugin/public'; +import type { PresentationUtilPluginStart } from '@kbn/presentation-util-plugin/public'; + import { dashboardCopyToDashboardAction } from '../../dashboard_strings'; -import { EmbeddableStateTransfer, IEmbeddable } from '../../services/embeddable'; -import { toMountPoint } from '../../services/kibana_react'; -import { PresentationUtilPluginStart } from '../../services/presentation_util'; -import { Action, IncompatibleActionError } from '../../services/ui_actions'; import { DASHBOARD_CONTAINER_TYPE, DashboardContainer } from '../embeddable'; import { CopyToDashboardModal } from './copy_to_dashboard_modal'; +import { pluginServices } from '../../services/plugin_services'; export const ACTION_COPY_TO_DASHBOARD = 'copyToDashboard'; @@ -36,13 +38,19 @@ export class CopyToDashboardAction implements Action session.close()} - stateTransfer={this.stateTransfer} - capabilities={this.capabilities} dashboardId={(embeddable.parent as DashboardContainer).getInput().id} embeddable={embeddable} />, - { theme$: this.theme.theme$ } + { theme$: this.theme$ } ), { maxWidth: 400, diff --git a/src/plugins/dashboard/public/application/actions/copy_to_dashboard_modal.tsx b/src/plugins/dashboard/public/application/actions/copy_to_dashboard_modal.tsx index 77b136de9d7c1..7f9a99ed27231 100644 --- a/src/plugins/dashboard/public/application/actions/copy_to_dashboard_modal.tsx +++ b/src/plugins/dashboard/public/application/actions/copy_to_dashboard_modal.tsx @@ -22,20 +22,14 @@ import { EuiFocusTrap, EuiOutsideClickDetector, } from '@elastic/eui'; -import { DashboardCopyToCapabilities } from './copy_to_dashboard_action'; -import { LazyDashboardPicker, withSuspense } from '../../services/presentation_util'; +import { IEmbeddable, PanelNotFoundError } from '@kbn/embeddable-plugin/public'; +import { LazyDashboardPicker, withSuspense } from '@kbn/presentation-util-plugin/public'; import { dashboardCopyToDashboardAction } from '../../dashboard_strings'; -import { - EmbeddableStateTransfer, - IEmbeddable, - PanelNotFoundError, -} from '../../services/embeddable'; import { createDashboardEditUrl, DashboardConstants, DashboardContainer } from '../..'; import { DashboardPanelState } from '..'; +import { pluginServices } from '../../services/plugin_services'; interface CopyToDashboardModalProps { - capabilities: DashboardCopyToCapabilities; - stateTransfer: EmbeddableStateTransfer; PresentationUtilContext: React.FC; embeddable: IEmbeddable; dashboardId?: string; @@ -46,12 +40,16 @@ const DashboardPicker = withSuspense(LazyDashboardPicker); export function CopyToDashboardModal({ PresentationUtilContext, - stateTransfer, - capabilities, dashboardId, embeddable, closeModal, }: CopyToDashboardModalProps) { + const { + embeddable: { getStateTransfer }, + dashboardCapabilities: { createNew: canCreateNew, showWriteControls: canEditExisting }, + } = pluginServices.getServices(); + const stateTransfer = getStateTransfer(); + const [dashboardOption, setDashboardOption] = useState<'new' | 'existing'>('existing'); const [selectedDashboard, setSelectedDashboard] = useState<{ id: string; name: string } | null>( null @@ -63,6 +61,7 @@ export function CopyToDashboardModal({ if (!panelToCopy) { throw new PanelNotFoundError(); } + const state = { type: embeddable.type, input: { @@ -114,7 +113,7 @@ export function CopyToDashboardModal({ data-test-subj="add-to-dashboard-options" >
- {capabilities.canEditExisting && ( + {canEditExisting && ( <> )} - {capabilities.canCreateNew && ( + {canCreateNew && ( <> null) as any, {} as any) -); -const start = doStart(); +} from '@kbn/embeddable-plugin/public/lib/test_samples/embeddables'; + +import { pluginServices } from '../../services/plugin_services'; let container: DashboardContainer; let embeddable: ContactCardEmbeddable; +const mockEmbeddableFactory = new ContactCardEmbeddableFactory((() => null) as any, {} as any); +pluginServices.getServices().embeddable.getEmbeddableFactory = jest + .fn() + .mockReturnValue(mockEmbeddableFactory); + beforeEach(async () => { - const options = { - ExitFullScreenButton: () => null, - SavedObjectFinder: () => null, - application: {} as any, - embeddable: start, - inspector: {} as any, - notifications: {} as any, - overlays: {} as any, - savedObjectMetaData: {} as any, - uiActions: {} as any, - uiSettings: uiSettingsServiceMock.createStartContract(), - http: coreMock.createStart().http, - theme: coreMock.createStart().theme, - presentationUtil: getStubPluginServices(), - screenshotMode: screenshotModePluginMock.createSetupContract(), - }; const input = getSampleDashboardInput({ panels: { '123': getSampleDashboardPanel({ @@ -60,7 +38,8 @@ beforeEach(async () => { }), }, }); - container = new DashboardContainer(input, options); + + container = new DashboardContainer(input); const contactCardEmbeddable = await container.addNewEmbeddable< ContactCardEmbeddableInput, diff --git a/src/plugins/dashboard/public/application/actions/expand_panel_action.tsx b/src/plugins/dashboard/public/application/actions/expand_panel_action.tsx index 98f7bbbee3d7c..c5da566b90a6a 100644 --- a/src/plugins/dashboard/public/application/actions/expand_panel_action.tsx +++ b/src/plugins/dashboard/public/application/actions/expand_panel_action.tsx @@ -6,11 +6,12 @@ * Side Public License, v 1. */ -import { DashboardContainerInput } from '../..'; -import { IEmbeddable } from '../../services/embeddable'; +import type { IEmbeddable } from '@kbn/embeddable-plugin/public'; +import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; + +import type { DashboardContainerInput } from '../..'; import { dashboardExpandPanelAction } from '../../dashboard_strings'; -import { Action, IncompatibleActionError } from '../../services/ui_actions'; -import { DASHBOARD_CONTAINER_TYPE, DashboardContainer } from '../embeddable'; +import { DASHBOARD_CONTAINER_TYPE, type DashboardContainer } from '../embeddable'; export const ACTION_EXPAND_PANEL = 'togglePanel'; diff --git a/src/plugins/dashboard/public/application/actions/export_csv_action.test.tsx b/src/plugins/dashboard/public/application/actions/export_csv_action.test.tsx index c75bce83651fd..ec63c3e0ec7de 100644 --- a/src/plugins/dashboard/public/application/actions/export_csv_action.test.tsx +++ b/src/plugins/dashboard/public/application/actions/export_csv_action.test.tsx @@ -7,8 +7,8 @@ */ import { CoreStart } from '@kbn/core/public'; +import { isErrorEmbeddable, IContainer, ErrorEmbeddable } from '@kbn/embeddable-plugin/public'; -import { isErrorEmbeddable, IContainer, ErrorEmbeddable } from '../../services/embeddable'; import { DashboardContainer } from '../embeddable/dashboard_container'; import { getSampleDashboardInput, getSampleDashboardPanel } from '../test_helpers'; import { @@ -17,28 +17,24 @@ import { ContactCardEmbeddableOutput, ContactCardExportableEmbeddableFactory, CONTACT_CARD_EXPORTABLE_EMBEDDABLE, -} from '../../services/embeddable_test_samples'; -import { coreMock, uiSettingsServiceMock } from '@kbn/core/public/mocks'; +} from '@kbn/embeddable-plugin/public/lib/test_samples/embeddables'; +import { coreMock } from '@kbn/core/public/mocks'; import { ExportCSVAction } from './export_csv_action'; -import { embeddablePluginMock } from '@kbn/embeddable-plugin/public/mocks'; -import { DataPublicPluginStart } from '@kbn/data-plugin/public/types'; -import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { LINE_FEED_CHARACTER } from '@kbn/data-plugin/common/exports/export_csv'; -import { getStubPluginServices } from '@kbn/presentation-util-plugin/public'; -import { screenshotModePluginMock } from '@kbn/screenshot-mode-plugin/public/mocks'; +import { pluginServices } from '../../services/plugin_services'; describe('Export CSV action', () => { - const { setup, doStart } = embeddablePluginMock.createInstance(); - setup.registerEmbeddableFactory( - CONTACT_CARD_EXPORTABLE_EMBEDDABLE, - new ContactCardExportableEmbeddableFactory((() => null) as any, {} as any) - ); - const start = doStart(); - let container: DashboardContainer; let embeddable: ContactCardEmbeddable; let coreStart: CoreStart; - let dataMock: jest.Mocked; + + const mockEmbeddableFactory = new ContactCardExportableEmbeddableFactory( + (() => null) as any, + {} as any + ); + pluginServices.getServices().embeddable.getEmbeddableFactory = jest + .fn() + .mockReturnValue(mockEmbeddableFactory); beforeEach(async () => { coreStart = coreMock.createStart(); @@ -49,22 +45,6 @@ describe('Export CSV action', () => { create: jest.fn().mockImplementation(() => ({ id: 'brandNewSavedObject' })), }; - const options = { - ExitFullScreenButton: () => null, - SavedObjectFinder: () => null, - application: {} as any, - embeddable: start, - inspector: {} as any, - notifications: {} as any, - overlays: coreStart.overlays, - savedObjectMetaData: {} as any, - uiActions: {} as any, - uiSettings: uiSettingsServiceMock.createStartContract(), - http: coreStart.http, - theme: coreStart.theme, - presentationUtil: getStubPluginServices(), - screenshotMode: screenshotModePluginMock.createSetupContract(), - }; const input = getSampleDashboardInput({ panels: { '123': getSampleDashboardPanel({ @@ -73,8 +53,7 @@ describe('Export CSV action', () => { }), }, }); - container = new DashboardContainer(input, options); - dataMock = dataPluginMock.createStartContract(); + container = new DashboardContainer(input); const contactCardEmbeddable = await container.addNewEmbeddable< ContactCardEmbeddableInput, @@ -92,7 +71,7 @@ describe('Export CSV action', () => { }); test('Download is incompatible with embeddables without getInspectorAdapters implementation', async () => { - const action = new ExportCSVAction({ core: coreStart, data: dataMock }); + const action = new ExportCSVAction(); const errorEmbeddable = new ErrorEmbeddable( 'Wow what an awful error', { id: ' 404' }, @@ -102,7 +81,7 @@ describe('Export CSV action', () => { }); test('Should download a compatible Embeddable', async () => { - const action = new ExportCSVAction({ core: coreStart, data: dataMock }); + const action = new ExportCSVAction(); const result = (await action.execute({ embeddable, asString: true })) as unknown as | undefined | Record; @@ -115,7 +94,7 @@ describe('Export CSV action', () => { }); test('Should not download incompatible Embeddable', async () => { - const action = new ExportCSVAction({ core: coreStart, data: dataMock }); + const action = new ExportCSVAction(); const errorEmbeddable = new ErrorEmbeddable( 'Wow what an awful error', { id: ' 404' }, diff --git a/src/plugins/dashboard/public/application/actions/export_csv_action.tsx b/src/plugins/dashboard/public/application/actions/export_csv_action.tsx index 2835e1c4dd6e2..fa90da0550050 100644 --- a/src/plugins/dashboard/public/application/actions/export_csv_action.tsx +++ b/src/plugins/dashboard/public/application/actions/export_csv_action.tsx @@ -6,23 +6,18 @@ * Side Public License, v 1. */ +import { exporters } from '@kbn/data-plugin/public'; +import { Action } from '@kbn/ui-actions-plugin/public'; import { Datatable } from '@kbn/expressions-plugin/public'; -import { CoreStart } from '@kbn/core/public'; +import { downloadMultipleAs } from '@kbn/share-plugin/public'; import { FormatFactory } from '@kbn/field-formats-plugin/common'; +import type { Adapters, IEmbeddable } from '@kbn/embeddable-plugin/public'; -import { DataPublicPluginStart, exporters } from '../../services/data'; -import { downloadMultipleAs } from '../../services/share'; -import { Adapters, IEmbeddable } from '../../services/embeddable'; -import { Action } from '../../services/ui_actions'; import { dashboardExportCsvAction } from '../../dashboard_strings'; +import { pluginServices } from '../../services/plugin_services'; export const ACTION_EXPORT_CSV = 'ACTION_EXPORT_CSV'; -export interface Params { - core: CoreStart; - data: DataPublicPluginStart; -} - export interface ExportContext { embeddable?: IEmbeddable; // used for testing @@ -35,12 +30,18 @@ export interface ExportContext { */ export class ExportCSVAction implements Action { public readonly id = ACTION_EXPORT_CSV; - public readonly type = ACTION_EXPORT_CSV; - public readonly order = 5; - constructor(protected readonly params: Params) {} + private fieldFormats; + private uiSettings; + + constructor() { + ({ + data: { fieldFormats: this.fieldFormats }, + settings: { uiSettings: this.uiSettings }, + } = pluginServices.getServices()); + } public getIconType() { return 'exportAction'; @@ -58,8 +59,8 @@ export class ExportCSVAction implements Action { }; private getFormatter = (): FormatFactory | undefined => { - if (this.params.data) { - return this.params.data.fieldFormats.deserialize; + if (this.fieldFormats) { + return this.fieldFormats.deserialize; } }; @@ -76,6 +77,7 @@ export class ExportCSVAction implements Action { if (!formatFactory) { return; } + const tableAdapters = this.getDataTableContent( context?.embeddable?.getInspectorAdapters() ) as Record; @@ -91,8 +93,8 @@ export class ExportCSVAction implements Action { memo[`${context!.embeddable!.getTitle() || untitledFilename}${postFix}.csv`] = { content: exporters.datatableToCSV(datatable, { - csvSeparator: this.params.core.uiSettings.get('csv:separator', ','), - quoteValues: this.params.core.uiSettings.get('csv:quoteValues', true), + csvSeparator: this.uiSettings.get('csv:separator', ','), + quoteValues: this.uiSettings.get('csv:quoteValues', true), formatFactory, escapeFormulaValues: false, }), diff --git a/src/plugins/dashboard/public/application/actions/filters_notification_badge.test.tsx b/src/plugins/dashboard/public/application/actions/filters_notification_badge.test.tsx index 5b990fe3ae62e..275a5625e5e0b 100644 --- a/src/plugins/dashboard/public/application/actions/filters_notification_badge.test.tsx +++ b/src/plugins/dashboard/public/application/actions/filters_notification_badge.test.tsx @@ -9,41 +9,35 @@ import { getSampleDashboardInput } from '../test_helpers'; import { DashboardContainer } from '../embeddable/dashboard_container'; -import { coreMock, uiSettingsServiceMock } from '@kbn/core/public/mocks'; -import { CoreStart } from '@kbn/core/public'; import { FiltersNotificationBadge } from '.'; import { embeddablePluginMock } from '@kbn/embeddable-plugin/public/mocks'; import { type Query, type AggregateQuery, Filter } from '@kbn/es-query'; - import { ErrorEmbeddable, FilterableEmbeddable, IContainer, isErrorEmbeddable, -} from '../../services/embeddable'; +} from '@kbn/embeddable-plugin/public'; + import { ContactCardEmbeddable, ContactCardEmbeddableFactory, ContactCardEmbeddableInput, ContactCardEmbeddableOutput, CONTACT_CARD_EMBEDDABLE, -} from '../../services/embeddable_test_samples'; -import { getStubPluginServices } from '@kbn/presentation-util-plugin/public'; -import { screenshotModePluginMock } from '@kbn/screenshot-mode-plugin/public/mocks'; +} from '@kbn/embeddable-plugin/public/lib/test_samples/embeddables'; +import { pluginServices } from '../../services/plugin_services'; -const { setup, doStart } = embeddablePluginMock.createInstance(); -setup.registerEmbeddableFactory( - CONTACT_CARD_EMBEDDABLE, - new ContactCardEmbeddableFactory((() => null) as any, {} as any) -); -const start = doStart(); +const mockEmbeddableFactory = new ContactCardEmbeddableFactory((() => null) as any, {} as any); +pluginServices.getServices().embeddable.getEmbeddableFactory = jest + .fn() + .mockReturnValue(mockEmbeddableFactory); let action: FiltersNotificationBadge; let container: DashboardContainer; let embeddable: ContactCardEmbeddable & FilterableEmbeddable; const mockGetFilters = jest.fn(async () => [] as Filter[]); const mockGetQuery = jest.fn(async () => undefined as Query | AggregateQuery | undefined); -let coreStart: CoreStart; const getMockPhraseFilter = (key: string, value: string) => { return { @@ -66,26 +60,7 @@ const getMockPhraseFilter = (key: string, value: string) => { }; beforeEach(async () => { - coreStart = coreMock.createStart(); - - const containerOptions = { - ExitFullScreenButton: () => null, - SavedObjectFinder: () => null, - application: {} as any, - embeddable: start, - inspector: {} as any, - notifications: {} as any, - overlays: coreStart.overlays, - savedObjectMetaData: {} as any, - uiActions: {} as any, - uiSettings: uiSettingsServiceMock.createStartContract(), - http: coreStart.http, - theme: coreStart.theme, - presentationUtil: getStubPluginServices(), - screenshotMode: screenshotModePluginMock.createSetupContract(), - }; - - container = new DashboardContainer(getSampleDashboardInput(), containerOptions); + container = new DashboardContainer(getSampleDashboardInput()); const contactCardEmbeddable = await container.addNewEmbeddable< ContactCardEmbeddableInput, @@ -98,13 +73,7 @@ beforeEach(async () => { throw new Error('Failed to create embeddable'); } - action = new FiltersNotificationBadge( - coreStart.application, - embeddablePluginMock.createStartContract(), - coreStart.overlays, - coreStart.theme, - coreStart.uiSettings - ); + action = new FiltersNotificationBadge(); embeddable = embeddablePluginMock.mockFilterableEmbeddable(contactCardEmbeddable, { getFilters: () => mockGetFilters(), getQuery: () => mockGetQuery(), diff --git a/src/plugins/dashboard/public/application/actions/filters_notification_badge.tsx b/src/plugins/dashboard/public/application/actions/filters_notification_badge.tsx index e5a1d1af32eea..6dbe7d5dbe3c9 100644 --- a/src/plugins/dashboard/public/application/actions/filters_notification_badge.tsx +++ b/src/plugins/dashboard/public/application/actions/filters_notification_badge.tsx @@ -8,19 +8,16 @@ import React from 'react'; -import { CoreStart, OverlayStart } from '@kbn/core/public'; -import { - EditPanelAction, - EmbeddableStart, - isFilterableEmbeddable, -} from '@kbn/embeddable-plugin/public'; +import { EditPanelAction, isFilterableEmbeddable } from '@kbn/embeddable-plugin/public'; +import { type AggregateQuery } from '@kbn/es-query'; +import { toMountPoint } from '@kbn/kibana-react-plugin/public'; +import type { ApplicationStart } from '@kbn/core/public'; import { createKibanaReactContext } from '@kbn/kibana-react-plugin/public'; +import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; +import { type IEmbeddable, isErrorEmbeddable } from '@kbn/embeddable-plugin/public'; -import { type AggregateQuery } from '@kbn/es-query'; -import { Action, IncompatibleActionError } from '../../services/ui_actions'; -import { toMountPoint } from '../../services/kibana_react'; -import { IEmbeddable, isErrorEmbeddable } from '../../services/embeddable'; import { dashboardFilterNotificationBadge } from '../../dashboard_strings'; +import { pluginServices } from '../../services/plugin_services'; export const BADGE_FILTERS_NOTIFICATION = 'ACTION_FILTERS_NOTIFICATION'; @@ -35,14 +32,19 @@ export class FiltersNotificationBadge implements Action m.FiltersNotificationModal ); - const session = this.overlays.openModal( + const session = this.openModal( toMountPoint( session.close()} /> , - { theme$: this.theme.theme$ } + { theme$ } ), { 'data-test-subj': 'filtersNotificationModal', diff --git a/src/plugins/dashboard/public/application/actions/filters_notification_modal.test.tsx b/src/plugins/dashboard/public/application/actions/filters_notification_modal.test.tsx index 2729395b7eec5..9a83bf52e1092 100644 --- a/src/plugins/dashboard/public/application/actions/filters_notification_modal.test.tsx +++ b/src/plugins/dashboard/public/application/actions/filters_notification_modal.test.tsx @@ -8,60 +8,35 @@ import React from 'react'; import { findTestSubject, mountWithIntl } from '@kbn/test-jest-helpers'; +import { FilterableEmbeddable, isErrorEmbeddable, ViewMode } from '@kbn/embeddable-plugin/public'; import { DashboardContainer } from '../embeddable/dashboard_container'; import { embeddablePluginMock } from '@kbn/embeddable-plugin/public/mocks'; import { getSampleDashboardInput } from '../test_helpers'; -import { CoreStart } from '@kbn/core/public'; -import { coreMock, uiSettingsServiceMock } from '@kbn/core/public/mocks'; import { EuiModalFooter } from '@elastic/eui'; -import { getStubPluginServices } from '@kbn/presentation-util-plugin/public'; -import { screenshotModePluginMock } from '@kbn/screenshot-mode-plugin/public/mocks'; import { FiltersNotificationModal, FiltersNotificationProps } from './filters_notification_modal'; -import { FilterableEmbeddable, isErrorEmbeddable, ViewMode } from '../../services/embeddable'; import { - CONTACT_CARD_EMBEDDABLE, + ContactCardEmbeddable, ContactCardEmbeddableFactory, ContactCardEmbeddableInput, ContactCardEmbeddableOutput, - ContactCardEmbeddable, -} from '../../services/embeddable_test_samples'; + CONTACT_CARD_EMBEDDABLE, +} from '@kbn/embeddable-plugin/public/lib/test_samples/embeddables'; import { act } from 'react-dom/test-utils'; +import { pluginServices } from '../../services/plugin_services'; describe('LibraryNotificationPopover', () => { - const { setup, doStart } = embeddablePluginMock.createInstance(); - setup.registerEmbeddableFactory( - CONTACT_CARD_EMBEDDABLE, - new ContactCardEmbeddableFactory((() => null) as any, {} as any) - ); - const start = doStart(); + const mockEmbeddableFactory = new ContactCardEmbeddableFactory((() => null) as any, {} as any); + pluginServices.getServices().embeddable.getEmbeddableFactory = jest + .fn() + .mockReturnValue(mockEmbeddableFactory); let container: DashboardContainer; let embeddable: ContactCardEmbeddable & FilterableEmbeddable; let defaultProps: FiltersNotificationProps; - let coreStart: CoreStart; beforeEach(async () => { - coreStart = coreMock.createStart(); - - const containerOptions = { - ExitFullScreenButton: () => null, - SavedObjectFinder: () => null, - application: {} as any, - embeddable: start, - inspector: {} as any, - notifications: {} as any, - overlays: coreStart.overlays, - savedObjectMetaData: {} as any, - uiActions: {} as any, - uiSettings: uiSettingsServiceMock.createStartContract(), - http: coreStart.http, - theme: coreStart.theme, - presentationUtil: getStubPluginServices(), - screenshotMode: screenshotModePluginMock.createSetupContract(), - }; - - container = new DashboardContainer(getSampleDashboardInput(), containerOptions); + container = new DashboardContainer(getSampleDashboardInput()); const contactCardEmbeddable = await container.addNewEmbeddable< ContactCardEmbeddableInput, ContactCardEmbeddableOutput, diff --git a/src/plugins/dashboard/public/application/actions/library_notification_action.test.tsx b/src/plugins/dashboard/public/application/actions/library_notification_action.test.tsx index 1f1570fe02e30..f1202de4ac1b6 100644 --- a/src/plugins/dashboard/public/application/actions/library_notification_action.test.tsx +++ b/src/plugins/dashboard/public/application/actions/library_notification_action.test.tsx @@ -9,8 +9,6 @@ import { getSampleDashboardInput } from '../test_helpers'; import { DashboardContainer } from '../embeddable/dashboard_container'; -import { coreMock, uiSettingsServiceMock } from '@kbn/core/public/mocks'; -import { CoreStart } from '@kbn/core/public'; import { LibraryNotificationAction, UnlinkFromLibraryAction } from '.'; import { embeddablePluginMock } from '@kbn/embeddable-plugin/public/mocks'; import { @@ -19,55 +17,32 @@ import { isErrorEmbeddable, ReferenceOrValueEmbeddable, ViewMode, -} from '../../services/embeddable'; +} from '@kbn/embeddable-plugin/public'; import { ContactCardEmbeddable, ContactCardEmbeddableFactory, ContactCardEmbeddableInput, ContactCardEmbeddableOutput, CONTACT_CARD_EMBEDDABLE, -} from '../../services/embeddable_test_samples'; -import { getStubPluginServices } from '@kbn/presentation-util-plugin/public'; -import { screenshotModePluginMock } from '@kbn/screenshot-mode-plugin/public/mocks'; +} from '@kbn/embeddable-plugin/public/lib/test_samples/embeddables'; +import { pluginServices } from '../../services/plugin_services'; -const { setup, doStart } = embeddablePluginMock.createInstance(); -setup.registerEmbeddableFactory( - CONTACT_CARD_EMBEDDABLE, - new ContactCardEmbeddableFactory((() => null) as any, {} as any) -); -const start = doStart(); +const mockEmbeddableFactory = new ContactCardEmbeddableFactory((() => null) as any, {} as any); +pluginServices.getServices().embeddable.getEmbeddableFactory = jest + .fn() + .mockReturnValue(mockEmbeddableFactory); let container: DashboardContainer; let embeddable: ContactCardEmbeddable & ReferenceOrValueEmbeddable; -let coreStart: CoreStart; let unlinkAction: UnlinkFromLibraryAction; beforeEach(async () => { - coreStart = coreMock.createStart(); - unlinkAction = { getDisplayName: () => 'unlink from dat library', execute: jest.fn(), } as unknown as UnlinkFromLibraryAction; - const containerOptions = { - ExitFullScreenButton: () => null, - SavedObjectFinder: () => null, - application: {} as any, - embeddable: start, - inspector: {} as any, - notifications: {} as any, - overlays: coreStart.overlays, - savedObjectMetaData: {} as any, - uiActions: {} as any, - uiSettings: uiSettingsServiceMock.createStartContract(), - http: coreStart.http, - theme: coreStart.theme, - presentationUtil: getStubPluginServices(), - screenshotMode: screenshotModePluginMock.createSetupContract(), - }; - - container = new DashboardContainer(getSampleDashboardInput(), containerOptions); + container = new DashboardContainer(getSampleDashboardInput()); const contactCardEmbeddable = await container.addNewEmbeddable< ContactCardEmbeddableInput, @@ -91,7 +66,7 @@ beforeEach(async () => { }); test('Notification is incompatible with Error Embeddables', async () => { - const action = new LibraryNotificationAction(coreStart.theme, unlinkAction); + const action = new LibraryNotificationAction(unlinkAction); const errorEmbeddable = new ErrorEmbeddable( 'Wow what an awful error', { id: ' 404' }, @@ -101,19 +76,19 @@ test('Notification is incompatible with Error Embeddables', async () => { }); test('Notification is shown when embeddable on dashboard has reference type input', async () => { - const action = new LibraryNotificationAction(coreStart.theme, unlinkAction); + const action = new LibraryNotificationAction(unlinkAction); embeddable.updateInput(await embeddable.getInputAsRefType()); expect(await action.isCompatible({ embeddable })).toBe(true); }); test('Notification is not shown when embeddable input is by value', async () => { - const action = new LibraryNotificationAction(coreStart.theme, unlinkAction); + const action = new LibraryNotificationAction(unlinkAction); embeddable.updateInput(await embeddable.getInputAsValueType()); expect(await action.isCompatible({ embeddable })).toBe(false); }); test('Notification is not shown when view mode is set to view', async () => { - const action = new LibraryNotificationAction(coreStart.theme, unlinkAction); + const action = new LibraryNotificationAction(unlinkAction); embeddable.updateInput(await embeddable.getInputAsRefType()); embeddable.updateInput({ viewMode: ViewMode.VIEW }); expect(await action.isCompatible({ embeddable })).toBe(false); diff --git a/src/plugins/dashboard/public/application/actions/library_notification_action.tsx b/src/plugins/dashboard/public/application/actions/library_notification_action.tsx index 3360d39f6f245..a5abe8161e9ad 100644 --- a/src/plugins/dashboard/public/application/actions/library_notification_action.tsx +++ b/src/plugins/dashboard/public/application/actions/library_notification_action.tsx @@ -8,19 +8,19 @@ import React from 'react'; -import { CoreStart } from '@kbn/core/public'; -import { Action, IncompatibleActionError } from '../../services/ui_actions'; -import { KibanaThemeProvider, reactToUiComponent } from '../../services/kibana_react'; import { - IEmbeddable, ViewMode, - isReferenceOrValueEmbeddable, + type IEmbeddable, isErrorEmbeddable, -} from '../../services/embeddable'; + isReferenceOrValueEmbeddable, +} from '@kbn/embeddable-plugin/public'; +import { KibanaThemeProvider, reactToUiComponent } from '@kbn/kibana-react-plugin/public'; +import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; import { UnlinkFromLibraryAction } from '.'; import { LibraryNotificationPopover } from './library_notification_popover'; import { dashboardLibraryNotification } from '../../dashboard_strings'; +import { pluginServices } from '../../services/plugin_services'; export const ACTION_LIBRARY_NOTIFICATION = 'ACTION_LIBRARY_NOTIFICATION'; @@ -33,7 +33,15 @@ export class LibraryNotificationAction implements Action { const { embeddable } = context; return ( - + { - const { setup, doStart } = embeddablePluginMock.createInstance(); - setup.registerEmbeddableFactory( - CONTACT_CARD_EMBEDDABLE, - new ContactCardEmbeddableFactory((() => null) as any, {} as any) - ); - const start = doStart(); + const mockEmbeddableFactory = new ContactCardEmbeddableFactory((() => null) as any, {} as any); + pluginServices.getServices().embeddable.getEmbeddableFactory = jest + .fn() + .mockReturnValue(mockEmbeddableFactory); let container: DashboardContainer; let defaultProps: LibraryNotificationProps; - let coreStart: CoreStart; beforeEach(async () => { - coreStart = coreMock.createStart(); - - const containerOptions = { - ExitFullScreenButton: () => null, - SavedObjectFinder: () => null, - application: {} as any, - embeddable: start, - inspector: {} as any, - notifications: {} as any, - overlays: coreStart.overlays, - savedObjectMetaData: {} as any, - uiActions: {} as any, - uiSettings: uiSettingsServiceMock.createStartContract(), - http: coreStart.http, - theme: coreStart.theme, - presentationUtil: getStubPluginServices(), - screenshotMode: screenshotModePluginMock.createSetupContract(), - }; - - container = new DashboardContainer(getSampleDashboardInput(), containerOptions); + container = new DashboardContainer(getSampleDashboardInput()); const contactCardEmbeddable = await container.addNewEmbeddable< ContactCardEmbeddableInput, ContactCardEmbeddableOutput, diff --git a/src/plugins/dashboard/public/application/actions/open_replace_panel_flyout.tsx b/src/plugins/dashboard/public/application/actions/open_replace_panel_flyout.tsx index 5d07378c6514d..5322e56831ff1 100644 --- a/src/plugins/dashboard/public/application/actions/open_replace_panel_flyout.tsx +++ b/src/plugins/dashboard/public/application/actions/open_replace_panel_flyout.tsx @@ -7,34 +7,33 @@ */ import React from 'react'; -import { CoreStart } from '@kbn/core/public'; -import { toMountPoint } from '../../services/kibana_react'; -import { ReplacePanelFlyout } from './replace_panel_flyout'; -import { + +import type { IContainer, IEmbeddable, - EmbeddableStart, EmbeddableInput, EmbeddableOutput, -} from '../../services/embeddable'; +} from '@kbn/embeddable-plugin/public'; +import { toMountPoint } from '@kbn/kibana-react-plugin/public'; + +import { ReplacePanelFlyout } from './replace_panel_flyout'; +import { pluginServices } from '../../services/plugin_services'; export async function openReplacePanelFlyout(options: { embeddable: IContainer; - core: CoreStart; savedObjectFinder: React.ComponentType; - notifications: CoreStart['notifications']; panelToRemove: IEmbeddable; - getEmbeddableFactories: EmbeddableStart['getEmbeddableFactories']; }) { + const { embeddable, panelToRemove, savedObjectFinder } = options; + const { - embeddable, - core, - panelToRemove, - savedObjectFinder, - notifications, - getEmbeddableFactories, - } = options; - const flyoutSession = core.overlays.openFlyout( + settings: { + theme: { theme$ }, + }, + overlays: { openFlyout }, + } = pluginServices.getServices(); + + const flyoutSession = openFlyout( toMountPoint( , - { theme$: core.theme.theme$ } + { theme$ } ), { 'data-test-subj': 'dashboardReplacePanel', diff --git a/src/plugins/dashboard/public/application/actions/replace_panel_action.test.tsx b/src/plugins/dashboard/public/application/actions/replace_panel_action.test.tsx index 27de8862fbc2a..0a035d06d4fd9 100644 --- a/src/plugins/dashboard/public/application/actions/replace_panel_action.test.tsx +++ b/src/plugins/dashboard/public/application/actions/replace_panel_action.test.tsx @@ -10,48 +10,25 @@ import { ReplacePanelAction } from './replace_panel_action'; import { DashboardContainer } from '../embeddable/dashboard_container'; import { getSampleDashboardInput, getSampleDashboardPanel } from '../test_helpers'; -import { coreMock, uiSettingsServiceMock } from '@kbn/core/public/mocks'; -import { CoreStart } from '@kbn/core/public'; -import { embeddablePluginMock } from '@kbn/embeddable-plugin/public/mocks'; -import { isErrorEmbeddable } from '../../services/embeddable'; +import { isErrorEmbeddable } from '@kbn/embeddable-plugin/public'; import { - CONTACT_CARD_EMBEDDABLE, - ContactCardEmbeddableFactory, ContactCardEmbeddable, + ContactCardEmbeddableFactory, ContactCardEmbeddableInput, ContactCardEmbeddableOutput, -} from '../../services/embeddable_test_samples'; -import { getStubPluginServices } from '@kbn/presentation-util-plugin/public'; -import { screenshotModePluginMock } from '@kbn/screenshot-mode-plugin/public/mocks'; - -const { setup, doStart } = embeddablePluginMock.createInstance(); -setup.registerEmbeddableFactory( CONTACT_CARD_EMBEDDABLE, - new ContactCardEmbeddableFactory((() => null) as any, {} as any) -); -const start = doStart(); +} from '@kbn/embeddable-plugin/public/lib/test_samples/embeddables'; + +import { pluginServices } from '../../services/plugin_services'; + +const mockEmbeddableFactory = new ContactCardEmbeddableFactory((() => null) as any, {} as any); +pluginServices.getServices().embeddable.getEmbeddableFactory = jest + .fn() + .mockReturnValue(mockEmbeddableFactory); let container: DashboardContainer; let embeddable: ContactCardEmbeddable; -let coreStart: CoreStart; beforeEach(async () => { - coreStart = coreMock.createStart(); - const options = { - ExitFullScreenButton: () => null, - SavedObjectFinder: () => null, - application: {} as any, - embeddable: start, - inspector: {} as any, - notifications: {} as any, - overlays: coreStart.overlays, - savedObjectMetaData: {} as any, - uiActions: {} as any, - uiSettings: uiSettingsServiceMock.createStartContract(), - http: coreStart.http, - theme: coreStart.theme, - presentationUtil: getStubPluginServices(), - screenshotMode: screenshotModePluginMock.createSetupContract(), - }; const input = getSampleDashboardInput({ panels: { '123': getSampleDashboardPanel({ @@ -60,7 +37,7 @@ beforeEach(async () => { }), }, }); - container = new DashboardContainer(input, options); + container = new DashboardContainer(input); const contactCardEmbeddable = await container.addNewEmbeddable< ContactCardEmbeddableInput, @@ -79,25 +56,13 @@ beforeEach(async () => { test('Executes the replace panel action', async () => { let SavedObjectFinder: any; - let notifications: any; - const action = new ReplacePanelAction( - coreStart, - SavedObjectFinder, - notifications, - start.getEmbeddableFactories - ); + const action = new ReplacePanelAction(SavedObjectFinder); action.execute({ embeddable }); }); test('Is not compatible when embeddable is not in a dashboard container', async () => { let SavedObjectFinder: any; - let notifications: any; - const action = new ReplacePanelAction( - coreStart, - SavedObjectFinder, - notifications, - start.getEmbeddableFactories - ); + const action = new ReplacePanelAction(SavedObjectFinder); expect( await action.isCompatible({ embeddable: new ContactCardEmbeddable( @@ -110,13 +75,7 @@ test('Is not compatible when embeddable is not in a dashboard container', async test('Execute throws an error when called with an embeddable not in a parent', async () => { let SavedObjectFinder: any; - let notifications: any; - const action = new ReplacePanelAction( - coreStart, - SavedObjectFinder, - notifications, - start.getEmbeddableFactories - ); + const action = new ReplacePanelAction(SavedObjectFinder); async function check() { await action.execute({ embeddable: container }); } @@ -125,24 +84,12 @@ test('Execute throws an error when called with an embeddable not in a parent', a test('Returns title', async () => { let SavedObjectFinder: any; - let notifications: any; - const action = new ReplacePanelAction( - coreStart, - SavedObjectFinder, - notifications, - start.getEmbeddableFactories - ); + const action = new ReplacePanelAction(SavedObjectFinder); expect(action.getDisplayName({ embeddable })).toBeDefined(); }); test('Returns an icon', async () => { let SavedObjectFinder: any; - let notifications: any; - const action = new ReplacePanelAction( - coreStart, - SavedObjectFinder, - notifications, - start.getEmbeddableFactories - ); + const action = new ReplacePanelAction(SavedObjectFinder); expect(action.getIconType({ embeddable })).toBeDefined(); }); diff --git a/src/plugins/dashboard/public/application/actions/replace_panel_action.tsx b/src/plugins/dashboard/public/application/actions/replace_panel_action.tsx index 5291b414bc34e..f39988842e3fc 100644 --- a/src/plugins/dashboard/public/application/actions/replace_panel_action.tsx +++ b/src/plugins/dashboard/public/application/actions/replace_panel_action.tsx @@ -6,10 +6,9 @@ * Side Public License, v 1. */ -import { CoreStart } from '@kbn/core/public'; -import { IEmbeddable, ViewMode, EmbeddableStart } from '../../services/embeddable'; -import { DASHBOARD_CONTAINER_TYPE, DashboardContainer } from '../embeddable'; -import { Action, IncompatibleActionError } from '../../services/ui_actions'; +import { type IEmbeddable, ViewMode } from '@kbn/embeddable-plugin/public'; +import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; +import { DASHBOARD_CONTAINER_TYPE, type DashboardContainer } from '../embeddable'; import { openReplacePanelFlyout } from './open_replace_panel_flyout'; import { dashboardReplacePanelAction } from '../../dashboard_strings'; @@ -28,12 +27,7 @@ export class ReplacePanelAction implements Action { public readonly id = ACTION_REPLACE_PANEL; public order = 3; - constructor( - private core: CoreStart, - private savedobjectfinder: React.ComponentType, - private notifications: CoreStart['notifications'], - private getEmbeddableFactories: EmbeddableStart['getEmbeddableFactories'] - ) {} + constructor(private savedobjectfinder: React.ComponentType) {} public getDisplayName({ embeddable }: ReplacePanelActionContext) { if (!embeddable.parent || !isDashboard(embeddable.parent)) { @@ -68,11 +62,8 @@ export class ReplacePanelAction implements Action { const dash = embeddable.parent; openReplacePanelFlyout({ embeddable: dash, - core: this.core, savedObjectFinder: this.savedobjectfinder, - notifications: this.notifications, panelToRemove: view, - getEmbeddableFactories: this.getEmbeddableFactories, }); } } diff --git a/src/plugins/dashboard/public/application/actions/replace_panel_flyout.tsx b/src/plugins/dashboard/public/application/actions/replace_panel_flyout.tsx index 5b112ed754b24..6369ff82b821f 100644 --- a/src/plugins/dashboard/public/application/actions/replace_panel_flyout.tsx +++ b/src/plugins/dashboard/public/application/actions/replace_panel_flyout.tsx @@ -8,25 +8,23 @@ import React from 'react'; import { EuiFlyoutBody, EuiFlyoutHeader, EuiTitle } from '@elastic/eui'; -import { NotificationsStart, Toast } from '@kbn/core/public'; -import { DashboardPanelState } from '../embeddable'; +import { Toast } from '@kbn/core/public'; import { EmbeddableInput, EmbeddableOutput, - EmbeddableStart, IContainer, IEmbeddable, SavedObjectEmbeddableInput, -} from '../../services/embeddable'; +} from '@kbn/embeddable-plugin/public'; +import { DashboardPanelState } from '../embeddable'; import { dashboardReplacePanelAction } from '../../dashboard_strings'; +import { pluginServices } from '../../services/plugin_services'; interface Props { container: IContainer; savedObjectsFinder: React.ComponentType; onClose: () => void; - notifications: NotificationsStart; panelToRemove: IEmbeddable; - getEmbeddableFactories: EmbeddableStart['getEmbeddableFactories']; } export class ReplacePanelFlyout extends React.Component { @@ -39,13 +37,17 @@ export class ReplacePanelFlyout extends React.Component { } public showToast = (name: string) => { + const { + notifications: { toasts }, + } = pluginServices.getServices(); + // To avoid the clutter of having toast messages cover flyout // close previous toast message before creating a new one if (this.lastToast) { - this.props.notifications.toasts.remove(this.lastToast); + toasts.remove(this.lastToast); } - this.lastToast = this.props.notifications.toasts.addSuccess({ + this.lastToast = toasts.addSuccess({ title: dashboardReplacePanelAction.getSuccessMessage(name), 'data-test-subj': 'addObjectToContainerSuccess', }); @@ -84,11 +86,15 @@ export class ReplacePanelFlyout extends React.Component { }; public render() { + const { + embeddable: { getEmbeddableFactories }, + } = pluginServices.getServices(); + const SavedObjectFinder = this.props.savedObjectsFinder; const savedObjectsFinder = ( Boolean(embeddableFactory.savedObjectMetaData) && !embeddableFactory.isContainerType diff --git a/src/plugins/dashboard/public/application/actions/unlink_from_library_action.test.tsx b/src/plugins/dashboard/public/application/actions/unlink_from_library_action.test.tsx index 14a30ce3b90f3..854383edd4e14 100644 --- a/src/plugins/dashboard/public/application/actions/unlink_from_library_action.test.tsx +++ b/src/plugins/dashboard/public/application/actions/unlink_from_library_action.test.tsx @@ -6,8 +6,6 @@ * Side Public License, v 1. */ -import { CoreStart } from '@kbn/core/public'; - import { ViewMode, IContainer, @@ -15,12 +13,7 @@ import { isErrorEmbeddable, ReferenceOrValueEmbeddable, SavedObjectEmbeddableInput, -} from '../../services/embeddable'; -import { UnlinkFromLibraryAction } from '.'; -import { getSampleDashboardInput } from '../test_helpers'; -import { DashboardContainer } from '../embeddable/dashboard_container'; -import { coreMock, uiSettingsServiceMock } from '@kbn/core/public/mocks'; - +} from '@kbn/embeddable-plugin/public'; import { embeddablePluginMock } from '@kbn/embeddable-plugin/public/mocks'; import { ContactCardEmbeddable, @@ -28,41 +21,23 @@ import { ContactCardEmbeddableInput, ContactCardEmbeddableOutput, CONTACT_CARD_EMBEDDABLE, -} from '../../services/embeddable_test_samples'; -import { getStubPluginServices } from '@kbn/presentation-util-plugin/public'; -import { screenshotModePluginMock } from '@kbn/screenshot-mode-plugin/public/mocks'; +} from '@kbn/embeddable-plugin/public/lib/test_samples/embeddables'; -const { setup, doStart } = embeddablePluginMock.createInstance(); -setup.registerEmbeddableFactory( - CONTACT_CARD_EMBEDDABLE, - new ContactCardEmbeddableFactory((() => null) as any, {} as any) -); -const start = doStart(); +import { UnlinkFromLibraryAction } from '.'; +import { getSampleDashboardInput } from '../test_helpers'; +import { DashboardContainer } from '../embeddable/dashboard_container'; +import { pluginServices } from '../../services/plugin_services'; let container: DashboardContainer; let embeddable: ContactCardEmbeddable & ReferenceOrValueEmbeddable; -let coreStart: CoreStart; -beforeEach(async () => { - coreStart = coreMock.createStart(); - - const containerOptions = { - ExitFullScreenButton: () => null, - SavedObjectFinder: () => null, - application: {} as any, - embeddable: start, - inspector: {} as any, - notifications: {} as any, - overlays: coreStart.overlays, - savedObjectMetaData: {} as any, - uiActions: {} as any, - uiSettings: uiSettingsServiceMock.createStartContract(), - http: coreStart.http, - theme: coreStart.theme, - presentationUtil: getStubPluginServices(), - screenshotMode: screenshotModePluginMock.createSetupContract(), - }; - container = new DashboardContainer(getSampleDashboardInput(), containerOptions); +const mockEmbeddableFactory = new ContactCardEmbeddableFactory((() => null) as any, {} as any); +pluginServices.getServices().embeddable.getEmbeddableFactory = jest + .fn() + .mockReturnValue(mockEmbeddableFactory); + +beforeEach(async () => { + container = new DashboardContainer(getSampleDashboardInput()); const contactCardEmbeddable = await container.addNewEmbeddable< ContactCardEmbeddableInput, @@ -86,7 +61,7 @@ beforeEach(async () => { }); test('Unlink is incompatible with Error Embeddables', async () => { - const action = new UnlinkFromLibraryAction({ toasts: coreStart.notifications.toasts }); + const action = new UnlinkFromLibraryAction(); const errorEmbeddable = new ErrorEmbeddable( 'Wow what an awful error', { id: ' 404' }, @@ -96,19 +71,19 @@ test('Unlink is incompatible with Error Embeddables', async () => { }); test('Unlink is compatible when embeddable on dashboard has reference type input', async () => { - const action = new UnlinkFromLibraryAction({ toasts: coreStart.notifications.toasts }); + const action = new UnlinkFromLibraryAction(); embeddable.updateInput(await embeddable.getInputAsRefType()); expect(await action.isCompatible({ embeddable })).toBe(true); }); test('Unlink is not compatible when embeddable input is by value', async () => { - const action = new UnlinkFromLibraryAction({ toasts: coreStart.notifications.toasts }); + const action = new UnlinkFromLibraryAction(); embeddable.updateInput(await embeddable.getInputAsValueType()); expect(await action.isCompatible({ embeddable })).toBe(false); }); test('Unlink is not compatible when view mode is set to view', async () => { - const action = new UnlinkFromLibraryAction({ toasts: coreStart.notifications.toasts }); + const action = new UnlinkFromLibraryAction(); embeddable.updateInput(await embeddable.getInputAsRefType()); embeddable.updateInput({ viewMode: ViewMode.VIEW }); expect(await action.isCompatible({ embeddable })).toBe(false); @@ -129,7 +104,7 @@ test('Unlink is not compatible when embeddable is not in a dashboard container', mockedByReferenceInput: { savedObjectId: 'test', id: orphanContactCard.id }, mockedByValueInput: { firstName: 'Kibanana', id: orphanContactCard.id }, }); - const action = new UnlinkFromLibraryAction({ toasts: coreStart.notifications.toasts }); + const action = new UnlinkFromLibraryAction(); expect(await action.isCompatible({ embeddable: orphanContactCard })).toBe(false); }); @@ -137,7 +112,7 @@ test('Unlink replaces embeddableId and retains panel count', async () => { const dashboard = embeddable.getRoot() as IContainer; const originalPanelCount = Object.keys(dashboard.getInput().panels).length; const originalPanelKeySet = new Set(Object.keys(dashboard.getInput().panels)); - const action = new UnlinkFromLibraryAction({ toasts: coreStart.notifications.toasts }); + const action = new UnlinkFromLibraryAction(); await action.execute({ embeddable }); expect(Object.keys(container.getInput().panels).length).toEqual(originalPanelCount); @@ -167,7 +142,7 @@ test('Unlink unwraps all attributes from savedObject', async () => { }); const dashboard = embeddable.getRoot() as IContainer; const originalPanelKeySet = new Set(Object.keys(dashboard.getInput().panels)); - const action = new UnlinkFromLibraryAction({ toasts: coreStart.notifications.toasts }); + const action = new UnlinkFromLibraryAction(); await action.execute({ embeddable }); const newPanelId = Object.keys(container.getInput().panels).find( (key) => !originalPanelKeySet.has(key) diff --git a/src/plugins/dashboard/public/application/actions/unlink_from_library_action.tsx b/src/plugins/dashboard/public/application/actions/unlink_from_library_action.tsx index c0b5162cb019b..e399411e77fee 100644 --- a/src/plugins/dashboard/public/application/actions/unlink_from_library_action.tsx +++ b/src/plugins/dashboard/public/application/actions/unlink_from_library_action.tsx @@ -6,19 +6,19 @@ * Side Public License, v 1. */ -import { NotificationsStart } from '@kbn/core/public'; -import { Action, IncompatibleActionError } from '../../services/ui_actions'; import { ViewMode, - PanelState, - IEmbeddable, + type PanelState, + type IEmbeddable, + isErrorEmbeddable, PanelNotFoundError, - EmbeddableInput, + type EmbeddableInput, isReferenceOrValueEmbeddable, - isErrorEmbeddable, -} from '../../services/embeddable'; +} from '@kbn/embeddable-plugin/public'; +import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; import { dashboardUnlinkFromLibraryAction } from '../../dashboard_strings'; -import { DashboardPanelState, DASHBOARD_CONTAINER_TYPE, DashboardContainer } from '..'; +import { type DashboardPanelState, DASHBOARD_CONTAINER_TYPE, type DashboardContainer } from '..'; +import { pluginServices } from '../../services/plugin_services'; export const ACTION_UNLINK_FROM_LIBRARY = 'unlinkFromLibrary'; @@ -31,7 +31,13 @@ export class UnlinkFromLibraryAction implements Action().services; + chrome: { setBreadcrumbs, setIsVisible }, + coreContext: { executionContext }, + data: { search }, + embeddable: { getStateTransfer }, + notifications: { toasts }, + screenshotMode: { isScreenshotMode }, + settings: { uiSettings }, + spaces: { getLegacyUrlConflict }, + } = pluginServices.getServices(); const [showNoDataPage, setShowNoDataPage] = useState(false); const dashboardTitleRef = useRef(null); @@ -57,12 +60,12 @@ export function DashboardApp({ createKbnUrlStateStorage({ history, useHash: uiSettings.get('state:storeInSessionStorage'), - ...withNotifyOnErrors(core.notifications.toasts), + ...withNotifyOnErrors(toasts), }), - [core.notifications.toasts, history, uiSettings] + [toasts, history, uiSettings] ); - useExecutionContext(core.executionContext, { + useExecutionContext(executionContext, { type: 'application', page: 'app', id: savedDashboardId || 'new', @@ -90,10 +93,7 @@ export function DashboardApp({ // Build app leave handler whenever hasUnsavedChanges changes useEffect(() => { onAppLeave((actions) => { - if ( - dashboardAppState.hasUnsavedChanges && - !embeddable.getStateTransfer().isTransferInProgress - ) { + if (dashboardAppState.hasUnsavedChanges && !getStateTransfer().isTransferInProgress) { return actions.confirm( leaveConfirmStrings.getLeaveSubtitle(), leaveConfirmStrings.getLeaveTitle() @@ -105,12 +105,12 @@ export function DashboardApp({ // reset on app leave handler so leaving from the listing page doesn't trigger a confirmation onAppLeave((actions) => actions.default()); }; - }, [onAppLeave, embeddable, dashboardAppState.hasUnsavedChanges]); + }, [onAppLeave, getStateTransfer, dashboardAppState.hasUnsavedChanges]); // Set breadcrumbs when dashboard's title or view mode changes useEffect(() => { if (!dashboardState.title && savedDashboardId) return; - chrome.setBreadcrumbs([ + setBreadcrumbs([ { text: getDashboardBreadcrumb(), 'data-test-subj': 'dashboardListingBreadcrumb', @@ -122,14 +122,14 @@ export function DashboardApp({ text: dashboardTitle, }, ]); - }, [chrome, dashboardState.title, redirectTo, savedDashboardId, dashboardTitle]); + }, [setBreadcrumbs, dashboardState.title, redirectTo, savedDashboardId, dashboardTitle]); // clear search session when leaving dashboard route useEffect(() => { return () => { - data.search.session.clear(); + search.session.clear(); }; - }, [data.search.session]); + }, [search.session]); const printMode = useMemo( () => dashboardAppState.getLatestDashboardState?.().viewMode === ViewMode.PRINT, @@ -137,8 +137,8 @@ export function DashboardApp({ ); useEffect(() => { - if (!embedSettings) chrome.setIsVisible(!printMode); - }, [chrome, printMode, embedSettings]); + if (!embedSettings) setIsVisible(!printMode); + }, [setIsVisible, printMode, embedSettings]); return ( <> @@ -163,7 +163,7 @@ export function DashboardApp({ {dashboardAppState.savedDashboard.outcome === 'conflict' && dashboardAppState.savedDashboard.id && dashboardAppState.savedDashboard.aliasId - ? spacesService?.ui.components.getLegacyUrlConflict({ + ? getLegacyUrlConflict?.({ currentObjectId: dashboardAppState.savedDashboard.id, otherObjectId: dashboardAppState.savedDashboard.aliasId, otherObjectPath: `#${createDashboardEditUrl( @@ -173,9 +173,7 @@ export function DashboardApp({ : null}
diff --git a/src/plugins/dashboard/public/application/dashboard_app_no_data.tsx b/src/plugins/dashboard/public/application/dashboard_app_no_data.tsx index 7cd43e635b495..5198c625a198c 100644 --- a/src/plugins/dashboard/public/application/dashboard_app_no_data.tsx +++ b/src/plugins/dashboard/public/application/dashboard_app_no_data.tsx @@ -11,10 +11,8 @@ import { AnalyticsNoDataPageKibanaProvider, AnalyticsNoDataPage, } from '@kbn/shared-ux-page-analytics-no-data'; -import { DataPublicPluginStart } from '@kbn/data-plugin/public'; -import { DashboardAppServices } from '../types'; -import { useKibana } from '../services/kibana_react'; +import { pluginServices } from '../services/plugin_services'; export const DashboardAppNoDataPage = ({ onDataViewCreated, @@ -22,13 +20,25 @@ export const DashboardAppNoDataPage = ({ onDataViewCreated: () => void; }) => { const { - services: { core, data, dataViewEditor }, - } = useKibana(); + application, + data: { dataViews }, + dataViewEditor, + http: { basePath }, + documentationLinks: { indexPatternsDocLink, kibanaGuideDocLink }, + } = pluginServices.getServices(); + const analyticsServices = { - coreStart: core as unknown as React.ComponentProps< - typeof AnalyticsNoDataPageKibanaProvider - >['coreStart'], - dataViews: data.dataViews, + coreStart: { + docLinks: { + links: { + kibana: { guide: kibanaGuideDocLink }, + indexPatterns: { introduction: indexPatternsDocLink }, + }, + }, + application, + http: { basePath }, + }, + dataViews, dataViewEditor, }; return ( @@ -38,9 +48,11 @@ export const DashboardAppNoDataPage = ({ ); }; -export const isDashboardAppInNoDataState = async ( - dataViews: DataPublicPluginStart['dataViews'] -) => { +export const isDashboardAppInNoDataState = async () => { + const { + data: { dataViews }, + } = pluginServices.getServices(); + const hasUserDataView = await dataViews.hasData.hasUserDataView().catch(() => false); return !hasUserDataView; }; diff --git a/src/plugins/dashboard/public/application/dashboard_router.tsx b/src/plugins/dashboard/public/application/dashboard_router.tsx index adafcb3d1f6dd..cc683ba63149c 100644 --- a/src/plugins/dashboard/public/application/dashboard_router.tsx +++ b/src/plugins/dashboard/public/application/dashboard_router.tsx @@ -10,35 +10,31 @@ import './index.scss'; import React from 'react'; import { History } from 'history'; import { Provider } from 'react-redux'; -import { first } from 'rxjs/operators'; -import { I18nProvider } from '@kbn/i18n-react'; import { parse, ParsedQuery } from 'query-string'; import { render, unmountComponentAtNode } from 'react-dom'; import { Switch, Route, RouteComponentProps, HashRouter, Redirect } from 'react-router-dom'; +import { I18nProvider } from '@kbn/i18n-react'; +import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; +import { createKbnUrlStateStorage, withNotifyOnErrors } from '@kbn/kibana-utils-plugin/public'; +import { AppMountParameters, CoreSetup } from '@kbn/core/public'; + import { DashboardListing } from './listing'; import { dashboardStateStore } from './state'; import { DashboardApp } from './dashboard_app'; +import { addHelpMenuToAppChrome } from './lib'; import { DashboardNoMatch } from './listing/dashboard_no_match'; -import { KibanaContextProvider, KibanaThemeProvider } from '../services/kibana_react'; -import { addHelpMenuToAppChrome, DashboardSessionStorage } from './lib'; import { createDashboardListingFilterUrl } from '../dashboard_constants'; import { createDashboardEditUrl, DashboardConstants } from '../dashboard_constants'; -import { getDashboardPageTitle, dashboardReadonlyBadge } from '../dashboard_strings'; -import { createKbnUrlStateStorage, withNotifyOnErrors } from '../services/kibana_utils'; -import { DashboardAppServices, DashboardEmbedSettings, RedirectToProps } from '../types'; -import { - DashboardFeatureFlagConfig, - DashboardSetupDependencies, - DashboardStart, - DashboardStartDependencies, -} from '../plugin'; +import { dashboardReadonlyBadge, getDashboardPageTitle } from '../dashboard_strings'; import { - AppMountParameters, - CoreSetup, - PluginInitializerContext, - ScopedHistory, -} from '../services/core'; + DashboardAppServices, + DashboardEmbedSettings, + RedirectToProps, + DashboardMountContextProps, +} from '../types'; +import { DashboardStart, DashboardStartDependencies } from '../plugin'; +import { pluginServices } from '../services/plugin_services'; export const dashboardUrlParams = { showTopMenu: 'show-top-menu', @@ -49,97 +45,35 @@ export const dashboardUrlParams = { export interface DashboardMountProps { appUnMounted: () => void; - restorePreviousUrl: () => void; - - scopedHistory: ScopedHistory; element: AppMountParameters['element']; - initializerContext: PluginInitializerContext; - onAppLeave: AppMountParameters['onAppLeave']; core: CoreSetup; - setHeaderActionMenu: AppMountParameters['setHeaderActionMenu']; - usageCollection: DashboardSetupDependencies['usageCollection']; + mountContext: DashboardMountContextProps; } -export async function mountApp({ - core, - element, - onAppLeave, - appUnMounted, - scopedHistory, - usageCollection, - initializerContext, - restorePreviousUrl, - setHeaderActionMenu, -}: DashboardMountProps) { - const [coreStart, pluginsStart, dashboardStart] = await core.getStartServices(); +export async function mountApp({ core, element, appUnMounted, mountContext }: DashboardMountProps) { + const [, , dashboardStart] = await core.getStartServices(); // TODO: Remove as part of https://github.com/elastic/kibana/pull/138774 + const { DashboardMountContext } = await import('./hooks/dashboard_mount_context'); const { - navigation, - savedObjects, - urlForwarding, + chrome: { setBadge, docTitle }, + dashboardCapabilities: { showWriteControls }, data: dataStart, - share: shareStart, - spaces: spacesApi, - embeddable: embeddableStart, - savedObjectsTaggingOss, - visualizations, - presentationUtil, - screenshotMode, - dataViewEditor, - } = pluginsStart; + embeddable, + settings: { uiSettings }, + } = pluginServices.getServices(); - const activeSpaceId = - spacesApi && (await spacesApi.getActiveSpace$().pipe(first()).toPromise())?.id; let globalEmbedSettings: DashboardEmbedSettings | undefined; let routerHistory: History; + // TODO: Remove as part of https://github.com/elastic/kibana/pull/138774 const dashboardServices: DashboardAppServices = { - navigation, - onAppLeave, - savedObjects, - urlForwarding, - dataViewEditor, - visualizations, - usageCollection, - core: coreStart, - data: dataStart, - share: shareStart, - initializerContext, - restorePreviousUrl, - setHeaderActionMenu, - chrome: coreStart.chrome, - embeddable: embeddableStart, - uiSettings: coreStart.uiSettings, - scopedHistory: () => scopedHistory, - screenshotModeService: screenshotMode, - dataViews: dataStart.dataViews, - savedQueryService: dataStart.query.savedQueries, - savedObjectsClient: coreStart.savedObjects.client, savedDashboards: dashboardStart.getSavedDashboardLoader(), - savedObjectsTagging: savedObjectsTaggingOss?.getTaggingApi(), - allowByValueEmbeddables: - initializerContext.config.get().allowByValueEmbeddables, - dashboardCapabilities: { - show: Boolean(coreStart.application.capabilities.dashboard.show), - saveQuery: Boolean(coreStart.application.capabilities.dashboard.saveQuery), - createNew: Boolean(coreStart.application.capabilities.dashboard.createNew), - mapsCapabilities: { save: Boolean(coreStart.application.capabilities.maps?.save) }, - createShortUrl: Boolean(coreStart.application.capabilities.dashboard.createShortUrl), - showWriteControls: Boolean(coreStart.application.capabilities.dashboard.showWriteControls), - visualizeCapabilities: { save: Boolean(coreStart.application.capabilities.visualize?.save) }, - storeSearchSession: Boolean(coreStart.application.capabilities.dashboard.storeSearchSession), - }, - dashboardSessionStorage: new DashboardSessionStorage( - core.notifications.toasts, - activeSpaceId || 'default' - ), - spacesService: spacesApi, }; const getUrlStateStorage = (history: RouteComponentProps['history']) => createKbnUrlStateStorage({ history, - useHash: coreStart.uiSettings.get('state:storeInSessionStorage'), + useHash: uiSettings.get('state:storeInSessionStorage'), ...withNotifyOnErrors(core.notifications.toasts), }); @@ -187,7 +121,7 @@ export async function mountApp({ }; const renderListingPage = (routeProps: RouteComponentProps) => { - coreStart.chrome.docTitle.change(getDashboardPageTitle()); + docTitle.change(getDashboardPageTitle()); const routeParams = parse(routeProps.history.location.search); const title = (routeParams.title as string) || undefined; const filter = (routeParams.filter as string) || undefined; @@ -209,7 +143,7 @@ export async function mountApp({ }; const hasEmbeddableIncoming = Boolean( - dashboardServices.embeddable + embeddable .getStateTransfer() .getIncomingEmbeddablePackage(DashboardConstants.DASHBOARDS_ID, false) ); @@ -219,15 +153,16 @@ export async function mountApp({ // dispatch synthetic hash change event to update hash history objects // this is necessary because hash updates triggered by using popState won't trigger this event naturally. - const unlistenParentHistory = scopedHistory.listen(() => { + const unlistenParentHistory = mountContext.scopedHistory().listen(() => { window.dispatchEvent(new HashChangeEvent('hashchange')); }); const app = ( + // TODO: Remove KibanaContextProvider as part of https://github.com/elastic/kibana/pull/138774 - + @@ -250,15 +185,16 @@ export async function mountApp({ - + ); - addHelpMenuToAppChrome(dashboardServices.chrome, coreStart.docLinks); - if (!dashboardServices.dashboardCapabilities.showWriteControls) { - coreStart.chrome.setBadge({ + addHelpMenuToAppChrome(); + + if (!showWriteControls) { + setBadge({ text: dashboardReadonlyBadge.getText(), tooltip: dashboardReadonlyBadge.getTooltip(), iconType: 'glasses', diff --git a/src/plugins/dashboard/public/application/embeddable/dashboard_container.test.tsx b/src/plugins/dashboard/public/application/embeddable/dashboard_container.test.tsx index 063301fcdde17..b6c3d2055d88c 100644 --- a/src/plugins/dashboard/public/application/embeddable/dashboard_container.test.tsx +++ b/src/plugins/dashboard/public/application/embeddable/dashboard_container.test.tsx @@ -10,60 +10,40 @@ import React from 'react'; import { mount } from 'enzyme'; import { findTestSubject, nextTick } from '@kbn/test-jest-helpers'; -import { DashboardContainer, DashboardContainerServices } from './dashboard_container'; -import { getSampleDashboardInput, getSampleDashboardPanel } from '../test_helpers'; import { I18nProvider } from '@kbn/i18n-react'; -import { embeddablePluginMock } from '@kbn/embeddable-plugin/public/mocks'; - -import { KibanaContextProvider } from '../../services/kibana_react'; import { CONTEXT_MENU_TRIGGER, EmbeddablePanel, isErrorEmbeddable, ViewMode, -} from '../../services/embeddable'; +} from '@kbn/embeddable-plugin/public'; import { - CONTACT_CARD_EMBEDDABLE, + ContactCardEmbeddable, ContactCardEmbeddableFactory, ContactCardEmbeddableInput, - ContactCardEmbeddable, - EMPTY_EMBEDDABLE, ContactCardEmbeddableOutput, - createEditModeAction, -} from '../../services/embeddable_test_samples'; -import { applicationServiceMock, coreMock, uiSettingsServiceMock } from '@kbn/core/public/mocks'; -import { inspectorPluginMock } from '@kbn/inspector-plugin/public/mocks'; + CONTACT_CARD_EMBEDDABLE, + EMPTY_EMBEDDABLE, +} from '@kbn/embeddable-plugin/public/lib/test_samples/embeddables'; +import { applicationServiceMock, coreMock } from '@kbn/core/public/mocks'; import { uiActionsPluginMock } from '@kbn/ui-actions-plugin/public/mocks'; -import { getStubPluginServices } from '@kbn/presentation-util-plugin/public'; +import { createEditModeAction } from '@kbn/embeddable-plugin/public/lib/test_samples'; + +import { DashboardContainer } from './dashboard_container'; +import { getSampleDashboardInput, getSampleDashboardPanel } from '../test_helpers'; +import { pluginServices } from '../../services/plugin_services'; +import { ApplicationStart } from '@kbn/core-application-browser'; -const presentationUtil = getStubPluginServices(); const theme = coreMock.createStart().theme; +let application: ApplicationStart | undefined; -const options: DashboardContainerServices = { - // TODO: clean up use of any - application: {} as any, - embeddable: {} as any, - notifications: {} as any, - overlays: {} as any, - inspector: {} as any, - screenshotMode: {} as any, - SavedObjectFinder: () => null, - ExitFullScreenButton: () => null, - uiActions: {} as any, - uiSettings: uiSettingsServiceMock.createStartContract(), - http: coreMock.createStart().http, - theme, - presentationUtil, -}; +const embeddableFactory = new ContactCardEmbeddableFactory((() => null) as any, {} as any); +pluginServices.getServices().embeddable.getEmbeddableFactory = jest + .fn() + .mockReturnValue(embeddableFactory); beforeEach(() => { - const { setup, doStart } = embeddablePluginMock.createInstance(); - setup.registerEmbeddableFactory( - CONTACT_CARD_EMBEDDABLE, - new ContactCardEmbeddableFactory((() => null) as any, {} as any) - ); - options.embeddable = doStart(); - options.application = applicationServiceMock.createStartContract(); + application = applicationServiceMock.createStartContract(); }); test('DashboardContainer initializes embeddables', async (done) => { @@ -75,7 +55,7 @@ test('DashboardContainer initializes embeddables', async (done) => { }), }, }); - const container = new DashboardContainer(initialInput, options); + const container = new DashboardContainer(initialInput); const subscription = container.getOutput$().subscribe((output) => { if (container.getOutput().embeddableLoaded['123']) { @@ -96,7 +76,7 @@ test('DashboardContainer initializes embeddables', async (done) => { }); test('DashboardContainer.addNewEmbeddable', async () => { - const container = new DashboardContainer(getSampleDashboardInput(), options); + const container = new DashboardContainer(getSampleDashboardInput()); const embeddable = await container.addNewEmbeddable( CONTACT_CARD_EMBEDDABLE, { @@ -127,7 +107,7 @@ test('DashboardContainer.replacePanel', async (done) => { }, }); - const container = new DashboardContainer(initialInput, options); + const container = new DashboardContainer(initialInput); let counter = 0; const subscription = container.getInput$().subscribe( @@ -168,7 +148,7 @@ test('Container view mode change propagates to existing children', async (done) }), }, }); - const container = new DashboardContainer(initialInput, options); + const container = new DashboardContainer(initialInput); const embeddable = await container.untilEmbeddableLoaded('123'); expect(embeddable.getInput().viewMode).toBe(ViewMode.VIEW); @@ -178,7 +158,7 @@ test('Container view mode change propagates to existing children', async (done) }); test('Container view mode change propagates to new children', async () => { - const container = new DashboardContainer(getSampleDashboardInput(), options); + const container = new DashboardContainer(getSampleDashboardInput()); const embeddable = await container.addNewEmbeddable< ContactCardEmbeddableInput, ContactCardEmbeddableOutput, @@ -197,8 +177,7 @@ test('Container view mode change propagates to new children', async () => { test('searchSessionId propagates to children', async () => { const searchSessionId1 = 'searchSessionId1'; const container = new DashboardContainer( - getSampleDashboardInput({ searchSessionId: searchSessionId1 }), - options + getSampleDashboardInput({ searchSessionId: searchSessionId1 }) ); const embeddable = await container.addNewEmbeddable< ContactCardEmbeddableInput, @@ -217,7 +196,6 @@ test('searchSessionId propagates to children', async () => { }); test('DashboardContainer in edit mode shows edit mode actions', async () => { - const inspector = inspectorPluginMock.createStartContract(); const uiActionsSetup = uiActionsPluginMock.createSetupContract(); const editModeAction = createEditModeAction(); @@ -225,7 +203,7 @@ test('DashboardContainer in edit mode shows edit mode actions', async () => { uiActionsSetup.addTriggerAction(CONTEXT_MENU_TRIGGER, editModeAction); const initialInput = getSampleDashboardInput({ viewMode: ViewMode.VIEW }); - const container = new DashboardContainer(initialInput, options); + const container = new DashboardContainer(initialInput); const embeddable = await container.addNewEmbeddable< ContactCardEmbeddableInput, @@ -235,24 +213,22 @@ test('DashboardContainer in edit mode shows edit mode actions', async () => { firstName: 'Bob', }); + const DashboardServicesProvider = pluginServices.getContextProvider(); + const component = mount( - - - Promise.resolve([])} - getAllEmbeddableFactories={(() => []) as any} - getEmbeddableFactory={(() => null) as any} - notifications={{} as any} - application={options.application} - overlays={{} as any} - inspector={inspector} - SavedObjectFinder={() => null} - theme={theme} - /> - - + + Promise.resolve([])} + getAllEmbeddableFactories={(() => []) as any} + getEmbeddableFactory={(() => null) as any} + notifications={{} as any} + application={application} + SavedObjectFinder={() => null} + theme={theme} + /> + ); diff --git a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx index cd95877c0aba6..4f7483cf06f35 100644 --- a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx +++ b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx @@ -7,69 +7,44 @@ */ import React from 'react'; +import uuid from 'uuid'; import ReactDOM from 'react-dom'; + import { I18nProvider } from '@kbn/i18n-react'; import { Subscription } from 'rxjs'; -import uuid from 'uuid'; -import { CoreStart, IUiSettingsClient, KibanaExecutionContext } from '@kbn/core/public'; -import { Start as InspectorStartContract } from '@kbn/inspector-plugin/public'; +import type { KibanaExecutionContext } from '@kbn/core/public'; import { reportPerformanceMetricEvent } from '@kbn/ebt-tools'; - -import { ControlGroupContainer } from '@kbn/controls-plugin/public'; -import { Filter, TimeRange } from '@kbn/es-query'; -import { DataView } from '@kbn/data-views-plugin/public'; - -import { UiActionsStart } from '../../services/ui_actions'; -import { RefreshInterval, Query } from '../../services/data'; +import type { ControlGroupContainer } from '@kbn/controls-plugin/public'; +import type { Filter, TimeRange } from '@kbn/es-query'; +import type { DataView } from '@kbn/data-views-plugin/public'; import { ViewMode, Container, - PanelState, - IEmbeddable, - EmbeddableInput, - EmbeddableStart, - EmbeddableOutput, - EmbeddableFactory, + type PanelState, + type IEmbeddable, + type EmbeddableInput, + type EmbeddableOutput, + type EmbeddableFactory, ErrorEmbeddable, isErrorEmbeddable, -} from '../../services/embeddable'; +} from '@kbn/embeddable-plugin/public'; +import type { Query } from '@kbn/es-query'; +import type { RefreshInterval } from '@kbn/data-plugin/public'; +import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; + import { DASHBOARD_CONTAINER_TYPE } from './dashboard_constants'; import { createPanelState } from './panel'; import { DashboardPanelState } from './types'; import { DashboardViewport } from './viewport/dashboard_viewport'; -import { - KibanaContextProvider, - KibanaReactContext, - KibanaReactContextValue, - KibanaThemeProvider, -} from '../../services/kibana_react'; import { PLACEHOLDER_EMBEDDABLE } from './placeholder'; import { DASHBOARD_LOADED_EVENT } from '../../events'; -import { DashboardAppCapabilities, DashboardContainerInput } from '../../types'; -import { PresentationUtilPluginStart } from '../../services/presentation_util'; -import type { ScreenshotModePluginStart } from '../../services/screenshot_mode'; +import { DashboardContainerInput } from '../../types'; import { PanelPlacementMethod, IPanelPlacementArgs } from './panel/dashboard_panel_placement'; import { combineDashboardFiltersWithControlGroupFilters, syncDashboardControlGroup, } from '../lib/dashboard_control_group'; - -export interface DashboardContainerServices { - ExitFullScreenButton: React.ComponentType; - presentationUtil: PresentationUtilPluginStart; - SavedObjectFinder: React.ComponentType; - notifications: CoreStart['notifications']; - application: CoreStart['application']; - inspector: InspectorStartContract; - overlays: CoreStart['overlays']; - screenshotMode: ScreenshotModePluginStart; - uiSettings: IUiSettingsClient; - embeddable: EmbeddableStart; - uiActions: UiActionsStart; - theme: CoreStart['theme']; - http: CoreStart['http']; - analytics?: CoreStart['analytics']; -} +import { pluginServices } from '../../services/plugin_services'; export interface DashboardLoadedInfo { timeToData: number; @@ -97,20 +72,6 @@ export interface InheritedChildInput extends IndexSignature { executionContext?: KibanaExecutionContext; } -export type DashboardReactContextValue = KibanaReactContextValue; -export type DashboardReactContext = KibanaReactContext; - -const defaultCapabilities: DashboardAppCapabilities = { - show: false, - createNew: false, - saveQuery: false, - createShortUrl: false, - showWriteControls: false, - mapsCapabilities: { save: false }, - visualizeCapabilities: { save: false }, - storeSearchSession: true, -}; - export class DashboardContainer extends Container { public readonly type = DASHBOARD_CONTAINER_TYPE; @@ -122,6 +83,10 @@ export class DashboardContainer extends Container - - - - - - - + + + , dom ); diff --git a/src/plugins/dashboard/public/application/embeddable/dashboard_container_by_value_renderer.tsx b/src/plugins/dashboard/public/application/embeddable/dashboard_container_by_value_renderer.tsx index 4384dc06cee9a..8b62703600893 100644 --- a/src/plugins/dashboard/public/application/embeddable/dashboard_container_by_value_renderer.tsx +++ b/src/plugins/dashboard/public/application/embeddable/dashboard_container_by_value_renderer.tsx @@ -7,9 +7,9 @@ */ import * as React from 'react'; +import { EmbeddableRenderer } from '@kbn/embeddable-plugin/public'; import { DashboardContainerInput } from '../..'; import { DashboardContainerFactory } from './dashboard_container_factory'; -import { EmbeddableRenderer } from '../../services/embeddable'; interface Props { input: DashboardContainerInput; diff --git a/src/plugins/dashboard/public/application/embeddable/dashboard_container_factory.tsx b/src/plugins/dashboard/public/application/embeddable/dashboard_container_factory.tsx index 52786ff05c9e8..27670ee104367 100644 --- a/src/plugins/dashboard/public/application/embeddable/dashboard_container_factory.tsx +++ b/src/plugins/dashboard/public/application/embeddable/dashboard_container_factory.tsx @@ -17,20 +17,22 @@ import { CONTROL_GROUP_TYPE, } from '@kbn/controls-plugin/public'; import { getDefaultControlGroupInput } from '@kbn/controls-plugin/common'; -import { DashboardContainerInput } from '../..'; -import { DASHBOARD_CONTAINER_TYPE } from './dashboard_constants'; -import type { DashboardContainer, DashboardContainerServices } from './dashboard_container'; import { Container, ErrorEmbeddable, ContainerOutput, EmbeddableFactory, EmbeddableFactoryDefinition, -} from '../../services/embeddable'; +} from '@kbn/embeddable-plugin/public'; + +import { DashboardContainerInput } from '../..'; +import { DASHBOARD_CONTAINER_TYPE } from './dashboard_constants'; +import type { DashboardContainer } from './dashboard_container'; import { createExtract, createInject, } from '../../../common/embeddable/dashboard_container_persistable_state'; +import { pluginServices } from '../../services/plugin_services'; export type DashboardContainerFactory = EmbeddableFactory< DashboardContainerInput, @@ -47,10 +49,7 @@ export class DashboardContainerFactoryDefinition public inject: EmbeddablePersistableStateService['inject']; public extract: EmbeddablePersistableStateService['extract']; - constructor( - private readonly getStartServices: () => Promise, - private readonly persistableStateService: EmbeddablePersistableStateService - ) { + constructor(private readonly persistableStateService: EmbeddablePersistableStateService) { this.inject = createInject(this.persistableStateService); this.extract = createExtract(this.persistableStateService); } @@ -81,8 +80,11 @@ export class DashboardContainerFactoryDefinition initialInput: DashboardContainerInput, parent?: Container ): Promise => { - const services = await this.getStartServices(); - const controlsGroupFactory = services.embeddable.getEmbeddableFactory< + const { + embeddable: { getEmbeddableFactory }, + } = pluginServices.getServices(); + + const controlsGroupFactory = getEmbeddableFactory< ControlGroupInput, ControlGroupOutput, ControlGroupContainer @@ -97,10 +99,11 @@ export class DashboardContainerFactoryDefinition filters, query, }); + const { DashboardContainer: DashboardContainerEmbeddable } = await import( './dashboard_container' ); - return new DashboardContainerEmbeddable(initialInput, services, parent, controlGroup); + return Promise.resolve(new DashboardContainerEmbeddable(initialInput, parent, controlGroup)); }; } diff --git a/src/plugins/dashboard/public/application/embeddable/empty_screen/dashboard_empty_screen.test.tsx b/src/plugins/dashboard/public/application/embeddable/empty_screen/dashboard_empty_screen.test.tsx index b3ee8ddf758b2..3a0164b00b0e4 100644 --- a/src/plugins/dashboard/public/application/embeddable/empty_screen/dashboard_empty_screen.test.tsx +++ b/src/plugins/dashboard/public/application/embeddable/empty_screen/dashboard_empty_screen.test.tsx @@ -10,21 +10,22 @@ import React from 'react'; import { mountWithIntl } from '@kbn/test-jest-helpers'; import { findTestSubject } from '@elastic/eui/lib/test'; import { DashboardEmptyScreen, DashboardEmptyScreenProps } from './dashboard_empty_screen'; -import { coreMock } from '@kbn/core/public/mocks'; +import { pluginServices } from '../../../services/plugin_services'; describe('DashboardEmptyScreen', () => { - const setupMock = coreMock.createSetup(); + const DashboardServicesProvider = pluginServices.getContextProvider(); const defaultProps = { - isEditMode: false, onLinkClick: jest.fn(), - uiSettings: setupMock.uiSettings, - http: setupMock.http, }; function mountComponent(props?: Partial) { const compProps = { ...defaultProps, ...props }; - return mountWithIntl(); + return mountWithIntl( + + + + ); } test('renders correctly with view mode', () => { @@ -44,7 +45,9 @@ describe('DashboardEmptyScreen', () => { }); test('renders correctly with readonly mode', () => { - const component = mountComponent({ isReadonlyMode: true }); + pluginServices.getServices().dashboardCapabilities.showWriteControls = false; + + const component = mountComponent(); expect(component.render()).toMatchSnapshot(); const paragraph = component.find('.dshStartScreen__panelDesc'); expect(paragraph.length).toBe(0); diff --git a/src/plugins/dashboard/public/application/embeddable/empty_screen/dashboard_empty_screen.tsx b/src/plugins/dashboard/public/application/embeddable/empty_screen/dashboard_empty_screen.tsx index ff6fe501e13cb..9c5a98388350b 100644 --- a/src/plugins/dashboard/public/application/embeddable/empty_screen/dashboard_empty_screen.tsx +++ b/src/plugins/dashboard/public/application/embeddable/empty_screen/dashboard_empty_screen.tsx @@ -18,22 +18,21 @@ import { EuiText, EuiTitle, } from '@elastic/eui'; -import { IUiSettingsClient, HttpStart } from '@kbn/core/public'; import { emptyScreenStrings } from '../../../dashboard_strings'; +import { pluginServices } from '../../../services/plugin_services'; export interface DashboardEmptyScreenProps { isEditMode?: boolean; - uiSettings: IUiSettingsClient; - http: HttpStart; - isReadonlyMode?: boolean; } -export function DashboardEmptyScreen({ - isEditMode, - uiSettings, - http, - isReadonlyMode, -}: DashboardEmptyScreenProps) { +export function DashboardEmptyScreen({ isEditMode }: DashboardEmptyScreenProps) { + const { + dashboardCapabilities: { showWriteControls }, + http: { basePath }, + settings: { uiSettings }, + } = pluginServices.getServices(); + const isReadonlyMode = !showWriteControls; + const IS_DARK_THEME = uiSettings.get('theme:darkMode'); const emptyStateGraphicURL = IS_DARK_THEME ? '/plugins/home/assets/welcome_graphic_dark_2x.png' @@ -53,7 +52,7 @@ export function DashboardEmptyScreen({ paddingSize="none" className="dshStartScreen__pageContent" > - +

{mainText}

diff --git a/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.test.tsx b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.test.tsx index c0bfe41b2c86d..41e8b900d360f 100644 --- a/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.test.tsx +++ b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.test.tsx @@ -8,35 +8,29 @@ // @ts-ignore import sizeMe from 'react-sizeme'; - import React from 'react'; -import { mountWithIntl } from '@kbn/test-jest-helpers'; import { skip } from 'rxjs/operators'; + +import { mountWithIntl } from '@kbn/test-jest-helpers'; + import { DashboardGrid, DashboardGridProps } from './dashboard_grid'; -import { DashboardContainer, DashboardContainerServices } from '../dashboard_container'; +import { DashboardContainer } from '../dashboard_container'; import { getSampleDashboardInput } from '../../test_helpers'; -import { KibanaContextProvider } from '../../../services/kibana_react'; -import { embeddablePluginMock } from '@kbn/embeddable-plugin/public/mocks'; import { - CONTACT_CARD_EMBEDDABLE, ContactCardEmbeddableFactory, -} from '../../../services/embeddable_test_samples'; -import { coreMock, uiSettingsServiceMock } from '@kbn/core/public/mocks'; -import { getStubPluginServices } from '@kbn/presentation-util-plugin/public'; -import { screenshotModePluginMock } from '@kbn/screenshot-mode-plugin/public/mocks'; + CONTACT_CARD_EMBEDDABLE, +} from '@kbn/embeddable-plugin/public/lib/test_samples/embeddables'; +import { pluginServices } from '../../../services/plugin_services'; let dashboardContainer: DashboardContainer | undefined; -const presentationUtil = getStubPluginServices(); +const DashboardServicesProvider = pluginServices.getContextProvider(); function prepare(props?: Partial) { - const { setup, doStart } = embeddablePluginMock.createInstance(); - setup.registerEmbeddableFactory( - CONTACT_CARD_EMBEDDABLE, - new ContactCardEmbeddableFactory((() => null) as any, {} as any) - ); - const start = doStart(); + const embeddableFactory = new ContactCardEmbeddableFactory((() => null) as any, {} as any); + pluginServices.getServices().embeddable.getEmbeddableFactory = jest + .fn() + .mockReturnValue(embeddableFactory); - const getEmbeddableFactory = start.getEmbeddableFactory; const initialInput = getSampleDashboardInput({ panels: { '1': { @@ -51,40 +45,14 @@ function prepare(props?: Partial) { }, }, }); - const options: DashboardContainerServices = { - application: {} as any, - embeddable: { - getTriggerCompatibleActions: (() => []) as any, - getEmbeddableFactories: start.getEmbeddableFactories, - getEmbeddablePanel: jest.fn(), - getEmbeddableFactory, - } as any, - notifications: {} as any, - overlays: {} as any, - inspector: { - isAvailable: jest.fn(), - } as any, - SavedObjectFinder: () => null, - ExitFullScreenButton: () => null, - uiActions: { - getTriggerCompatibleActions: (() => []) as any, - } as any, - uiSettings: uiSettingsServiceMock.createStartContract(), - http: coreMock.createStart().http, - theme: coreMock.createStart().theme, - presentationUtil, - screenshotMode: screenshotModePluginMock.createSetupContract(), - }; - dashboardContainer = new DashboardContainer(initialInput, options); + dashboardContainer = new DashboardContainer(initialInput); const defaultTestProps: DashboardGridProps = { container: dashboardContainer, - kibana: null as any, intl: null as any, }; return { props: Object.assign(defaultTestProps, props), - options, }; } @@ -100,13 +68,12 @@ afterAll(() => { // unhandled promise rejection: https://github.com/elastic/kibana/issues/112699 test.skip('renders DashboardGrid', () => { - const { props, options } = prepare(); + const { props } = prepare(); + const component = mountWithIntl( - - - - - + + + ); const panelElements = component.find('EmbeddableChildPanel'); expect(panelElements.length).toBe(2); @@ -114,13 +81,11 @@ test.skip('renders DashboardGrid', () => { // unhandled promise rejection: https://github.com/elastic/kibana/issues/112699 test.skip('renders DashboardGrid with no visualizations', () => { - const { props, options } = prepare(); + const { props } = prepare(); const component = mountWithIntl( - - - - - + + + ); props.container.updateInput({ panels: {} }); @@ -130,13 +95,11 @@ test.skip('renders DashboardGrid with no visualizations', () => { // unhandled promise rejection: https://github.com/elastic/kibana/issues/112699 test.skip('DashboardGrid removes panel when removed from container', () => { - const { props, options } = prepare(); + const { props } = prepare(); const component = mountWithIntl( - - - - - + + + ); const originalPanels = props.container.getInput().panels; @@ -150,13 +113,11 @@ test.skip('DashboardGrid removes panel when removed from container', () => { // unhandled promise rejection: https://github.com/elastic/kibana/issues/112699 test.skip('DashboardGrid renders expanded panel', () => { - const { props, options } = prepare(); + const { props } = prepare(); const component = mountWithIntl( - - - - - + + + ); props.container.updateInput({ expandedPanelId: '1' }); @@ -179,13 +140,11 @@ test.skip('DashboardGrid renders expanded panel', () => { // unhandled promise rejection: https://github.com/elastic/kibana/issues/112699 test.skip('DashboardGrid unmount unsubscribes', async (done) => { - const { props, options } = prepare(); + const { props } = prepare(); const component = mountWithIntl( - - - - - + + + ); component.unmount(); diff --git a/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx index 250bd46f07199..64afdcdb2e609 100644 --- a/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx +++ b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx @@ -6,27 +6,25 @@ * Side Public License, v 1. */ -import 'react-grid-layout/css/styles.css'; -import 'react-resizable/css/styles.css'; - -import sizeMe from 'react-sizeme'; -import { injectI18n } from '@kbn/i18n-react'; -import classNames from 'classnames'; import _ from 'lodash'; import React from 'react'; +import sizeMe from 'react-sizeme'; +import classNames from 'classnames'; import { Subscription } from 'rxjs'; +import 'react-resizable/css/styles.css'; +import 'react-grid-layout/css/styles.css'; import ReactGridLayout, { Layout, ReactGridLayoutProps } from 'react-grid-layout'; + +import { injectI18n } from '@kbn/i18n-react'; +import { ViewMode, EmbeddablePhaseEvent } from '@kbn/embeddable-plugin/public'; + +import { DashboardContainer, DashboardLoadedInfo } from '../dashboard_container'; import { GridData } from '../../../../common'; -import { ViewMode, EmbeddablePhaseEvent } from '../../../services/embeddable'; -import { DASHBOARD_GRID_COLUMN_COUNT, DASHBOARD_GRID_HEIGHT } from '../dashboard_constants'; -import { DashboardLoadedEventStatus, DashboardPanelState } from '../types'; -import { withKibana } from '../../../services/kibana_react'; -import { - DashboardContainer, - DashboardReactContextValue, - DashboardLoadedInfo, -} from '../dashboard_container'; import { DashboardGridItem } from './dashboard_grid_item'; +import { DashboardLoadedEventStatus, DashboardPanelState } from '../types'; +import { DASHBOARD_GRID_COLUMN_COUNT, DASHBOARD_GRID_HEIGHT } from '../dashboard_constants'; +import { pluginServices } from '../../../services/plugin_services'; +import { dashboardLoadingErrorStrings } from '../../../dashboard_strings'; let lastValidGridSize = 0; @@ -105,7 +103,6 @@ const config = { monitorWidth: true }; const ResponsiveSizedGrid = sizeMe(config)(ResponsiveGrid); export interface DashboardGridProps extends ReactIntl.InjectedIntlProps { - kibana: DashboardReactContextValue; container: DashboardContainer; onDataLoaded?: (data: DashboardLoadedInfo) => void; } @@ -146,20 +143,17 @@ class DashboardGridUi extends React.Component { this.mounted = true; let isLayoutInvalid = false; let layout; + + const { + notifications: { toasts }, + } = pluginServices.getServices(); + try { layout = this.buildLayoutFromPanels(); } catch (error) { console.error(error); // eslint-disable-line no-console - isLayoutInvalid = true; - this.props.kibana.notifications.toasts.danger({ - title: this.props.intl.formatMessage({ - id: 'dashboard.dashboardGrid.toast.unableToLoadDashboardDangerMessage', - defaultMessage: 'Unable to load dashboard.', - }), - body: (error as { message: string }).message, - toastLifeTimeMs: 5000, - }); + toasts.addDanger(dashboardLoadingErrorStrings.getDashboardGridError(error.message)); } this.setState({ layout, @@ -228,7 +222,7 @@ class DashboardGridUi extends React.Component { return null; } - const { container, kibana } = this.props; + const { container } = this.props; const { focusedPanelIndex, panels, expandedPanelId, viewMode } = this.state; const isViewMode = viewMode === ViewMode.VIEW; @@ -292,7 +286,6 @@ class DashboardGridUi extends React.Component { index={index + 1} type={type} container={container} - PanelComponent={kibana.services.embeddable.EmbeddablePanel} expandedPanelId={expandedPanelId} focusedPanelId={focusedPanelIndex} onPanelStatusChange={onPanelStatusChange} @@ -318,4 +311,4 @@ class DashboardGridUi extends React.Component { } } -export const DashboardGrid = injectI18n(withKibana(DashboardGridUi)); +export const DashboardGrid = injectI18n(DashboardGridUi); diff --git a/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid_item.tsx b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid_item.tsx index d898093fa5267..8b12d1f574fb7 100644 --- a/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid_item.tsx +++ b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid_item.tsx @@ -10,12 +10,17 @@ import React, { useState, useRef, useEffect, FC } from 'react'; import { EuiLoadingChart } from '@elastic/eui'; import classNames from 'classnames'; -import { EmbeddableChildPanel, EmbeddablePhaseEvent, ViewMode } from '../../../services/embeddable'; -import { useLabs } from '../../../services/presentation_util'; +import { + EmbeddableChildPanel, + EmbeddablePhaseEvent, + ViewMode, +} from '@kbn/embeddable-plugin/public'; + import { DashboardPanelState } from '../types'; import { DashboardContainer } from '..'; +import { pluginServices } from '../../../services/plugin_services'; -type PanelProps = Pick; +type PanelProps = Pick; type DivProps = Pick, 'className' | 'style' | 'children'>; interface Props extends PanelProps, DivProps { @@ -38,7 +43,6 @@ const Item = React.forwardRef( focusedPanelId, id, index, - PanelComponent, type, onPanelStatusChange, isRenderable = true, @@ -51,6 +55,10 @@ const Item = React.forwardRef( }, ref ) => { + const { + embeddable: { EmbeddablePanel: PanelComponent }, + } = pluginServices.getServices(); + const expandPanel = expandedPanelId !== undefined && expandedPanelId === id; const hidePanel = expandedPanelId !== undefined && expandedPanelId !== id; const classes = classNames({ @@ -123,9 +131,12 @@ export const ObservedItem: FC = (props: Props) => { }; export const DashboardGridItem: FC = (props: Props) => { - const { isProjectEnabled } = useLabs(); + const { + settings: { isProjectEnabledInLabs }, + } = pluginServices.getServices(); + const isPrintMode = props.container.getInput().viewMode === ViewMode.PRINT; - const isEnabled = !isPrintMode && isProjectEnabled('labs:dashboard:deferBelowFold'); + const isEnabled = !isPrintMode && isProjectEnabledInLabs('labs:dashboard:deferBelowFold'); return isEnabled ? : ; }; diff --git a/src/plugins/dashboard/public/application/embeddable/panel/create_panel_state.test.ts b/src/plugins/dashboard/public/application/embeddable/panel/create_panel_state.test.ts index a194c1d2a9d1a..10c3044ea912a 100644 --- a/src/plugins/dashboard/public/application/embeddable/panel/create_panel_state.test.ts +++ b/src/plugins/dashboard/public/application/embeddable/panel/create_panel_state.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { EmbeddableInput } from '../../../services/embeddable'; +import { EmbeddableInput } from '@kbn/embeddable-plugin/public'; import { CONTACT_CARD_EMBEDDABLE } from '@kbn/embeddable-plugin/public/lib/test_samples'; import { DEFAULT_PANEL_HEIGHT, DEFAULT_PANEL_WIDTH } from '../dashboard_constants'; import { DashboardPanelState } from '../types'; diff --git a/src/plugins/dashboard/public/application/embeddable/panel/create_panel_state.ts b/src/plugins/dashboard/public/application/embeddable/panel/create_panel_state.ts index 46a75ca06bdb5..5aa9066ea1eba 100644 --- a/src/plugins/dashboard/public/application/embeddable/panel/create_panel_state.ts +++ b/src/plugins/dashboard/public/application/embeddable/panel/create_panel_state.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { PanelState, EmbeddableInput } from '../../../services/embeddable'; +import { PanelState, EmbeddableInput } from '@kbn/embeddable-plugin/public'; import { DEFAULT_PANEL_HEIGHT, DEFAULT_PANEL_WIDTH } from '../dashboard_constants'; import { DashboardPanelState } from '../types'; import { diff --git a/src/plugins/dashboard/public/application/embeddable/panel/dashboard_panel_placement.ts b/src/plugins/dashboard/public/application/embeddable/panel/dashboard_panel_placement.ts index 64b398200db1d..9d90b711a6843 100644 --- a/src/plugins/dashboard/public/application/embeddable/panel/dashboard_panel_placement.ts +++ b/src/plugins/dashboard/public/application/embeddable/panel/dashboard_panel_placement.ts @@ -7,7 +7,7 @@ */ import _ from 'lodash'; -import { PanelNotFoundError } from '../../../services/embeddable'; +import { PanelNotFoundError } from '@kbn/embeddable-plugin/public'; import { GridData } from '../../../../common'; import { DashboardPanelState, DASHBOARD_GRID_COLUMN_COUNT } from '..'; diff --git a/src/plugins/dashboard/public/application/embeddable/placeholder/placeholder_embeddable.tsx b/src/plugins/dashboard/public/application/embeddable/placeholder/placeholder_embeddable.tsx index 9bdb8de1c42c0..a7fa1e793ebf0 100644 --- a/src/plugins/dashboard/public/application/embeddable/placeholder/placeholder_embeddable.tsx +++ b/src/plugins/dashboard/public/application/embeddable/placeholder/placeholder_embeddable.tsx @@ -8,27 +8,20 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import { EuiLoadingChart } from '@elastic/eui'; import classNames from 'classnames'; -import { CoreStart } from '@kbn/core/public'; -import { Embeddable, EmbeddableInput, IContainer } from '../../../services/embeddable'; -import { KibanaThemeProvider } from '../../../services/kibana_react'; -export const PLACEHOLDER_EMBEDDABLE = 'placeholder'; +import { EuiLoadingChart } from '@elastic/eui'; +import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; +import { Embeddable, type EmbeddableInput, type IContainer } from '@kbn/embeddable-plugin/public'; +import { pluginServices } from '../../../services/plugin_services'; -export interface PlaceholderEmbeddableServices { - theme: CoreStart['theme']; -} +export const PLACEHOLDER_EMBEDDABLE = 'placeholder'; export class PlaceholderEmbeddable extends Embeddable { public readonly type = PLACEHOLDER_EMBEDDABLE; private node?: HTMLElement; - constructor( - initialInput: EmbeddableInput, - private readonly services: PlaceholderEmbeddableServices, - parent?: IContainer - ) { + constructor(initialInput: EmbeddableInput, parent?: IContainer) { super(initialInput, {}, parent); this.input = initialInput; } @@ -38,9 +31,15 @@ export class PlaceholderEmbeddable extends Embeddable { } this.node = node; + const { + settings: { + theme: { theme$ }, + }, + } = pluginServices.getServices(); + const classes = classNames('embPanel', 'embPanel-isLoading'); ReactDOM.render( - +
diff --git a/src/plugins/dashboard/public/application/embeddable/placeholder/placeholder_embeddable_factory.ts b/src/plugins/dashboard/public/application/embeddable/placeholder/placeholder_embeddable_factory.ts index b0dce72ad77e3..74ce8bf96edbd 100644 --- a/src/plugins/dashboard/public/application/embeddable/placeholder/placeholder_embeddable_factory.ts +++ b/src/plugins/dashboard/public/application/embeddable/placeholder/placeholder_embeddable_factory.ts @@ -12,17 +12,13 @@ import { EmbeddableFactoryDefinition, EmbeddableInput, IContainer, -} from '../../../services/embeddable'; -import { - PlaceholderEmbeddable, - PlaceholderEmbeddableServices, - PLACEHOLDER_EMBEDDABLE, -} from './placeholder_embeddable'; +} from '@kbn/embeddable-plugin/public'; +import { PlaceholderEmbeddable, PLACEHOLDER_EMBEDDABLE } from './placeholder_embeddable'; export class PlaceholderEmbeddableFactory implements EmbeddableFactoryDefinition { public readonly type = PLACEHOLDER_EMBEDDABLE; - constructor(private readonly getStartServices: () => Promise) {} + constructor() {} public async isEditable() { return false; @@ -33,8 +29,7 @@ export class PlaceholderEmbeddableFactory implements EmbeddableFactoryDefinition } public async create(initialInput: EmbeddableInput, parent?: IContainer) { - const services = await this.getStartServices(); - return new PlaceholderEmbeddable(initialInput, services, parent); + return new PlaceholderEmbeddable(initialInput, parent); } public getDisplayName() { diff --git a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx index 0fa1d4beba497..27c86a8ff6c09 100644 --- a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx +++ b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx @@ -10,61 +10,29 @@ import { findTestSubject } from '@elastic/eui/lib/test'; import React from 'react'; import { skip } from 'rxjs/operators'; import { mount } from 'enzyme'; + import { I18nProvider } from '@kbn/i18n-react'; import { nextTick } from '@kbn/test-jest-helpers'; -import { DashboardViewport, DashboardViewportProps } from './dashboard_viewport'; -import { DashboardContainer, DashboardContainerServices } from '../dashboard_container'; -import { getSampleDashboardInput } from '../../test_helpers'; -import { KibanaContextProvider } from '../../../services/kibana_react'; -import { embeddablePluginMock } from '@kbn/embeddable-plugin/public/mocks'; -import { applicationServiceMock, coreMock, uiSettingsServiceMock } from '@kbn/core/public/mocks'; import { ContactCardEmbeddableFactory, CONTACT_CARD_EMBEDDABLE, } from '@kbn/embeddable-plugin/public/lib/test_samples'; -import { getStubPluginServices } from '@kbn/presentation-util-plugin/public'; -import { screenshotModePluginMock } from '@kbn/screenshot-mode-plugin/public/mocks'; -let dashboardContainer: DashboardContainer | undefined; -const presentationUtil = getStubPluginServices(); +import { DashboardViewport, DashboardViewportProps } from './dashboard_viewport'; +import { DashboardContainer } from '../dashboard_container'; +import { getSampleDashboardInput } from '../../test_helpers'; +import { pluginServices } from '../../../services/plugin_services'; -const ExitFullScreenButton = () =>
EXIT
; +let dashboardContainer: DashboardContainer | undefined; +const DashboardServicesProvider = pluginServices.getContextProvider(); function getProps(props?: Partial): { props: DashboardViewportProps; - options: DashboardContainerServices; } { - const { setup, doStart } = embeddablePluginMock.createInstance(); - setup.registerEmbeddableFactory( - CONTACT_CARD_EMBEDDABLE, - new ContactCardEmbeddableFactory((() => null) as any, {} as any) - ); - - const start = doStart(); - const options: DashboardContainerServices = { - application: applicationServiceMock.createStartContract(), - uiSettings: uiSettingsServiceMock.createStartContract(), - http: coreMock.createStart().http, - theme: coreMock.createStart().theme, - embeddable: { - getTriggerCompatibleActions: (() => []) as any, - getEmbeddablePanel: jest.fn(), - getEmbeddableFactories: start.getEmbeddableFactories, - getEmbeddableFactory: start.getEmbeddableFactory, - } as any, - notifications: {} as any, - overlays: {} as any, - inspector: { - isAvailable: jest.fn(), - } as any, - SavedObjectFinder: () => null, - ExitFullScreenButton, - uiActions: { - getTriggerCompatibleActions: (() => []) as any, - } as any, - presentationUtil, - screenshotMode: screenshotModePluginMock.createSetupContract(), - }; + const embeddableFactory = new ContactCardEmbeddableFactory((() => null) as any, {} as any); + pluginServices.getServices().embeddable.getEmbeddableFactory = jest + .fn() + .mockReturnValue(embeddableFactory); const input = getSampleDashboardInput({ panels: { @@ -81,26 +49,23 @@ function getProps(props?: Partial): { }, }); - dashboardContainer = new DashboardContainer(input, options); + dashboardContainer = new DashboardContainer(input); const defaultTestProps: DashboardViewportProps = { container: dashboardContainer, }; return { props: Object.assign(defaultTestProps, props), - options, }; } // unhandled promise rejection: https://github.com/elastic/kibana/issues/112699 test.skip('renders DashboardViewport', () => { - const { props, options } = getProps(); + const { props } = getProps(); const component = mount( - - - - - + + + ); const panels = findTestSubject(component, 'dashboardPanel'); @@ -109,15 +74,13 @@ test.skip('renders DashboardViewport', () => { // unhandled promise rejection: https://github.com/elastic/kibana/issues/112699 test.skip('renders DashboardViewport with no visualizations', () => { - const { props, options } = getProps(); + const { props } = getProps(); props.container.updateInput({ panels: {} }); const component = mount( - - - - - + + + ); const panels = findTestSubject(component, 'dashboardPanel'); @@ -128,15 +91,13 @@ test.skip('renders DashboardViewport with no visualizations', () => { // unhandled promise rejection: https://github.com/elastic/kibana/issues/112699 test.skip('renders DashboardEmptyScreen', () => { - const { props, options } = getProps(); + const { props } = getProps(); props.container.updateInput({ panels: {} }); const component = mount( - - - - - + + + ); const dashboardEmptyScreenDiv = component.find('.dshDashboardEmptyScreen'); @@ -147,15 +108,13 @@ test.skip('renders DashboardEmptyScreen', () => { // unhandled promise rejection: https://github.com/elastic/kibana/issues/112699 test.skip('renders exit full screen button when in full screen mode', async () => { - const { props, options } = getProps(); + const { props } = getProps(); props.container.updateInput({ isFullScreenMode: true }); const component = mount( - - - - - + + + ); @@ -176,15 +135,13 @@ test.skip('renders exit full screen button when in full screen mode', async () = // unhandled promise rejection: https://github.com/elastic/kibana/issues/112699 test.skip('renders exit full screen button when in full screen mode and empty screen', async () => { - const { props, options } = getProps(); + const { props } = getProps(); props.container.updateInput({ panels: {}, isFullScreenMode: true }); const component = mount( - - - - - + + + ); expect((component.find('.dshDashboardViewport').childAt(0).type() as any).name).toBe( @@ -204,14 +161,12 @@ test.skip('renders exit full screen button when in full screen mode and empty sc // unhandled promise rejection: https://github.com/elastic/kibana/issues/112699 test.skip('DashboardViewport unmount unsubscribes', async (done) => { - const { props, options } = getProps(); + const { props } = getProps(); const component = mount( - - - - - + + + ); component.unmount(); diff --git a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx index ebb91ba57fd46..52e76295eb998 100644 --- a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx +++ b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx @@ -8,26 +8,26 @@ import React from 'react'; import { Subscription } from 'rxjs'; + import { CalloutProps, ControlGroupContainer, LazyControlsCallout, } from '@kbn/controls-plugin/public'; -import { ViewMode } from '../../../services/embeddable'; -import { - DashboardContainer, - DashboardReactContextValue, - DashboardLoadedInfo, -} from '../dashboard_container'; +import { ViewMode } from '@kbn/embeddable-plugin/public'; +import { withSuspense } from '@kbn/presentation-util-plugin/public'; +import { context } from '@kbn/kibana-react-plugin/public'; +import { ExitFullScreenButton as ExitFullScreenButtonUi } from '@kbn/kibana-react-plugin/public'; +import { CoreStart } from '@kbn/core/public'; + +import { DashboardContainer, DashboardLoadedInfo } from '../dashboard_container'; import { DashboardGrid } from '../grid'; -import { context } from '../../../services/kibana_react'; import { DashboardEmptyScreen } from '../empty_screen/dashboard_empty_screen'; -import { withSuspense } from '../../../services/presentation_util'; +import { pluginServices } from '../../../services/plugin_services'; export interface DashboardViewportProps { container: DashboardContainer; controlGroup?: ControlGroupContainer; - controlsEnabled?: boolean; onDataLoaded?: (data: DashboardLoadedInfo) => void; } @@ -45,8 +45,6 @@ const ControlsCallout = withSuspense(LazyControlsCallout); export class DashboardViewport extends React.Component { static contextType = context; - public declare readonly context: DashboardReactContextValue; - private controlsRoot: React.RefObject; private subscription?: Subscription; @@ -106,11 +104,18 @@ export class DashboardViewport extends React.Component @@ -147,21 +152,16 @@ export class DashboardViewport extends React.Component {isFullScreenMode && ( - )} {this.props.container.getPanelCount() === 0 && (
- +
)} {this.state.controlGroupReady && ( diff --git a/src/plugins/dashboard/public/application/hooks/dashboard_mount_context.ts b/src/plugins/dashboard/public/application/hooks/dashboard_mount_context.ts new file mode 100644 index 0000000000000..967fbf67e4566 --- /dev/null +++ b/src/plugins/dashboard/public/application/hooks/dashboard_mount_context.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ScopedHistory } from '@kbn/core-application-browser'; +import { createContext, useContext } from 'react'; +import { DashboardMountContextProps } from '../../types'; + +export const DashboardMountContext = createContext({ + // default values for the dashboard mount context + restorePreviousUrl: () => {}, + scopedHistory: () => ({} as ScopedHistory), + onAppLeave: (handler) => {}, + setHeaderActionMenu: (mountPoint) => {}, +}); + +export const useDashboardMountContext = () => { + return useContext(DashboardMountContext); +}; diff --git a/src/plugins/dashboard/public/application/hooks/use_dashboard_app_state.test.tsx b/src/plugins/dashboard/public/application/hooks/use_dashboard_app_state.test.tsx index e30b26532b518..c462df50ef27f 100644 --- a/src/plugins/dashboard/public/application/hooks/use_dashboard_app_state.test.tsx +++ b/src/plugins/dashboard/public/application/hooks/use_dashboard_app_state.test.tsx @@ -7,31 +7,29 @@ */ import React from 'react'; -import { of } from 'rxjs'; import { Provider } from 'react-redux'; import { createBrowserHistory } from 'history'; import { renderHook, act, RenderHookResult } from '@testing-library/react-hooks'; +import { createKbnUrlStateStorage, defer } from '@kbn/kibana-utils-plugin/public'; +import { DataView } from '@kbn/data-views-plugin/public'; -import { DashboardSessionStorage } from '../lib'; -import { coreMock } from '@kbn/core/public/mocks'; import { DashboardConstants } from '../../dashboard_constants'; -import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; -import { SavedObjectLoader } from '../../services/saved_objects'; +import { SavedObjectLoader } from '../../services/saved_object_loader'; import { DashboardAppServices, DashboardAppState } from '../../types'; import { DashboardContainer } from '../embeddable/dashboard_container'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; -import { EmbeddableFactory, ViewMode } from '../../services/embeddable'; import { dashboardStateStore, setDescription, setViewMode } from '../state'; -import { DashboardContainerServices } from '../embeddable/dashboard_container'; -import { createKbnUrlStateStorage, defer } from '@kbn/kibana-utils-plugin/public'; import { useDashboardAppState, UseDashboardStateProps } from './use_dashboard_app_state'; import { getSampleDashboardInput, getSavedDashboardMock, makeDefaultServices, } from '../test_helpers'; -import { DataView } from '../../services/data_views'; + import type { Filter } from '@kbn/es-query'; +import { pluginServices } from '../../services/plugin_services'; +import { EmbeddableFactory, ViewMode } from '@kbn/embeddable-plugin/public'; +import { DashboardServices } from '../../services/types'; interface SetupEmbeddableFactoryReturn { finalizeEmbeddableCreation: () => void; @@ -59,55 +57,29 @@ const createDashboardAppStateProps = (): UseDashboardStateProps => ({ const createDashboardAppStateServices = () => { const defaults = makeDefaultServices(); + const defaultDataView = { id: 'foo', fields: [{ name: 'bar' }] } as DataView; - defaults.dataViews.getDefaultDataView = jest - .fn() - .mockImplementation(() => Promise.resolve(defaultDataView)); - defaults.dataViews.getDefaultId = jest - .fn() - .mockImplementation(() => Promise.resolve(defaultDataView.id)); - - defaults.dataViews.getDefault = jest - .fn() - .mockImplementation(() => Promise.resolve(defaultDataView)); - - const data = dataPluginMock.createStartContract(); - data.query.filterManager.getUpdates$ = jest.fn().mockImplementation(() => of(void 0)); - data.query.filterManager.getFilters = jest.fn().mockImplementation(() => []); - data.query.queryString.getUpdates$ = jest.fn().mockImplementation(() => of({})); - data.query.timefilter.timefilter.getTimeUpdate$ = jest.fn().mockImplementation(() => of(void 0)); - data.query.timefilter.timefilter.getRefreshIntervalUpdate$ = jest - .fn() - .mockImplementation(() => of(void 0)); - - return { ...defaults, data }; + + (pluginServices.getServices().data.dataViews.getDefaultDataView as jest.Mock).mockResolvedValue( + defaultDataView + ); + (pluginServices.getServices().data.dataViews.getDefaultId as jest.Mock).mockResolvedValue( + defaultDataView.id + ); + (pluginServices.getServices().data.query.filterManager.getFilters as jest.Mock).mockReturnValue( + [] + ); + + return defaults; }; const setupEmbeddableFactory = ( services: DashboardAppServices, id: string ): SetupEmbeddableFactoryReturn => { - const coreStart = coreMock.createStart(); - const containerOptions = { - notifications: services.core.notifications, - savedObjectMetaData: {} as unknown, - ExitFullScreenButton: () => null, - embeddable: services.embeddable, - uiSettings: services.uiSettings, - SavedObjectFinder: () => null, - overlays: coreStart.overlays, - application: {} as unknown, - inspector: {} as unknown, - uiActions: {} as unknown, - http: coreStart.http, - } as unknown as DashboardContainerServices; - - const dashboardContainer = new DashboardContainer( - { ...getSampleDashboardInput(), id }, - containerOptions - ); + const dashboardContainer = new DashboardContainer({ ...getSampleDashboardInput(), id }); const deferEmbeddableCreate = defer(); - services.embeddable.getEmbeddableFactory = jest.fn().mockImplementation( + pluginServices.getServices().embeddable.getEmbeddableFactory = jest.fn().mockImplementation( () => ({ create: () => deferEmbeddableCreate.promise, @@ -136,15 +108,23 @@ const renderDashboardAppStateHook = ({ const props = { ...createDashboardAppStateProps(), ...(partialProps ?? {}) }; const services = { ...createDashboardAppStateServices(), ...(partialServices ?? {}) }; const embeddableFactoryResult = setupEmbeddableFactory(services, originalDashboardEmbeddableId); + const DashboardServicesProvider = pluginServices.getContextProvider(); + const renderHookResult = renderHook( - (replaceProps: Partial) => - useDashboardAppState({ ...props, ...replaceProps }), + (replaceProps: Partial) => { + return useDashboardAppState({ ...props, ...replaceProps }); + }, { - wrapper: ({ children }) => ( - - {children} - - ), + wrapper: ({ children }) => { + return ( + + {/* Can't get rid of KibanaContextProvider here yet because of saved dashboard tests below */} + + {children} + + + ); + }, } ); return { embeddableFactoryResult, renderHookResult, services, props }; @@ -153,6 +133,7 @@ const renderDashboardAppStateHook = ({ describe('Dashboard container lifecycle', () => { test('Dashboard container is destroyed on unmount', async () => { const { renderHookResult, embeddableFactoryResult } = renderDashboardAppStateHook({}); + embeddableFactoryResult.finalizeEmbeddableCreation(); await renderHookResult.waitForNextUpdate(); @@ -256,26 +237,28 @@ describe.skip('Dashboard initial state', () => { await renderHookResult.waitForNextUpdate(); expect(getResult().getLatestDashboardState?.().timeRestore).toEqual(true); - expect(services.data.query.timefilter.timefilter.setTime).toHaveBeenCalledWith({ + expect( + (services as DashboardAppServices & { data: DashboardServices['data'] }).data.query.timefilter + .timefilter.setTime + ).toHaveBeenCalledWith({ from: 'now-13d', to: 'now', }); - expect(services.data.query.filterManager.setAppFilters).toHaveBeenCalledWith([ - { meta: { test: 'filterMeTimbers' } } as unknown as Filter, - ]); + expect( + (services as DashboardAppServices & { data: DashboardServices['data'] }).data.query + .filterManager.setAppFilters + ).toHaveBeenCalledWith([{ meta: { test: 'filterMeTimbers' } } as unknown as Filter]); }); it('Combines session state and URL state into initial state', async () => { - const dashboardSessionStorage = { - getState: jest - .fn() - .mockReturnValue({ viewMode: ViewMode.EDIT, description: 'this should be overwritten' }), - } as unknown as DashboardSessionStorage; + pluginServices.getServices().dashboardSessionStorage.getState = jest + .fn() + .mockReturnValue({ viewMode: ViewMode.EDIT, description: 'this should be overwritten' }); + const kbnUrlStateStorage = createKbnUrlStateStorage(); kbnUrlStateStorage.set('_a', { description: 'with this' }); const { renderHookResult, embeddableFactoryResult } = renderDashboardAppStateHook({ partialProps: { kbnUrlStateStorage }, - partialServices: { dashboardSessionStorage }, }); const getResult = () => renderHookResult.result.current; @@ -326,7 +309,6 @@ describe.skip('Dashboard state sync', () => { }); it('pushes unsaved changes to the session storage', async () => { - const { services } = defaultDashboardAppStateHookResult; expect(getResult().getLatestDashboardState?.().fullScreenMode).toBe(false); act(() => { dashboardStateStore.dispatch(setViewMode(ViewMode.EDIT)); // session storage is only populated in edit mode @@ -335,7 +317,7 @@ describe.skip('Dashboard state sync', () => { await act(async () => { await new Promise((resolve) => setTimeout(resolve, 3)); }); - expect(services.dashboardSessionStorage.setState).toHaveBeenCalledWith( + expect(pluginServices.getServices().dashboardSessionStorage.setState).toHaveBeenCalledWith( 'testDashboardId', expect.objectContaining({ description: 'Wow an even cooler description.', diff --git a/src/plugins/dashboard/public/application/hooks/use_dashboard_app_state.ts b/src/plugins/dashboard/public/application/hooks/use_dashboard_app_state.ts index 30bb15c6ecd49..932bfdd016b38 100644 --- a/src/plugins/dashboard/public/application/hooks/use_dashboard_app_state.ts +++ b/src/plugins/dashboard/public/application/hooks/use_dashboard_app_state.ts @@ -11,13 +11,14 @@ import { debounceTime, switchMap } from 'rxjs/operators'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { BehaviorSubject, combineLatest, Observable, Subject } from 'rxjs'; +import { ViewMode } from '@kbn/embeddable-plugin/public'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import type { IKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public'; + import { DashboardConstants } from '../..'; -import { ViewMode } from '../../services/embeddable'; -import { useKibana } from '../../services/kibana_react'; import { getNewDashboardTitle } from '../../dashboard_strings'; -import { IKbnUrlStateStorage } from '../../services/kibana_utils'; import { setDashboardState, useDashboardDispatch, useDashboardSelector } from '../state'; -import { +import type { DashboardBuildContext, DashboardAppServices, DashboardAppState, @@ -39,6 +40,8 @@ import { areRefreshIntervalsEqual, } from '../lib'; import { isDashboardAppInNoDataState } from '../dashboard_app_no_data'; +import { pluginServices } from '../../services/plugin_services'; +import { useDashboardMountContext } from './dashboard_mount_context'; export interface UseDashboardStateProps { history: History; @@ -76,31 +79,26 @@ export const useDashboardAppState = ({ const [lastSavedState, setLastSavedState] = useState(); const $onLastSavedStateChange = useMemo(() => new Subject(), []); + const { + services: { savedDashboards }, + } = useKibana(); + /** - * Unpack services + * Unpack services and context */ - const services = useKibana().services; + const { scopedHistory } = useDashboardMountContext(); const { - data, - core, - chrome, - embeddable, - dataViews, - usageCollection, - savedDashboards, - initializerContext, - savedObjectsTagging, + chrome: { docTitle }, dashboardCapabilities, dashboardSessionStorage, - scopedHistory, - spacesService, - screenshotModeService, - } = services; - const { docTitle } = chrome; - const { notifications } = core; - const { query, search } = data; + data: { query, search, dataViews }, + embeddable, + initializerContext: { kibanaVersion }, + screenshotMode: { isScreenshotMode, getScreenshotContext }, + spaces: { redirectLegacyUrl }, + notifications, + } = pluginServices.getServices(); const { getStateTransfer } = embeddable; - const { version: kibanaVersion } = initializerContext.env.packageInfo; /** * This useEffect triggers when the dashboard ID changes, and is in charge of loading the saved dashboard, @@ -121,19 +119,10 @@ export const useDashboardAppState = ({ * from the dashboardId. This build context doesn't contain any extrenuous services. */ const dashboardBuildContext: DashboardBuildContext = { - query, - search, history, - embeddable, - dataViews, - notifications, - kibanaVersion, savedDashboards, kbnUrlStateStorage, - initializerContext, - savedObjectsTagging, isEmbeddedExternally, - dashboardCapabilities, dispatchDashboardStateChange, $checkForUnsavedChanges: new Subject(), $onDashboardStateChange: dashboardAppState.$onDashboardStateChange, @@ -145,7 +134,7 @@ export const useDashboardAppState = ({ /** * Ensure default data view exists and there is data in elasticsearch */ - const isEmpty = await isDashboardAppInNoDataState(dataViews); + const isEmpty = await isDashboardAppInNoDataState(); if (showNoDataPage || isEmpty) { setShowNoDataPage(true); return; @@ -178,10 +167,10 @@ export const useDashboardAppState = ({ savedDashboard.aliasId ); const aliasPurpose = savedDashboard.aliasPurpose; - if (screenshotModeService?.isScreenshotMode()) { + if (isScreenshotMode()) { scopedHistory().replace(path); } else { - await spacesService?.ui.redirectLegacyUrl({ path, aliasPurpose }); + await redirectLegacyUrl?.({ path, aliasPurpose }); } // Return so we don't run any more of the hook and let it rerun after the redirect that just happened return; @@ -201,9 +190,7 @@ export const useDashboardAppState = ({ savedDashboard, }); - const printLayoutDetected = - screenshotModeService?.isScreenshotMode() && - screenshotModeService.getScreenshotContext('layout') === 'print'; + const printLayoutDetected = isScreenshotMode() && getScreenshotContext('layout') === 'print'; const initialDashboardState = { ...savedDashboardState, @@ -236,7 +223,6 @@ export const useDashboardAppState = ({ initialDashboardState, incomingEmbeddable, savedDashboard, - data, executionContext: { type: 'dashboard', description: savedDashboard.title, @@ -253,7 +239,6 @@ export const useDashboardAppState = ({ */ const dataViewsSubscription = syncDashboardDataViews({ dashboardContainer, - dataViews: dashboardBuildContext.dataViews, onUpdateDataViews: async (newDataViewIds: string[]) => { if (newDataViewIds?.[0]) { dashboardContainer.controlGroup?.setRelevantDataViewId(newDataViewIds[0]); @@ -279,7 +264,7 @@ export const useDashboardAppState = ({ * Any time the redux state, or the last saved state changes, compare them, set the unsaved * changes state, and and push the unsaved changes to session storage. */ - const { timefilter } = dashboardBuildContext.query.timefilter; + const { timefilter } = query.timefilter; const lastSavedSubscription = combineLatest([ $onLastSavedStateChange, dashboardAppState.$onDashboardStateChange, @@ -336,10 +321,6 @@ export const useDashboardAppState = ({ const updateLastSavedState = () => { setLastSavedState( savedObjectToDashboardState({ - showWriteControls: dashboardBuildContext.dashboardCapabilities.showWriteControls, - version: dashboardBuildContext.kibanaVersion, - savedObjectsTagging, - usageCollection, savedDashboard, }) ); @@ -384,12 +365,9 @@ export const useDashboardAppState = ({ dashboardCapabilities, isEmbeddedExternally, kbnUrlStateStorage, - savedObjectsTagging, - initializerContext, savedDashboardId, getStateTransfer, savedDashboards, - usageCollection, scopedHistory, notifications, dataViews, @@ -399,11 +377,11 @@ export const useDashboardAppState = ({ history, search, query, - data, showNoDataPage, setShowNoDataPage, - spacesService?.ui, - screenshotModeService, + redirectLegacyUrl, + getScreenshotContext, + isScreenshotMode, ]); /** @@ -419,7 +397,7 @@ export const useDashboardAppState = ({ } if (dashboardAppState.getLatestDashboardState().timeRestore) { - const { timefilter } = data.query.timefilter; + const { timefilter } = query.timefilter; const { timeFrom: from, timeTo: to, refreshInterval } = dashboardAppState.savedDashboard; if (from && to) timefilter.setTime({ from, to }); if (refreshInterval) timefilter.setRefreshInterval(refreshInterval); @@ -430,7 +408,7 @@ export const useDashboardAppState = ({ viewMode: ViewMode.VIEW, }) ); - }, [lastSavedState, dashboardAppState, data.query.timefilter, dispatchDashboardStateChange]); + }, [lastSavedState, dashboardAppState, query.timefilter, dispatchDashboardStateChange]); /** * publish state to the state change observable when redux state changes diff --git a/src/plugins/dashboard/public/application/lib/build_dashboard_container.ts b/src/plugins/dashboard/public/application/lib/build_dashboard_container.ts index 8d4193c0923e0..8ad2b7ddc52e2 100644 --- a/src/plugins/dashboard/public/application/lib/build_dashboard_container.ts +++ b/src/plugins/dashboard/public/application/lib/build_dashboard_container.ts @@ -7,19 +7,6 @@ */ import type { KibanaExecutionContext } from '@kbn/core/public'; -import { DashboardSavedObject } from '../../saved_dashboards'; -import { DashboardContainer, DASHBOARD_CONTAINER_TYPE } from '../embeddable'; -import { - DashboardBuildContext, - DashboardState, - DashboardContainerInput, - DashboardAppServices, -} from '../../types'; -import { - enableDashboardSearchSessions, - getSearchSessionIdFromURL, - stateToDashboardContainerInput, -} from '.'; import { ContainerOutput, EmbeddableFactoryNotFoundError, @@ -27,10 +14,19 @@ import { EmbeddablePackageState, ErrorEmbeddable, isErrorEmbeddable, -} from '../../services/embeddable'; +} from '@kbn/embeddable-plugin/public'; + +import { DashboardSavedObject } from '../../saved_dashboards'; +import { DashboardContainer, DASHBOARD_CONTAINER_TYPE } from '../embeddable'; +import { DashboardBuildContext, DashboardState, DashboardContainerInput } from '../../types'; +import { + enableDashboardSearchSessions, + getSearchSessionIdFromURL, + stateToDashboardContainerInput, +} from '.'; +import { pluginServices } from '../../services/plugin_services'; type BuildDashboardContainerProps = DashboardBuildContext & { - data: DashboardAppServices['data']; // the whole data service is required here because it is required by getLocatorParams savedDashboard: DashboardSavedObject; initialDashboardState: DashboardState; incomingEmbeddable?: EmbeddablePackageState; @@ -44,27 +40,25 @@ export const buildDashboardContainer = async ({ getLatestDashboardState, initialDashboardState, isEmbeddedExternally, - dashboardCapabilities, incomingEmbeddable, savedDashboard, - kibanaVersion, - embeddable, history, - data, executionContext, }: BuildDashboardContainerProps) => { const { - search: { session }, - } = data; + dashboardCapabilities: { storeSearchSession: canStoreSearchSession }, + data: { + search: { session }, + }, + embeddable: { getEmbeddableFactory }, + } = pluginServices.getServices(); // set up search session enableDashboardSearchSessions({ - data, - kibanaVersion, savedDashboard, initialDashboardState, getLatestDashboardState, - canStoreSearchSession: dashboardCapabilities.storeSearchSession, + canStoreSearchSession, }); if (incomingEmbeddable?.searchSessionId) { @@ -76,7 +70,7 @@ export const buildDashboardContainer = async ({ session.restore(searchSessionIdFromURL); } - const dashboardFactory = embeddable.getEmbeddableFactory< + const dashboardFactory = getEmbeddableFactory< DashboardContainerInput, ContainerOutput, DashboardContainer @@ -99,9 +93,7 @@ export const buildDashboardContainer = async ({ const initialInput = stateToDashboardContainerInput({ isEmbeddedExternally: Boolean(isEmbeddedExternally), dashboardState: initialDashboardState, - dashboardCapabilities, incomingEmbeddable, - query: data.query, searchSessionId, savedDashboard, executionContext, diff --git a/src/plugins/dashboard/public/application/lib/convert_dashboard_panels.ts b/src/plugins/dashboard/public/application/lib/convert_dashboard_panels.ts index 4ff1278d9467e..8e74245137f8e 100644 --- a/src/plugins/dashboard/public/application/lib/convert_dashboard_panels.ts +++ b/src/plugins/dashboard/public/application/lib/convert_dashboard_panels.ts @@ -10,6 +10,7 @@ import { convertSavedDashboardPanelToPanelState, convertPanelStateToSavedDashboardPanel, } from '../../../common/embeddable/embeddable_saved_object_converters'; +import { pluginServices } from '../../services/plugin_services'; import type { SavedDashboardPanel, DashboardPanelMap } from '../../types'; export const convertSavedPanelsToPanelMap = (panels?: SavedDashboardPanel[]): DashboardPanelMap => { @@ -20,8 +21,12 @@ export const convertSavedPanelsToPanelMap = (panels?: SavedDashboardPanel[]): Da return panelsMap; }; -export const convertPanelMapToSavedPanels = (panels: DashboardPanelMap, version: string) => { +export const convertPanelMapToSavedPanels = (panels: DashboardPanelMap) => { + const { + initializerContext: { kibanaVersion }, + } = pluginServices.getServices(); + return Object.values(panels).map((panel) => - convertPanelStateToSavedDashboardPanel(panel, version) + convertPanelStateToSavedDashboardPanel(panel, kibanaVersion) ); }; diff --git a/src/plugins/dashboard/public/application/lib/convert_dashboard_state.ts b/src/plugins/dashboard/public/application/lib/convert_dashboard_state.ts index 4eff4dc4ef84e..298b93c3a2fdb 100644 --- a/src/plugins/dashboard/public/application/lib/convert_dashboard_state.ts +++ b/src/plugins/dashboard/public/application/lib/convert_dashboard_state.ts @@ -7,8 +7,9 @@ */ import _ from 'lodash'; + import type { KibanaExecutionContext } from '@kbn/core/public'; -import { ControlGroupInput } from '@kbn/controls-plugin/public'; +import type { ControlGroupInput } from '@kbn/controls-plugin/public'; import { compareFilters, isFilterPinned, @@ -16,27 +17,19 @@ import { COMPARE_ALL_OPTIONS, type Filter, } from '@kbn/es-query'; -import { DashboardSavedObject } from '../../saved_dashboards'; +import { type EmbeddablePackageState, ViewMode } from '@kbn/embeddable-plugin/public'; +import type { TimeRange } from '@kbn/es-query'; + +import type { DashboardSavedObject } from '../../saved_dashboards'; import { getTagsFromSavedDashboard, migrateAppState } from '.'; -import { EmbeddablePackageState, ViewMode } from '../../services/embeddable'; -import { TimeRange } from '../../services/data'; import { convertPanelStateToSavedDashboardPanel } from '../../../common/embeddable/embeddable_saved_object_converters'; -import { - DashboardState, - RawDashboardState, - DashboardAppServices, - DashboardContainerInput, - DashboardBuildContext, -} from '../../types'; +import type { DashboardState, RawDashboardState, DashboardContainerInput } from '../../types'; import { convertSavedPanelsToPanelMap } from './convert_dashboard_panels'; import { deserializeControlGroupFromDashboardSavedObject } from './dashboard_control_group'; +import { pluginServices } from '../../services/plugin_services'; interface SavedObjectToDashboardStateProps { - version: string; - showWriteControls: boolean; savedDashboard: DashboardSavedObject; - usageCollection: DashboardAppServices['usageCollection']; - savedObjectsTagging: DashboardAppServices['savedObjectsTagging']; } interface StateToDashboardContainerInputProps { @@ -44,14 +37,11 @@ interface StateToDashboardContainerInputProps { isEmbeddedExternally?: boolean; dashboardState: DashboardState; savedDashboard: DashboardSavedObject; - query: DashboardBuildContext['query']; incomingEmbeddable?: EmbeddablePackageState; - dashboardCapabilities: DashboardBuildContext['dashboardCapabilities']; executionContext?: KibanaExecutionContext; } interface StateToRawDashboardStateProps { - version: string; state: DashboardState; } /** @@ -60,28 +50,25 @@ interface StateToRawDashboardStateProps { * dashboard panel to a panel state. */ export const savedObjectToDashboardState = ({ - version, savedDashboard, - usageCollection, - showWriteControls, - savedObjectsTagging, }: SavedObjectToDashboardStateProps): DashboardState => { - const rawState = migrateAppState( - { - fullScreenMode: false, - title: savedDashboard.title, - query: savedDashboard.getQuery(), - filters: savedDashboard.getFilters(), - timeRestore: savedDashboard.timeRestore, - description: savedDashboard.description || '', - tags: getTagsFromSavedDashboard(savedDashboard, savedObjectsTagging), - panels: savedDashboard.panelsJSON ? JSON.parse(savedDashboard.panelsJSON) : [], - viewMode: savedDashboard.id || showWriteControls ? ViewMode.EDIT : ViewMode.VIEW, - options: savedDashboard.optionsJSON ? JSON.parse(savedDashboard.optionsJSON) : {}, - }, - version, - usageCollection - ); + const { + dashboardCapabilities: { showWriteControls }, + } = pluginServices.getServices(); + + const rawState = migrateAppState({ + fullScreenMode: false, + title: savedDashboard.title, + query: savedDashboard.getQuery(), + filters: savedDashboard.getFilters(), + timeRestore: savedDashboard.timeRestore, + description: savedDashboard.description || '', + tags: getTagsFromSavedDashboard(savedDashboard), + panels: savedDashboard.panelsJSON ? JSON.parse(savedDashboard.panelsJSON) : [], + viewMode: savedDashboard.id || showWriteControls ? ViewMode.EDIT : ViewMode.VIEW, + options: savedDashboard.optionsJSON ? JSON.parse(savedDashboard.optionsJSON) : {}, + }); + if (rawState.timeRestore) { rawState.timeRange = { from: savedDashboard.timeFrom, to: savedDashboard.timeTo } as TimeRange; } @@ -95,14 +82,15 @@ export const savedObjectToDashboardState = ({ * Converts a dashboard state object to dashboard container input */ export const stateToDashboardContainerInput = ({ - dashboardCapabilities, isEmbeddedExternally, - query: queryService, searchSessionId, savedDashboard, dashboardState, executionContext, }: StateToDashboardContainerInputProps): DashboardContainerInput => { + const { + data: { query: queryService }, + } = pluginServices.getServices(); const { filterManager, timefilter: timefilterService } = queryService; const { timefilter } = timefilterService; @@ -134,7 +122,6 @@ export const stateToDashboardContainerInput = ({ ), isFullScreenMode: fullScreenMode, id: savedDashboard.id || '', - dashboardCapabilities, isEmbeddedExternally, ...(options || {}), controlGroupInput, @@ -162,11 +149,14 @@ const filtersAreEqual = (first: Filter, second: Filter) => * they require panels to be formatted as an array. */ export const stateToRawDashboardState = ({ - version, state, }: StateToRawDashboardStateProps): RawDashboardState => { + const { + initializerContext: { kibanaVersion }, + } = pluginServices.getServices(); + const savedDashboardPanels = Object.values(state.panels).map((panel) => - convertPanelStateToSavedDashboardPanel(panel, version) + convertPanelStateToSavedDashboardPanel(panel, kibanaVersion) ); return { ..._.omit(state, 'panels'), panels: savedDashboardPanels }; }; diff --git a/src/plugins/dashboard/public/application/lib/dashboard_session_restoration.ts b/src/plugins/dashboard/public/application/lib/dashboard_session_restoration.ts index 0b7f9ccb0e84f..b2cedeee4ee04 100644 --- a/src/plugins/dashboard/public/application/lib/dashboard_session_restoration.ts +++ b/src/plugins/dashboard/public/application/lib/dashboard_session_restoration.ts @@ -7,20 +7,22 @@ */ import { History } from 'history'; + import { createQueryParamObservable } from '@kbn/kibana-utils-plugin/public'; import type { Query } from '@kbn/es-query'; -import { DashboardAppLocatorParams, DashboardConstants } from '../..'; -import { DashboardState } from '../../types'; -import { getDashboardTitle } from '../../dashboard_strings'; -import { DashboardSavedObject } from '../../saved_dashboards'; -import { getQueryParams } from '../../services/kibana_utils'; import { - DataPublicPluginStart, noSearchSessionStorageCapabilityMessage, SearchSessionInfoProvider, -} from '../../services/data'; +} from '@kbn/data-plugin/public'; +import { getQueryParams } from '@kbn/kibana-utils-plugin/public'; + +import type { DashboardState } from '../../types'; +import type { DashboardSavedObject } from '../../saved_dashboards'; +import { DashboardAppLocatorParams, DashboardConstants } from '../..'; +import { getDashboardTitle } from '../../dashboard_strings'; import { stateToRawDashboardState } from './convert_dashboard_state'; import { DASHBOARD_APP_LOCATOR } from '../../locator'; +import { pluginServices } from '../../services/plugin_services'; export const getSearchSessionIdFromURL = (history: History): string | undefined => getQueryParams(history.location)[DashboardConstants.SEARCH_SESSION_ID] as string | undefined; @@ -29,8 +31,6 @@ export const getSessionURLObservable = (history: History) => createQueryParamObservable(history, DashboardConstants.SEARCH_SESSION_ID); export function createSessionRestorationDataProvider(deps: { - kibanaVersion: string; - data: DataPublicPluginStart; getAppState: () => DashboardState; getDashboardTitle: () => string; getDashboardId: () => string; @@ -53,16 +53,13 @@ export function enableDashboardSearchSessions({ initialDashboardState, getLatestDashboardState, savedDashboard, - kibanaVersion, - data, }: { - kibanaVersion: string; - data: DataPublicPluginStart; canStoreSearchSession: boolean; savedDashboard: DashboardSavedObject; initialDashboardState: DashboardState; getLatestDashboardState: () => DashboardState; }) { + const { data } = pluginServices.getServices(); const dashboardTitle = getDashboardTitle( initialDashboardState.title, initialDashboardState.viewMode, @@ -71,8 +68,6 @@ export function enableDashboardSearchSessions({ data.search.session.enableStorage( createSessionRestorationDataProvider({ - data, - kibanaVersion, getDashboardTitle: () => dashboardTitle, getDashboardId: () => savedDashboard?.id || '', getAppState: getLatestDashboardState, @@ -94,19 +89,17 @@ export function enableDashboardSearchSessions({ * as it was. */ function getLocatorParams({ - data, getAppState, - kibanaVersion, getDashboardId, shouldRestoreSearchSession, }: { - kibanaVersion: string; - data: DataPublicPluginStart; getAppState: () => DashboardState; getDashboardId: () => string; shouldRestoreSearchSession: boolean; }): DashboardAppLocatorParams { - const appState = stateToRawDashboardState({ state: getAppState(), version: kibanaVersion }); + const { data } = pluginServices.getServices(); + + const appState = stateToRawDashboardState({ state: getAppState() }); const { filterManager, queryString } = data.query; const { timefilter } = data.query.timefilter; diff --git a/src/plugins/dashboard/public/application/lib/dashboard_tagging.ts b/src/plugins/dashboard/public/application/lib/dashboard_tagging.ts index df2521044d1a3..0a8ec17aeb2f1 100644 --- a/src/plugins/dashboard/public/application/lib/dashboard_tagging.ts +++ b/src/plugins/dashboard/public/application/lib/dashboard_tagging.ts @@ -6,23 +6,26 @@ * Side Public License, v 1. */ +import type { TagDecoratedSavedObject } from '@kbn/saved-objects-tagging-oss-plugin/public'; +import type { SavedObject } from '@kbn/saved-objects-plugin/public'; + import { DashboardSavedObject } from '../..'; -import { SavedObject } from '../../services/saved_objects'; -import { SavedObjectsTaggingApi } from '../../services/saved_objects_tagging_oss'; -import type { TagDecoratedSavedObject } from '../../services/saved_objects_tagging_oss'; +import { pluginServices } from '../../services/plugin_services'; // TS is picky with type guards, we can't just inline `() => false` function defaultTaggingGuard(_obj: SavedObject): _obj is TagDecoratedSavedObject { return false; } -export const getTagsFromSavedDashboard = ( - savedDashboard: DashboardSavedObject, - api?: SavedObjectsTaggingApi -) => { - const hasTaggingCapabilities = getHasTaggingCapabilitiesGuard(api); +export const getTagsFromSavedDashboard = (savedDashboard: DashboardSavedObject) => { + const hasTaggingCapabilities = getHasTaggingCapabilitiesGuard(); return hasTaggingCapabilities(savedDashboard) ? savedDashboard.getTags() : []; }; -export const getHasTaggingCapabilitiesGuard = (api?: SavedObjectsTaggingApi) => - api?.ui.hasTagDecoration || defaultTaggingGuard; +export const getHasTaggingCapabilitiesGuard = () => { + const { + savedObjectsTagging: { hasTagDecoration }, + } = pluginServices.getServices(); + + return hasTagDecoration || defaultTaggingGuard; +}; diff --git a/src/plugins/dashboard/public/application/lib/diff_dashboard_state.test.ts b/src/plugins/dashboard/public/application/lib/diff_dashboard_state.test.ts index 895fbd0b2e39d..beef010ddd1cf 100644 --- a/src/plugins/dashboard/public/application/lib/diff_dashboard_state.test.ts +++ b/src/plugins/dashboard/public/application/lib/diff_dashboard_state.test.ts @@ -7,10 +7,10 @@ */ import { Filter } from '@kbn/es-query'; +import { EmbeddableInput, IEmbeddable, ViewMode } from '@kbn/embeddable-plugin/public'; import { DashboardOptions, DashboardState } from '../../types'; import { diffDashboardState } from './diff_dashboard_state'; -import { EmbeddableInput, IEmbeddable, ViewMode } from '../../services/embeddable'; const testFilter: Filter = { meta: { diff --git a/src/plugins/dashboard/public/application/lib/diff_dashboard_state.ts b/src/plugins/dashboard/public/application/lib/diff_dashboard_state.ts index 5fbc1cf2e28b3..ca913199a3ba2 100644 --- a/src/plugins/dashboard/public/application/lib/diff_dashboard_state.ts +++ b/src/plugins/dashboard/public/application/lib/diff_dashboard_state.ts @@ -9,11 +9,11 @@ import { xor, omit, isEmpty } from 'lodash'; import fastIsEqual from 'fast-deep-equal'; import { compareFilters, COMPARE_ALL_OPTIONS, type Filter, isFilterPinned } from '@kbn/es-query'; +import { IEmbeddable } from '@kbn/embeddable-plugin/public'; import { persistableControlGroupInputIsEqual } from '@kbn/controls-plugin/common'; import { DashboardContainerInput } from '../..'; import { DashboardOptions, DashboardPanelMap, DashboardState } from '../../types'; -import { IEmbeddable } from '../../services/embeddable'; const stateKeystoIgnore = [ 'expandedPanelId', diff --git a/src/plugins/dashboard/public/application/lib/filter_utils.ts b/src/plugins/dashboard/public/application/lib/filter_utils.ts index c6b9ae2d01cf3..9b9a1270fd3ba 100644 --- a/src/plugins/dashboard/public/application/lib/filter_utils.ts +++ b/src/plugins/dashboard/public/application/lib/filter_utils.ts @@ -8,9 +8,9 @@ import _ from 'lodash'; import moment, { Moment } from 'moment'; -import { Optional } from '@kbn/utility-types'; - -import { Filter, TimeRange, RefreshInterval } from '../../services/data'; +import type { Optional } from '@kbn/utility-types'; +import type { RefreshInterval } from '@kbn/data-plugin/public'; +import type { Filter, TimeRange } from '@kbn/es-query'; type TimeRangeCompare = Optional; type RefreshIntervalCompare = Optional; diff --git a/src/plugins/dashboard/public/application/lib/help_menu_util.ts b/src/plugins/dashboard/public/application/lib/help_menu_util.ts index 4a0b882e80ad9..9407fa31f545a 100644 --- a/src/plugins/dashboard/public/application/lib/help_menu_util.ts +++ b/src/plugins/dashboard/public/application/lib/help_menu_util.ts @@ -7,17 +7,22 @@ */ import { i18n } from '@kbn/i18n'; -import { ChromeStart, DocLinksStart } from '@kbn/core/public'; +import { pluginServices } from '../../services/plugin_services'; -export function addHelpMenuToAppChrome(chrome: ChromeStart, docLinks: DocLinksStart) { - chrome.setHelpExtension({ +export function addHelpMenuToAppChrome() { + const { + chrome: { setHelpExtension }, + documentationLinks: { kibanaGuideDocLink }, + } = pluginServices.getServices(); + + setHelpExtension({ appName: i18n.translate('dashboard.helpMenu.appName', { defaultMessage: 'Dashboards', }), links: [ { linkType: 'documentation', - href: `${docLinks.links.dashboard.guide}`, + href: `${kibanaGuideDocLink}`, }, ], }); diff --git a/src/plugins/dashboard/public/application/lib/index.ts b/src/plugins/dashboard/public/application/lib/index.ts index eab3604ff841b..1b4ab12d2bc1b 100644 --- a/src/plugins/dashboard/public/application/lib/index.ts +++ b/src/plugins/dashboard/public/application/lib/index.ts @@ -14,7 +14,6 @@ export { addHelpMenuToAppChrome } from './help_menu_util'; export { diffDashboardState } from './diff_dashboard_state'; export { getTagsFromSavedDashboard } from './dashboard_tagging'; export { syncDashboardUrlState } from './sync_dashboard_url_state'; -export { DashboardSessionStorage } from './dashboard_session_storage'; export { loadSavedDashboardState } from './load_saved_dashboard_state'; export { attemptLoadDashboardByTitle } from './load_dashboard_by_title'; export { syncDashboardFilterState } from './sync_dashboard_filter_state'; diff --git a/src/plugins/dashboard/public/application/lib/load_dashboard_by_title.ts b/src/plugins/dashboard/public/application/lib/load_dashboard_by_title.ts index f785589f1bbd6..bff9f8600c0ed 100644 --- a/src/plugins/dashboard/public/application/lib/load_dashboard_by_title.ts +++ b/src/plugins/dashboard/public/application/lib/load_dashboard_by_title.ts @@ -6,14 +6,17 @@ * Side Public License, v 1. */ -import { SavedObjectsClientContract } from '@kbn/core/public'; import { DashboardSavedObject } from '../..'; +import { pluginServices } from '../../services/plugin_services'; export async function attemptLoadDashboardByTitle( - title: string, - savedObjectsClient: SavedObjectsClientContract + title: string ): Promise<{ id: string } | undefined> { - const results = await savedObjectsClient.find({ + const { + savedObjects: { client }, + } = pluginServices.getServices(); + + const results = await client.find({ search: `"${title}"`, searchFields: ['title'], type: 'dashboard', diff --git a/src/plugins/dashboard/public/application/lib/load_saved_dashboard_state.ts b/src/plugins/dashboard/public/application/lib/load_saved_dashboard_state.ts index 1413301be63b4..6a7eba0884abe 100644 --- a/src/plugins/dashboard/public/application/lib/load_saved_dashboard_state.ts +++ b/src/plugins/dashboard/public/application/lib/load_saved_dashboard_state.ts @@ -6,13 +6,14 @@ * Side Public License, v 1. */ +import { ViewMode } from '@kbn/embeddable-plugin/public'; import { getDashboard60Warning, dashboardLoadingErrorStrings } from '../../dashboard_strings'; import { savedObjectToDashboardState } from './convert_dashboard_state'; import { DashboardState, DashboardBuildContext } from '../../types'; import { DashboardConstants, DashboardSavedObject } from '../..'; import { migrateLegacyQuery } from './migrate_legacy_query'; import { cleanFiltersForSerialize } from './filter_utils'; -import { ViewMode } from '../../services/embeddable'; +import { pluginServices } from '../../services/plugin_services'; interface LoadSavedDashboardStateReturn { savedDashboardState: DashboardState; @@ -23,21 +24,19 @@ interface LoadSavedDashboardStateReturn { * Loads, migrates, and returns state from a dashboard saved object. */ export const loadSavedDashboardState = async ({ - query, history, - notifications, - dataViews, savedDashboards, - usageCollection, savedDashboardId, - initializerContext, - savedObjectsTagging, - dashboardCapabilities, }: DashboardBuildContext & { savedDashboardId?: string }): Promise< LoadSavedDashboardStateReturn | undefined > => { - const { showWriteControls } = dashboardCapabilities; - const { queryString } = query; + const { + dashboardCapabilities: { showWriteControls }, + data: { + query: { queryString }, + }, + notifications: { toasts }, + } = pluginServices.getServices(); // BWC - remove for 8.0 if (savedDashboardId === 'create') { @@ -46,7 +45,7 @@ export const loadSavedDashboardState = async ({ pathname: DashboardConstants.CREATE_NEW_DASHBOARD_URL, }); - notifications.toasts.addWarning(getDashboard60Warning()); + toasts.addWarning(getDashboard60Warning()); return; } try { @@ -56,10 +55,6 @@ export const loadSavedDashboardState = async ({ })) as DashboardSavedObject; const savedDashboardState = savedObjectToDashboardState({ savedDashboard, - usageCollection, - showWriteControls, - savedObjectsTagging, - version: initializerContext.env.packageInfo.version, }); const isViewMode = !showWriteControls || Boolean(savedDashboard.id); @@ -72,9 +67,7 @@ export const loadSavedDashboardState = async ({ return { savedDashboardState, savedDashboard }; } catch (error) { // E.g. a corrupt or deleted dashboard - notifications.toasts.addDanger( - dashboardLoadingErrorStrings.getDashboardLoadError(error.message) - ); + toasts.addDanger(dashboardLoadingErrorStrings.getDashboardLoadError(error.message)); history.push(DashboardConstants.LANDING_PAGE_PATH); return; } diff --git a/src/plugins/dashboard/public/application/lib/migrate_app_state.test.ts b/src/plugins/dashboard/public/application/lib/migrate_app_state.test.ts index 3ddcb1265e0bd..578439070e970 100644 --- a/src/plugins/dashboard/public/application/lib/migrate_app_state.test.ts +++ b/src/plugins/dashboard/public/application/lib/migrate_app_state.test.ts @@ -6,9 +6,12 @@ * Side Public License, v 1. */ +import { pluginServices } from '../../services/plugin_services'; import { SavedDashboardPanel } from '../../types'; import { migrateAppState } from './migrate_app_state'; +pluginServices.getServices().initializerContext.kibanaVersion = '8.0'; + test('migrate app state from 6.0', async () => { const appState = { uiState: { @@ -26,7 +29,7 @@ test('migrate app state from 6.0', async () => { }, ], }; - migrateAppState(appState as any, '8.0'); + migrateAppState(appState as any); expect(appState.uiState).toBeUndefined(); const newPanel = appState.panels[0] as unknown as SavedDashboardPanel; @@ -40,7 +43,6 @@ test('migrate app state from 6.0', async () => { }); test('migrate sort from 6.1', async () => { - const TARGET_VERSION = '8.0'; const appState = { uiState: { 'P-1': { vis: { defaultColors: { '0+-+100': 'rgb(0,104,55)' } } }, @@ -59,7 +61,7 @@ test('migrate sort from 6.1', async () => { ], useMargins: false, }; - migrateAppState(appState as any, TARGET_VERSION); + migrateAppState(appState as any); expect(appState.uiState).toBeUndefined(); const newPanel = appState.panels[0] as unknown as SavedDashboardPanel; @@ -86,7 +88,7 @@ test('migrates 6.0 even when uiState does not exist', async () => { }, ], }; - migrateAppState(appState as any, '8.0'); + migrateAppState(appState as any); expect((appState as any).uiState).toBeUndefined(); const newPanel = appState.panels[0] as unknown as SavedDashboardPanel; @@ -116,7 +118,7 @@ test('6.2 migration adjusts w & h without margins', async () => { ], useMargins: false, }; - migrateAppState(appState as any, '8.0'); + migrateAppState(appState as any); expect((appState as any).uiState).toBeUndefined(); const newPanel = appState.panels[0] as unknown as SavedDashboardPanel; @@ -148,7 +150,7 @@ test('6.2 migration adjusts w & h with margins', async () => { ], useMargins: true, }; - migrateAppState(appState as any, '8.0'); + migrateAppState(appState as any); expect((appState as any).uiState).toBeUndefined(); const newPanel = appState.panels[0] as unknown as SavedDashboardPanel; diff --git a/src/plugins/dashboard/public/application/lib/migrate_app_state.ts b/src/plugins/dashboard/public/application/lib/migrate_app_state.ts index 03e436aea78f2..e077aab89ea8b 100644 --- a/src/plugins/dashboard/public/application/lib/migrate_app_state.ts +++ b/src/plugins/dashboard/public/application/lib/migrate_app_state.ts @@ -7,14 +7,13 @@ */ import semverSatisfies from 'semver/functions/satisfies'; -import type { SerializableRecord } from '@kbn/utility-types'; + import { i18n } from '@kbn/i18n'; import { METRIC_TYPE } from '@kbn/analytics'; +import type { SerializableRecord } from '@kbn/utility-types'; -import { UsageCollectionSetup } from '../../services/usage_collection'; import { RawDashboardState, SavedDashboardPanel } from '../../types'; -import { - migratePanelsTo730, +import type { SavedDashboardPanelTo60, SavedDashboardPanel730ToLatest, SavedDashboardPanel610, @@ -22,6 +21,8 @@ import { SavedDashboardPanel640To720, SavedDashboardPanel620, } from '../../../common'; +import { migratePanelsTo730 } from '../../../common'; +import { pluginServices } from '../../services/plugin_services'; /** * Attempts to migrate the state stored in the URL into the latest version of it. @@ -29,9 +30,7 @@ import { * Once we hit a major version, we can remove support for older style URLs and get rid of this logic. */ export function migrateAppState( - appState: { [key: string]: any } & RawDashboardState, - kibanaVersion: string, - usageCollection?: UsageCollectionSetup + appState: { [key: string]: any } & RawDashboardState ): RawDashboardState { if (!appState.panels) { throw new Error( @@ -41,6 +40,11 @@ export function migrateAppState( ); } + const { + usageCollection: { reportUiCounter }, + initializerContext: { kibanaVersion }, + } = pluginServices.getServices(); + const panelNeedsMigration = ( appState.panels as Array< | SavedDashboardPanelTo60 @@ -55,13 +59,9 @@ export function migrateAppState( const version = (panel as SavedDashboardPanel730ToLatest).version; - if (usageCollection) { + if (reportUiCounter) { // This will help us figure out when to remove support for older style URLs. - usageCollection.reportUiCounter( - 'DashboardPanelVersionInUrl', - METRIC_TYPE.LOADED, - `${version}` - ); + reportUiCounter('DashboardPanelVersionInUrl', METRIC_TYPE.LOADED, `${version}`); } return semverSatisfies(version, '<7.3'); diff --git a/src/plugins/dashboard/public/application/lib/migrate_legacy_query.ts b/src/plugins/dashboard/public/application/lib/migrate_legacy_query.ts index b08e2e4ebfddd..e8c9ff022b466 100644 --- a/src/plugins/dashboard/public/application/lib/migrate_legacy_query.ts +++ b/src/plugins/dashboard/public/application/lib/migrate_legacy_query.ts @@ -7,8 +7,7 @@ */ import { has } from 'lodash'; -import { Query } from '../../services/data'; - +import type { Query } from '@kbn/es-query'; /** * Creates a standardized query object from old queries that were either strings or pure ES query DSL * diff --git a/src/plugins/dashboard/public/application/lib/save_dashboard.ts b/src/plugins/dashboard/public/application/lib/save_dashboard.ts index 0be2211d4c2fc..f3347ca3f2041 100644 --- a/src/plugins/dashboard/public/application/lib/save_dashboard.ts +++ b/src/plugins/dashboard/public/application/lib/save_dashboard.ts @@ -9,51 +9,51 @@ import _ from 'lodash'; import { isFilterPinned } from '@kbn/es-query'; +import type { RefreshInterval } from '@kbn/data-plugin/public'; +import type { SavedObjectSaveOpts } from '@kbn/saved-objects-plugin/public'; + import { convertTimeToUTCString } from '.'; -import { NotificationsStart } from '../../services/core'; -import { DashboardSavedObject } from '../../saved_dashboards'; -import { DashboardRedirect, DashboardState } from '../../types'; -import { SavedObjectSaveOpts } from '../../services/saved_objects'; +import type { DashboardSavedObject } from '../../saved_dashboards'; import { dashboardSaveToastStrings } from '../../dashboard_strings'; import { getHasTaggingCapabilitiesGuard } from './dashboard_tagging'; -import { SavedObjectsTaggingApi } from '../../services/saved_objects_tagging_oss'; -import { RefreshInterval, TimefilterContract } from '../../services/data'; -import { convertPanelStateToSavedDashboardPanel } from '../../../common/embeddable/embeddable_saved_object_converters'; -import { DashboardSessionStorage } from './dashboard_session_storage'; +import type { DashboardRedirect, DashboardState } from '../../types'; import { serializeControlGroupToDashboardSavedObject } from './dashboard_control_group'; +import { convertPanelStateToSavedDashboardPanel } from '../../../common/embeddable/embeddable_saved_object_converters'; +import { pluginServices } from '../../services/plugin_services'; export type SavedDashboardSaveOpts = SavedObjectSaveOpts & { stayInEditMode?: boolean }; interface SaveDashboardProps { - version: string; redirectTo: DashboardRedirect; currentState: DashboardState; - timefilter: TimefilterContract; saveOptions: SavedDashboardSaveOpts; - toasts: NotificationsStart['toasts']; savedDashboard: DashboardSavedObject; - savedObjectsTagging?: SavedObjectsTaggingApi; - dashboardSessionStorage: DashboardSessionStorage; } export const saveDashboard = async ({ - toasts, - version, redirectTo, - timefilter, saveOptions, currentState, savedDashboard, - savedObjectsTagging, - dashboardSessionStorage, }: SaveDashboardProps): Promise<{ id?: string; redirected?: boolean; error?: any }> => { + const { + data: { + query: { + timefilter: { timefilter }, + }, + }, + dashboardSessionStorage, + initializerContext: { kibanaVersion }, + notifications, + } = pluginServices.getServices(); + const lastDashboardId = savedDashboard.id; - const hasTaggingCapabilities = getHasTaggingCapabilitiesGuard(savedObjectsTagging); + const hasTaggingCapabilities = getHasTaggingCapabilitiesGuard(); const { panels, title, tags, description, timeRestore, options } = currentState; const savedDashboardPanels = Object.values(panels).map((panel) => - convertPanelStateToSavedDashboardPanel(panel, version) + convertPanelStateToSavedDashboardPanel(panel, kibanaVersion) ); savedDashboard.title = title; @@ -88,7 +88,7 @@ export const saveDashboard = async ({ try { const newId = await savedDashboard.save(saveOptions); if (newId) { - toasts.addSuccess({ + notifications.toasts.addSuccess({ title: dashboardSaveToastStrings.getSuccessString(currentState.title), 'data-test-subj': 'saveDashboardSuccess', }); @@ -109,10 +109,12 @@ export const saveDashboard = async ({ } return { id: newId }; } catch (error) { - toasts.addDanger({ - title: dashboardSaveToastStrings.getFailureString(currentState.title, error.message), - 'data-test-subj': 'saveDashboardFailure', - }); + notifications.toasts.addDanger( + dashboardSaveToastStrings.getFailureString(currentState.title, error.message), + { + 'data-test-subj': 'saveDashboardFailure', + } + ); return { error }; } }; diff --git a/src/plugins/dashboard/public/application/lib/session_restoration.test.ts b/src/plugins/dashboard/public/application/lib/session_restoration.test.ts index 0a86ed7efe5f3..aeb83dd8a6e4c 100644 --- a/src/plugins/dashboard/public/application/lib/session_restoration.test.ts +++ b/src/plugins/dashboard/public/application/lib/session_restoration.test.ts @@ -7,21 +7,13 @@ */ import { getSavedDashboardMock } from '../test_helpers'; -import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { createSessionRestorationDataProvider, savedObjectToDashboardState } from '.'; +import { pluginServices } from '../../services/plugin_services'; describe('createSessionRestorationDataProvider', () => { - const mockDataPlugin = dataPluginMock.createStartContract(); - const version = '8.0.0'; const searchSessionInfoProvider = createSessionRestorationDataProvider({ - kibanaVersion: version, - data: mockDataPlugin, getAppState: () => savedObjectToDashboardState({ - version, - showWriteControls: true, - usageCollection: undefined, - savedObjectsTagging: undefined, savedDashboard: getSavedDashboardMock(), }), getDashboardTitle: () => 'Dashboard', @@ -31,9 +23,9 @@ describe('createSessionRestorationDataProvider', () => { describe('session state', () => { test('restoreState has sessionId and initialState has not', async () => { const searchSessionId = 'id'; - (mockDataPlugin.search.session.getSessionId as jest.Mock).mockImplementation( - () => searchSessionId - ); + ( + pluginServices.getServices().data.search.session.getSessionId as jest.Mock + ).mockImplementation(() => searchSessionId); const { initialState, restoreState } = await searchSessionInfoProvider.getLocatorData(); expect(initialState.searchSessionId).toBeUndefined(); expect(restoreState.searchSessionId).toBe(searchSessionId); @@ -42,12 +34,12 @@ describe('createSessionRestorationDataProvider', () => { test('restoreState has absoluteTimeRange', async () => { const relativeTime = 'relativeTime'; const absoluteTime = 'absoluteTime'; - (mockDataPlugin.query.timefilter.timefilter.getTime as jest.Mock).mockImplementation( - () => relativeTime - ); - (mockDataPlugin.query.timefilter.timefilter.getAbsoluteTime as jest.Mock).mockImplementation( - () => absoluteTime - ); + ( + pluginServices.getServices().data.query.timefilter.timefilter.getTime as jest.Mock + ).mockImplementation(() => relativeTime); + ( + pluginServices.getServices().data.query.timefilter.timefilter.getAbsoluteTime as jest.Mock + ).mockImplementation(() => absoluteTime); const { initialState, restoreState } = await searchSessionInfoProvider.getLocatorData(); expect(initialState.timeRange).toBe(relativeTime); expect(restoreState.timeRange).toBe(absoluteTime); diff --git a/src/plugins/dashboard/public/application/lib/sync_dashboard_container_input.ts b/src/plugins/dashboard/public/application/lib/sync_dashboard_container_input.ts index 74a2ba9525d38..67e098186f9e9 100644 --- a/src/plugins/dashboard/public/application/lib/sync_dashboard_container_input.ts +++ b/src/plugins/dashboard/public/application/lib/sync_dashboard_container_input.ts @@ -12,9 +12,10 @@ import { debounceTime, tap } from 'rxjs/operators'; import { compareFilters, COMPARE_ALL_OPTIONS, type Filter } from '@kbn/es-query'; import { replaceUrlHashQuery } from '@kbn/kibana-utils-plugin/public'; -import { DashboardContainer } from '../embeddable'; -import { Query } from '../../services/data'; -import { DashboardConstants, DashboardSavedObject } from '../..'; +import type { Query } from '@kbn/es-query'; + +import type { DashboardContainer } from '../embeddable'; +import { DashboardConstants, type DashboardSavedObject } from '../..'; import { setControlGroupState, setExpandedPanelId, @@ -25,12 +26,13 @@ import { setTimeslice, } from '../state'; import { diffDashboardContainerInput } from './diff_dashboard_state'; -import { DashboardBuildContext, DashboardContainerInput } from '../../types'; +import type { DashboardBuildContext, DashboardContainerInput } from '../../types'; import { getSearchSessionIdFromURL, getSessionURLObservable, stateToDashboardContainerInput, } from '.'; +import { pluginServices } from '../../services/plugin_services'; type SyncDashboardContainerCommon = DashboardBuildContext & { dashboardContainer: DashboardContainer; @@ -92,12 +94,15 @@ export const syncDashboardContainerInput = ( }; export const applyContainerChangesToState = ({ - query, applyFilters, dashboardContainer, getLatestDashboardState, dispatchDashboardStateChange, }: ApplyContainerChangesToStateProps) => { + const { + data: { query }, + } = pluginServices.getServices(); + const input = dashboardContainer.getInput(); const latestState = getLatestDashboardState(); if (Object.keys(latestState).length === 0) { @@ -138,16 +143,17 @@ export const applyContainerChangesToState = ({ export const applyStateChangesToContainer = ({ force, - search, history, savedDashboard, dashboardContainer, kbnUrlStateStorage, - query: queryService, isEmbeddedExternally, - dashboardCapabilities, getLatestDashboardState, }: ApplyStateChangesToContainerProps) => { + const { + data: { search }, + } = pluginServices.getServices(); + const latestState = getLatestDashboardState(); if (Object.keys(latestState).length === 0) { return; @@ -155,8 +161,6 @@ export const applyStateChangesToContainer = ({ const currentDashboardStateAsInput = stateToDashboardContainerInput({ dashboardState: latestState, isEmbeddedExternally, - dashboardCapabilities, - query: queryService, savedDashboard, }); const differences = diffDashboardContainerInput( diff --git a/src/plugins/dashboard/public/application/lib/sync_dashboard_data_views.ts b/src/plugins/dashboard/public/application/lib/sync_dashboard_data_views.ts index 0d55ee26ab6ed..afb59e050a204 100644 --- a/src/plugins/dashboard/public/application/lib/sync_dashboard_data_views.ts +++ b/src/plugins/dashboard/public/application/lib/sync_dashboard_data_views.ts @@ -10,22 +10,25 @@ import deepEqual from 'fast-deep-equal'; import { Observable, pipe, combineLatest } from 'rxjs'; import { distinctUntilChanged, switchMap, filter, map } from 'rxjs/operators'; +import { DataView } from '@kbn/data-views-plugin/common'; +import { isErrorEmbeddable } from '@kbn/embeddable-plugin/public'; + import { DashboardContainer } from '..'; -import { isErrorEmbeddable } from '../../services/embeddable'; -import { DataViewsContract } from '../../services/data'; -import { DataView } from '../../services/data_views'; +import { pluginServices } from '../../services/plugin_services'; interface SyncDashboardDataViewsProps { dashboardContainer: DashboardContainer; - dataViews: DataViewsContract; onUpdateDataViews: (newDataViewIds: string[]) => void; } export const syncDashboardDataViews = ({ dashboardContainer, - dataViews, onUpdateDataViews, }: SyncDashboardDataViewsProps) => { + const { + data: { dataViews }, + } = pluginServices.getServices(); + const updateDataViewsOperator = pipe( filter((container: DashboardContainer) => !!container && !isErrorEmbeddable(container)), map((container: DashboardContainer): string[] | undefined => { diff --git a/src/plugins/dashboard/public/application/lib/sync_dashboard_filter_state.ts b/src/plugins/dashboard/public/application/lib/sync_dashboard_filter_state.ts index a23ae278c6978..d02e674936aef 100644 --- a/src/plugins/dashboard/public/application/lib/sync_dashboard_filter_state.ts +++ b/src/plugins/dashboard/public/application/lib/sync_dashboard_filter_state.ts @@ -9,19 +9,20 @@ import _ from 'lodash'; import { merge } from 'rxjs'; import { debounceTime, finalize, map, switchMap, tap } from 'rxjs/operators'; -import { setQuery } from '../state'; -import { DashboardBuildContext, DashboardState } from '../../types'; -import { DashboardSavedObject } from '../../saved_dashboards'; -import { setFiltersAndQuery } from '../state/dashboard_state_slice'; import { - syncQueryStateWithUrl, connectToQueryState, - Filter, - Query, - waitUntilNextSessionCompletes$, GlobalQueryStateFromUrl, -} from '../../services/data'; + syncQueryStateWithUrl, + waitUntilNextSessionCompletes$, +} from '@kbn/data-plugin/public'; +import type { Filter, Query } from '@kbn/es-query'; + import { cleanFiltersForSerialize } from '.'; +import { setQuery } from '../state'; +import type { DashboardBuildContext, DashboardState } from '../../types'; +import type { DashboardSavedObject } from '../../saved_dashboards'; +import { setFiltersAndQuery } from '../state/dashboard_state_slice'; +import { pluginServices } from '../../services/plugin_services'; type SyncDashboardFilterStateProps = DashboardBuildContext & { initialDashboardState: DashboardState; @@ -34,16 +35,17 @@ type SyncDashboardFilterStateProps = DashboardBuildContext & { * and the dashboard Redux store. */ export const syncDashboardFilterState = ({ - search, savedDashboard, kbnUrlStateStorage, - query: queryService, initialDashboardState, $checkForUnsavedChanges, $onDashboardStateChange, $triggerDashboardRefresh, dispatchDashboardStateChange, }: SyncDashboardFilterStateProps) => { + const { + data: { query: queryService, search }, + } = pluginServices.getServices(); const { filterManager, queryString, timefilter } = queryService; const { timefilter: timefilterService } = timefilter; @@ -52,7 +54,6 @@ export const syncDashboardFilterState = ({ currentDashboardState: initialDashboardState, kbnUrlStateStorage, savedDashboard, - queryService, }); // this callback will be used any time new filters and query need to be applied. @@ -138,7 +139,6 @@ export const syncDashboardFilterState = ({ interface ApplyDashboardFilterStateProps { kbnUrlStateStorage: DashboardBuildContext['kbnUrlStateStorage']; - queryService: DashboardBuildContext['query']; currentDashboardState: DashboardState; savedDashboard: DashboardSavedObject; } @@ -147,9 +147,12 @@ export const applyDashboardFilterState = ({ currentDashboardState, kbnUrlStateStorage, savedDashboard, - queryService, }: ApplyDashboardFilterStateProps) => { - const { filterManager, queryString, timefilter } = queryService; + const { + data: { + query: { filterManager, queryString, timefilter }, + }, + } = pluginServices.getServices(); const { timefilter: timefilterService } = timefilter; // apply filters to the query service and to the saved dashboard diff --git a/src/plugins/dashboard/public/application/lib/sync_dashboard_url_state.ts b/src/plugins/dashboard/public/application/lib/sync_dashboard_url_state.ts index 0aa9e9c595fc1..947e3f5d69de7 100644 --- a/src/plugins/dashboard/public/application/lib/sync_dashboard_url_state.ts +++ b/src/plugins/dashboard/public/application/lib/sync_dashboard_url_state.ts @@ -29,11 +29,8 @@ type SyncDashboardUrlStateProps = DashboardBuildContext & { savedDashboard: Dash export const syncDashboardUrlState = ({ dispatchDashboardStateChange, getLatestDashboardState, - query: queryService, kbnUrlStateStorage, - usageCollection, savedDashboard, - kibanaVersion, }: SyncDashboardUrlStateProps) => { /** * Loads any dashboard state from the URL, and removes the state from the URL. @@ -44,7 +41,7 @@ export const syncDashboardUrlState = ({ let panelsMap: DashboardPanelMap = {}; if (rawAppStateInUrl.panels && rawAppStateInUrl.panels.length > 0) { - const rawState = migrateAppState(rawAppStateInUrl, kibanaVersion, usageCollection); + const rawState = migrateAppState(rawAppStateInUrl); panelsMap = convertSavedPanelsToPanelMap(rawState.panels); } @@ -78,7 +75,6 @@ export const syncDashboardUrlState = ({ applyDashboardFilterState({ currentDashboardState: updatedDashboardState, kbnUrlStateStorage, - queryService, savedDashboard, }); diff --git a/src/plugins/dashboard/public/application/listing/__snapshots__/dashboard_listing.test.tsx.snap b/src/plugins/dashboard/public/application/listing/__snapshots__/dashboard_listing.test.tsx.snap index 006ddb9595a97..064daca6db30b 100644 --- a/src/plugins/dashboard/public/application/listing/__snapshots__/dashboard_listing.test.tsx.snap +++ b/src/plugins/dashboard/public/application/listing/__snapshots__/dashboard_listing.test.tsx.snap @@ -24,19 +24,11 @@ exports[`after fetch When given a title that matches multiple dashboards, filter `; -exports[`after fetch renders warning when listingLimit is exceeded 1`] = ` - - - Create a dashboard - - } - body={ - -

- Analyze all of your Elastic data in one place by creating a dashboard and adding visualizations. -

-

- - Add some sample data - , - } - } - /> -

-
- } - iconType="dashboardApp" - title={ -

- Create your first dashboard -

- } - /> - } - entityName="dashboard" - entityNamePlural="dashboards" - findItems={[Function]} - headingId="dashboardListingHeading" - initialFilter="" - rowHeader="title" - searchFilters={Array []} - tableCaption="Dashboards" - tableColumns={ - Array [ - Object { - "field": "title", - "name": "Title", - "render": [Function], - "sortable": true, - }, - Object { - "field": "description", - "name": "Description", - "render": [Function], - "sortable": true, - }, - ] - } - tableListTitle="Dashboards" - theme={ - Object { - "theme$": Observable { - "_subscribe": [Function], - }, - } - } - toastNotifications={ - Object { - "add": [MockFunction], - "addDanger": [MockFunction], - "addError": [MockFunction], - "addInfo": [MockFunction], - "addSuccess": [MockFunction], - "addWarning": [MockFunction], - "get$": [MockFunction], - "remove": [MockFunction], - } - } - /> -
-`; - exports[`after fetch showWriteControls 1`] = ` void) => - overlays - .openConfirm(discardConfirmStrings.getDiscardSubtitle(), { - confirmButtonText: discardConfirmStrings.getDiscardConfirmButtonText(), - cancelButtonText: discardConfirmStrings.getDiscardCancelButtonText(), - buttonColor: 'danger', - defaultFocusedButton: EUI_MODAL_CANCEL_BUTTON, - title: discardConfirmStrings.getDiscardTitle(), - }) - .then((isConfirmed) => { - if (isConfirmed) { - discardCallback(); - } - }); +export const confirmDiscardUnsavedChanges = (discardCallback: () => void) => { + const { + overlays: { openConfirm }, + } = pluginServices.getServices(); + + openConfirm(discardConfirmStrings.getDiscardSubtitle(), { + confirmButtonText: discardConfirmStrings.getDiscardConfirmButtonText(), + cancelButtonText: discardConfirmStrings.getDiscardCancelButtonText(), + buttonColor: 'danger', + defaultFocusedButton: EUI_MODAL_CANCEL_BUTTON, + title: discardConfirmStrings.getDiscardTitle(), + }).then((isConfirmed) => { + if (isConfirmed) { + discardCallback(); + } + }); +}; export const confirmCreateWithUnsaved = ( - overlays: OverlayStart, - theme: CoreStart['theme'], startBlankCallback: () => void, contineCallback: () => void ) => { const titleId = 'confirmDiscardOrKeepTitle'; const descriptionId = 'confirmDiscardOrKeepDescription'; - const session = overlays.openModal( + const { + settings: { + theme: { theme$ }, + }, + overlays: { openModal }, + } = pluginServices.getServices(); + + const session = openModal( toMountPoint( , - { theme$: theme.theme$ } + { theme$ } ), { 'data-test-subj': 'dashboardCreateConfirmModal', diff --git a/src/plugins/dashboard/public/application/listing/dashboard_listing.test.tsx b/src/plugins/dashboard/public/application/listing/dashboard_listing.test.tsx index a4831d9231b73..d690c91574eef 100644 --- a/src/plugins/dashboard/public/application/listing/dashboard_listing.test.tsx +++ b/src/plugins/dashboard/public/application/listing/dashboard_listing.test.tsx @@ -8,15 +8,17 @@ import React from 'react'; import { mount } from 'enzyme'; + import { I18nProvider } from '@kbn/i18n-react'; +import { SimpleSavedObject } from '@kbn/core/public'; +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import { createKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public'; import { DashboardAppServices } from '../../types'; -import { SimpleSavedObject } from '@kbn/core/public'; -import { KibanaContextProvider } from '../../services/kibana_react'; -import { createKbnUrlStateStorage } from '../../services/kibana_utils'; import { DashboardListing, DashboardListingProps } from './dashboard_listing'; import { makeDefaultServices } from '../test_helpers'; -import { DASHBOARD_PANELS_UNSAVED_ID } from '../lib/dashboard_session_storage'; +import { pluginServices } from '../../services/plugin_services'; +import { DASHBOARD_PANELS_UNSAVED_ID } from '../../services/dashboard_session_storage/dashboard_session_storage_service'; function makeDefaultProps(): DashboardListingProps { return { @@ -37,9 +39,14 @@ function mountWith({ const wrappingComponent: React.FC<{ children: React.ReactNode; }> = ({ children }) => { + const DashboardServicesProvider = pluginServices.getContextProvider(); + return ( - {children} + {/* Can't get rid of KibanaContextProvider here yet because of 'call to action when no dashboards exist' tests below */} + + {children} + ); }; @@ -81,9 +88,10 @@ describe('after fetch', () => { hits: [], }); }; - services.dashboardSessionStorage.getDashboardIdsWithUnsavedChanges = () => [ - DASHBOARD_PANELS_UNSAVED_ID, - ]; + pluginServices.getServices().dashboardSessionStorage.getDashboardIdsWithUnsavedChanges = jest + .fn() + .mockReturnValueOnce([DASHBOARD_PANELS_UNSAVED_ID]) + .mockReturnValue(['dashboardUnsavedOne', 'dashboardUnsavedTwo']); const { component } = mountWith({ services }); // Ensure all promises resolve await new Promise((resolve) => process.nextTick(resolve)); @@ -107,8 +115,7 @@ describe('after fetch', () => { const title = 'search by title'; const props = makeDefaultProps(); props.title = title; - const services = makeDefaultServices(); - services.savedObjectsClient.find = () => { + pluginServices.getServices().savedObjects.client.find = () => { return Promise.resolve({ perPage: 10, total: 2, @@ -119,7 +126,7 @@ describe('after fetch', () => { ], }); }; - const { component } = mountWith({ props, services }); + const { component } = mountWith({ props }); // Ensure all promises resolve await new Promise((resolve) => process.nextTick(resolve)); // Ensure the state changes are reflected @@ -132,8 +139,7 @@ describe('after fetch', () => { const title = 'search by title'; const props = makeDefaultProps(); props.title = title; - const services = makeDefaultServices(); - services.savedObjectsClient.find = () => { + pluginServices.getServices().savedObjects.client.find = () => { return Promise.resolve({ perPage: 10, total: 1, @@ -141,7 +147,7 @@ describe('after fetch', () => { savedObjects: [{ attributes: { title }, id: 'you_found_me' } as SimpleSavedObject], }); }; - const { component } = mountWith({ props, services }); + const { component } = mountWith({ props }); // Ensure all promises resolve await new Promise((resolve) => process.nextTick(resolve)); // Ensure the state changes are reflected @@ -154,20 +160,9 @@ describe('after fetch', () => { }); test('showWriteControls', async () => { - const services = makeDefaultServices(); - services.dashboardCapabilities.showWriteControls = false; - const { component } = mountWith({ services }); - // Ensure all promises resolve - await new Promise((resolve) => process.nextTick(resolve)); - // Ensure the state changes are reflected - component.update(); - expect(component).toMatchSnapshot(); - }); + pluginServices.getServices().dashboardCapabilities.showWriteControls = false; - test('renders warning when listingLimit is exceeded', async () => { - const services = makeDefaultServices(); - services.savedObjects.settings.getListingLimit = () => 1; - const { component } = mountWith({ services }); + const { component } = mountWith({}); // Ensure all promises resolve await new Promise((resolve) => process.nextTick(resolve)); // Ensure the state changes are reflected diff --git a/src/plugins/dashboard/public/application/listing/dashboard_listing.tsx b/src/plugins/dashboard/public/application/listing/dashboard_listing.tsx index 17433562bcdab..0d72523e913e2 100644 --- a/src/plugins/dashboard/public/application/listing/dashboard_listing.tsx +++ b/src/plugins/dashboard/public/application/listing/dashboard_listing.tsx @@ -17,9 +17,13 @@ import { EuiButtonEmpty, } from '@elastic/eui'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { ApplicationStart, SavedObjectsFindOptionsReference } from '@kbn/core/public'; -import { useExecutionContext } from '@kbn/kibana-react-plugin/public'; +import type { ApplicationStart, SavedObjectsFindOptionsReference } from '@kbn/core/public'; import useMount from 'react-use/lib/useMount'; +import { useExecutionContext } from '@kbn/kibana-react-plugin/public'; +import { syncGlobalQueryStateWithUrl } from '@kbn/data-plugin/public'; +import { TableListView, useKibana } from '@kbn/kibana-react-plugin/public'; +import type { IKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public'; + import { attemptLoadDashboardByTitle } from '../lib'; import { DashboardAppServices, DashboardRedirect } from '../../types'; import { @@ -29,15 +33,12 @@ import { dashboardUnsavedListingStrings, getNewDashboardTitle, } from '../../dashboard_strings'; -import { syncQueryStateWithUrl } from '../../services/data'; -import { IKbnUrlStateStorage } from '../../services/kibana_utils'; -import { TableListView, useKibana } from '../../services/kibana_react'; -import { SavedObjectsTaggingApi } from '../../services/saved_objects_tagging_oss'; import { DashboardUnsavedListing } from './dashboard_unsaved_listing'; import { confirmCreateWithUnsaved, confirmDiscardUnsavedChanges } from './confirm_overlays'; import { getDashboardListItemLink } from './get_dashboard_list_item_link'; -import { DASHBOARD_PANELS_UNSAVED_ID } from '../lib/dashboard_session_storage'; import { DashboardAppNoDataPage, isDashboardAppInNoDataState } from '../dashboard_app_no_data'; +import { pluginServices } from '../../services/plugin_services'; +import { DASHBOARD_PANELS_UNSAVED_ID } from '../../services/dashboard_session_storage/dashboard_session_storage_service'; const SAVED_OBJECTS_LIMIT_SETTING = 'savedObjects:listingLimit'; const SAVED_OBJECTS_PER_PAGE_SETTING = 'savedObjects:perPage'; @@ -56,29 +57,32 @@ export const DashboardListing = ({ kbnUrlStateStorage, }: DashboardListingProps) => { const { - services: { - core, - data, - dataViews, - savedDashboards, - savedObjectsClient, - savedObjectsTagging, - dashboardCapabilities, - dashboardSessionStorage, - chrome: { setBreadcrumbs }, - }, + services: { savedDashboards }, } = useKibana(); + const { + application, + chrome: { setBreadcrumbs }, + coreContext: { executionContext }, + dashboardCapabilities: { showWriteControls }, + dashboardSessionStorage, + data: { query }, + notifications: { toasts }, + savedObjects: { client }, + savedObjectsTagging: { getSearchBarFilter, parseSearchQuery }, + settings: { uiSettings, theme }, + } = pluginServices.getServices(); + const [showNoDataPage, setShowNoDataPage] = useState(false); useMount(() => { - (async () => setShowNoDataPage(await isDashboardAppInNoDataState(dataViews)))(); + (async () => setShowNoDataPage(await isDashboardAppInNoDataState()))(); }); const [unsavedDashboardIds, setUnsavedDashboardIds] = useState( dashboardSessionStorage.getDashboardIdsWithUnsavedChanges() ); - useExecutionContext(core.executionContext, { + useExecutionContext(executionContext, { type: 'application', page: 'list', }); @@ -94,12 +98,12 @@ export const DashboardListing = ({ useEffect(() => { // syncs `_g` portion of url with query services - const { stop: stopSyncingQueryServiceStateWithUrl } = syncQueryStateWithUrl( - data.query, + const { stop: stopSyncingQueryServiceStateWithUrl } = syncGlobalQueryStateWithUrl( + query, kbnUrlStateStorage ); if (title) { - attemptLoadDashboardByTitle(title, savedObjectsClient).then((result) => { + attemptLoadDashboardByTitle(title).then((result) => { if (!result) return; redirectTo({ destination: 'dashboard', @@ -112,22 +116,15 @@ export const DashboardListing = ({ return () => { stopSyncingQueryServiceStateWithUrl(); }; - }, [title, savedObjectsClient, redirectTo, data.query, kbnUrlStateStorage]); + }, [title, client, redirectTo, query, kbnUrlStateStorage]); - const { showWriteControls } = dashboardCapabilities; - const listingLimit = core.uiSettings.get(SAVED_OBJECTS_LIMIT_SETTING); - const initialPageSize = core.uiSettings.get(SAVED_OBJECTS_PER_PAGE_SETTING); + const listingLimit = uiSettings.get(SAVED_OBJECTS_LIMIT_SETTING); + const initialPageSize = uiSettings.get(SAVED_OBJECTS_PER_PAGE_SETTING); const defaultFilter = title ? `"${title}"` : ''; const tableColumns = useMemo( - () => - getTableColumns( - core.application, - kbnUrlStateStorage, - core.uiSettings.get('state:storeInSessionStorage'), - savedObjectsTagging - ), - [core.application, core.uiSettings, kbnUrlStateStorage, savedObjectsTagging] + () => getTableColumns(kbnUrlStateStorage, uiSettings.get('state:storeInSessionStorage')), + [uiSettings, kbnUrlStateStorage] ); const createItem = useCallback(() => { @@ -135,8 +132,6 @@ export const DashboardListing = ({ redirectTo({ destination: 'dashboard' }); } else { confirmCreateWithUnsaved( - core.overlays, - core.theme, () => { dashboardSessionStorage.clearState(); redirectTo({ destination: 'dashboard' }); @@ -144,7 +139,7 @@ export const DashboardListing = ({ () => redirectTo({ destination: 'dashboard' }) ); } - }, [dashboardSessionStorage, redirectTo, core.overlays, core.theme]); + }, [dashboardSessionStorage, redirectTo]); const emptyPrompt = useMemo(() => { if (!showWriteControls) { @@ -170,7 +165,7 @@ export const DashboardListing = ({ size="s" color="danger" onClick={() => - confirmDiscardUnsavedChanges(core.overlays, () => { + confirmDiscardUnsavedChanges(() => { dashboardSessionStorage.clearState(DASHBOARD_PANELS_UNSAVED_ID); setUnsavedDashboardIds(dashboardSessionStorage.getDashboardIdsWithUnsavedChanges()); }) @@ -222,7 +217,7 @@ export const DashboardListing = ({ sampleDataInstallLink: ( - core.application.navigateToApp('home', { + application.navigateToApp('home', { path: '#/tutorial_directory/sampleData', }) } @@ -242,8 +237,7 @@ export const DashboardListing = ({ }, [ redirectTo, createItem, - core.overlays, - core.application, + application, showWriteControls, unsavedDashboardIds, dashboardSessionStorage, @@ -253,8 +247,8 @@ export const DashboardListing = ({ (filter: string) => { let searchTerm = filter; let references: SavedObjectsFindOptionsReference[] | undefined; - if (savedObjectsTagging) { - const parsed = savedObjectsTagging.ui.parseSearchQuery(filter, { + if (parseSearchQuery) { + const parsed = parseSearchQuery(filter, { useName: true, }); searchTerm = parsed.searchTerm; @@ -266,7 +260,7 @@ export const DashboardListing = ({ hasReference: references, }); }, - [listingLimit, savedDashboards, savedObjectsTagging] + [listingLimit, savedDashboards, parseSearchQuery] ); const deleteItems = useCallback( @@ -285,10 +279,9 @@ export const DashboardListing = ({ ); const searchFilters = useMemo(() => { - return savedObjectsTagging - ? [savedObjectsTagging.ui.getSearchBarFilter({ useName: true })] - : []; - }, [savedObjectsTagging]); + const searchBarFilter = getSearchBarFilter?.({ useName: true }); + return searchBarFilter ? [searchBarFilter] : []; + }, [getSearchBarFilter]); const { getEntityName, getTableCaption, getTableListTitle, getEntityNamePlural } = dashboardListingTable; @@ -304,7 +297,7 @@ export const DashboardListing = ({ initialPageSize={initialPageSize} editItem={!showWriteControls ? undefined : editItem} initialFilter={initialFilter ?? defaultFilter} - toastNotifications={core.notifications.toasts} + toastNotifications={toasts} headingId="dashboardListingHeading" findItems={fetchItems} rowHeader="title" @@ -318,8 +311,9 @@ export const DashboardListing = ({ listingLimit, tableColumns, }} - theme={core.theme} - application={core.application} + theme={theme} + // The below type conversion is necessary until the TableListView component allows partial services + application={application as unknown as ApplicationStart} > { +const getTableColumns = (kbnUrlStateStorage: IKbnUrlStateStorage, useHash: boolean) => { + const { + savedObjectsTagging: { getTableColumnDefinition }, + } = pluginServices.getServices(); + const tableColumnDefinition = getTableColumnDefinition?.(); + return [ { field: 'title', @@ -348,7 +342,6 @@ const getTableColumns = ( render: (field: string, record: { id: string; title: string; timeRestore: boolean }) => ( {record.description}, sortable: true, }, - ...(savedObjectsTagging ? [savedObjectsTagging.ui.getTableColumnDefinition()] : []), + ...(tableColumnDefinition ? [tableColumnDefinition] : []), ] as unknown as Array>>; }; diff --git a/src/plugins/dashboard/public/application/listing/dashboard_no_match.tsx b/src/plugins/dashboard/public/application/listing/dashboard_no_match.tsx index df7e9bc21e46d..03e87f1a344d7 100644 --- a/src/plugins/dashboard/public/application/listing/dashboard_no_match.tsx +++ b/src/plugins/dashboard/public/application/listing/dashboard_no_match.tsx @@ -6,24 +6,33 @@ * Side Public License, v 1. */ -import { i18n } from '@kbn/i18n'; import React, { useEffect } from 'react'; +import { RouteComponentProps } from 'react-router-dom'; + +import { i18n } from '@kbn/i18n'; import { EuiCallOut } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import { RouteComponentProps } from 'react-router-dom'; +import { toMountPoint } from '@kbn/kibana-react-plugin/public'; -import { useKibana, toMountPoint } from '../../services/kibana_react'; -import { DashboardAppServices } from '../../types'; import { DashboardConstants } from '../..'; +import { pluginServices } from '../../services/plugin_services'; +import { useDashboardMountContext } from '../hooks/dashboard_mount_context'; let bannerId: string | undefined; export const DashboardNoMatch = ({ history }: { history: RouteComponentProps['history'] }) => { - const { services } = useKibana(); + const { restorePreviousUrl } = useDashboardMountContext(); + const { + settings: { + theme: { theme$ }, + }, + overlays: { banners }, + urlForwarding: { navigateToLegacyKibanaUrl }, + } = pluginServices.getServices(); useEffect(() => { - services.restorePreviousUrl(); - const { navigated } = services.urlForwarding.navigateToLegacyKibanaUrl( + restorePreviousUrl(); + const { navigated } = navigateToLegacyKibanaUrl( history.location.pathname + history.location.search ); @@ -32,7 +41,7 @@ export const DashboardNoMatch = ({ history }: { history: RouteComponentProps['hi defaultMessage: 'Page not found', }); - bannerId = services.core.overlays.banners.replace( + bannerId = banners.replace( bannerId, toMountPoint( @@ -46,20 +55,20 @@ export const DashboardNoMatch = ({ history }: { history: RouteComponentProps['hi />

, - { theme$: services.core.theme.theme$ } + { theme$ } ) ); // hide the message after the user has had a chance to acknowledge it -- so it doesn't permanently stick around setTimeout(() => { if (bannerId) { - services.core.overlays.banners.remove(bannerId); + banners.remove(bannerId); } }, 15000); history.replace(DashboardConstants.LANDING_PAGE_PATH); } - }, [services, history]); + }, [restorePreviousUrl, navigateToLegacyKibanaUrl, banners, theme$, history]); return null; }; diff --git a/src/plugins/dashboard/public/application/listing/dashboard_unsaved_listing.test.tsx b/src/plugins/dashboard/public/application/listing/dashboard_unsaved_listing.test.tsx index 685d090c20459..1feec9bbdc42f 100644 --- a/src/plugins/dashboard/public/application/listing/dashboard_unsaved_listing.test.tsx +++ b/src/plugins/dashboard/public/application/listing/dashboard_unsaved_listing.test.tsx @@ -5,20 +5,21 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ +import React from 'react'; +import { mount } from 'enzyme'; import { I18nProvider } from '@kbn/i18n-react'; -import { findTestSubject } from '@elastic/eui/lib/test'; import { waitFor } from '@testing-library/react'; -import { mount } from 'enzyme'; -import React from 'react'; +import { findTestSubject } from '@elastic/eui/lib/test'; +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { DashboardSavedObject } from '../..'; import { DashboardAppServices } from '../../types'; -import { SavedObjectLoader } from '../../services/saved_objects'; -import { KibanaContextProvider } from '../../services/kibana_react'; -import { DASHBOARD_PANELS_UNSAVED_ID } from '../lib/dashboard_session_storage'; -import { DashboardUnsavedListing, DashboardUnsavedListingProps } from './dashboard_unsaved_listing'; import { makeDefaultServices } from '../test_helpers'; +import { SavedObjectLoader } from '../../services/saved_object_loader'; +import { DashboardUnsavedListing, DashboardUnsavedListingProps } from './dashboard_unsaved_listing'; +import { DASHBOARD_PANELS_UNSAVED_ID } from '../../services/dashboard_session_storage/dashboard_session_storage_service'; +import { pluginServices } from '../../services/plugin_services'; const mockedDashboards: { [key: string]: DashboardSavedObject } = { dashboardUnsavedOne: { @@ -67,6 +68,8 @@ function mountWith({ }> = ({ children }) => { return ( + {/* Only the old savedObjects service is used for `DashboardUnsavedListing`, so will need to wrap this in + `DashboardServicesProvider` instead once that is removed as part of https://github.com/elastic/kibana/pull/138774*/} {children} ); @@ -125,7 +128,7 @@ describe('Unsaved listing', () => { }); it('Shows a warning then clears changes when delete unsaved changes is pressed', async () => { - const { services, component } = mountWith({}); + const { component } = mountWith({}); const getDiscardButton = () => findTestSubject(component, 'discard-unsaved-Dashboard-Unsaved-One'); await waitFor(() => { @@ -135,8 +138,8 @@ describe('Unsaved listing', () => { getDiscardButton().simulate('click'); waitFor(() => { component.update(); - expect(services.core.overlays.openConfirm).toHaveBeenCalled(); - expect(services.dashboardSessionStorage.clearState).toHaveBeenCalledWith( + expect(pluginServices.getServices().overlays.openConfirm).toHaveBeenCalled(); + expect(pluginServices.getServices().dashboardSessionStorage.clearState).toHaveBeenCalledWith( 'dashboardUnsavedOne' ); }); @@ -162,12 +165,16 @@ describe('Unsaved listing', () => { const { component } = mountWith({ services, props }); waitFor(() => { component.update(); - expect(services.dashboardSessionStorage.clearState).toHaveBeenCalledWith('failCase1'); - expect(services.dashboardSessionStorage.clearState).toHaveBeenCalledWith('failCase2'); + expect(pluginServices.getServices().dashboardSessionStorage.clearState).toHaveBeenCalledWith( + 'failCase1' + ); + expect(pluginServices.getServices().dashboardSessionStorage.clearState).toHaveBeenCalledWith( + 'failCase2' + ); // clearing panels from dashboard with errors should cause getDashboardIdsWithUnsavedChanges to be called again. expect( - services.dashboardSessionStorage.getDashboardIdsWithUnsavedChanges + pluginServices.getServices().dashboardSessionStorage.getDashboardIdsWithUnsavedChanges ).toHaveBeenCalledTimes(2); }); }); diff --git a/src/plugins/dashboard/public/application/listing/dashboard_unsaved_listing.tsx b/src/plugins/dashboard/public/application/listing/dashboard_unsaved_listing.tsx index acff60d9bac3d..a5c5b1b224a32 100644 --- a/src/plugins/dashboard/public/application/listing/dashboard_unsaved_listing.tsx +++ b/src/plugins/dashboard/public/application/listing/dashboard_unsaved_listing.tsx @@ -6,6 +6,8 @@ * Side Public License, v 1. */ +import React, { useCallback, useEffect, useState } from 'react'; + import { EuiButtonEmpty, EuiCallOut, @@ -15,13 +17,14 @@ import { EuiSpacer, EuiTitle, } from '@elastic/eui'; -import React, { useCallback, useEffect, useState } from 'react'; -import { DashboardSavedObject } from '../..'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; + +import type { DashboardSavedObject } from '../..'; import { dashboardUnsavedListingStrings, getNewDashboardTitle } from '../../dashboard_strings'; -import { useKibana } from '../../services/kibana_react'; -import { DASHBOARD_PANELS_UNSAVED_ID } from '../lib/dashboard_session_storage'; -import { DashboardAppServices, DashboardRedirect } from '../../types'; +import type { DashboardAppServices, DashboardRedirect } from '../../types'; import { confirmDiscardUnsavedChanges } from './confirm_overlays'; +import { pluginServices } from '../../services/plugin_services'; +import { DASHBOARD_PANELS_UNSAVED_ID } from '../../services/dashboard_session_storage/dashboard_session_storage_service'; const DashboardUnsavedItem = ({ id, @@ -114,13 +117,11 @@ export const DashboardUnsavedListing = ({ refreshUnsavedDashboards, }: DashboardUnsavedListingProps) => { const { - services: { - dashboardSessionStorage, - savedDashboards, - core: { overlays }, - }, + services: { savedDashboards }, } = useKibana(); + const { dashboardSessionStorage } = pluginServices.getServices(); + const [items, setItems] = useState({}); const onOpen = useCallback( @@ -132,12 +133,12 @@ export const DashboardUnsavedListing = ({ const onDiscard = useCallback( (id?: string) => { - confirmDiscardUnsavedChanges(overlays, () => { + confirmDiscardUnsavedChanges(() => { dashboardSessionStorage.clearState(id); refreshUnsavedDashboards(); }); }, - [overlays, refreshUnsavedDashboards, dashboardSessionStorage] + [refreshUnsavedDashboards, dashboardSessionStorage] ); useEffect(() => { diff --git a/src/plugins/dashboard/public/application/listing/get_dashboard_list_item_link.test.ts b/src/plugins/dashboard/public/application/listing/get_dashboard_list_item_link.test.ts index 2b45977eaafed..31ebeb9e93c9b 100644 --- a/src/plugins/dashboard/public/application/listing/get_dashboard_list_item_link.test.ts +++ b/src/plugins/dashboard/public/application/listing/get_dashboard_list_item_link.test.ts @@ -7,7 +7,6 @@ */ import { getDashboardListItemLink } from './get_dashboard_list_item_link'; -import { ApplicationStart } from '@kbn/core/public'; import { createHashHistory } from 'history'; import { FilterStateStore } from '@kbn/es-query'; import { createKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public'; @@ -15,12 +14,6 @@ import { GLOBAL_STATE_STORAGE_KEY } from '../../dashboard_constants'; const DASHBOARD_ID = '13823000-99b9-11ea-9eb6-d9e8adceb647'; -const application = { - getUrlForApp: jest.fn((appId: string, options?: { path?: string; absolute?: boolean }) => { - return `/app/${appId}${options?.path}`; - }), -} as unknown as ApplicationStart; - const history = createHashHistory(); const kbnUrlStateStorage = createKbnUrlStateStorage({ history, @@ -30,27 +23,13 @@ kbnUrlStateStorage.set(GLOBAL_STATE_STORAGE_KEY, { time: { from: 'now-7d', to: ' describe('listing dashboard link', () => { test('creates a link to a dashboard without the timerange query if time is saved on the dashboard', async () => { - const url = getDashboardListItemLink( - application, - kbnUrlStateStorage, - false, - DASHBOARD_ID, - true - ); - expect(url).toMatchInlineSnapshot(`"/app/dashboards#/view/${DASHBOARD_ID}?_g=()"`); + const url = getDashboardListItemLink(kbnUrlStateStorage, false, DASHBOARD_ID, true); + expect(url).toMatchInlineSnapshot(`"http://localhost/#/?_g=()"`); }); test('creates a link to a dashboard with the timerange query if time is not saved on the dashboard', async () => { - const url = getDashboardListItemLink( - application, - kbnUrlStateStorage, - false, - DASHBOARD_ID, - false - ); - expect(url).toMatchInlineSnapshot( - `"/app/dashboards#/view/${DASHBOARD_ID}?_g=(time:(from:now-7d,to:now))"` - ); + const url = getDashboardListItemLink(kbnUrlStateStorage, false, DASHBOARD_ID, false); + expect(url).toMatchInlineSnapshot(`"http://localhost/#/?_g=(time:(from:now-7d,to:now))"`); }); }); @@ -65,15 +44,9 @@ describe('when global time changes', () => { }); test('propagates the correct time on the query', async () => { - const url = getDashboardListItemLink( - application, - kbnUrlStateStorage, - false, - DASHBOARD_ID, - false - ); + const url = getDashboardListItemLink(kbnUrlStateStorage, false, DASHBOARD_ID, false); expect(url).toMatchInlineSnapshot( - `"/app/dashboards#/view/${DASHBOARD_ID}?_g=(time:(from:'2021-01-05T11:45:53.375Z',to:'2021-01-21T11:46:00.990Z'))"` + `"http://localhost/#/?_g=(time:(from:'2021-01-05T11:45:53.375Z',to:'2021-01-21T11:46:00.990Z'))"` ); }); }); @@ -86,15 +59,9 @@ describe('when global refreshInterval changes', () => { }); test('propagates the refreshInterval on the query', async () => { - const url = getDashboardListItemLink( - application, - kbnUrlStateStorage, - false, - DASHBOARD_ID, - false - ); + const url = getDashboardListItemLink(kbnUrlStateStorage, false, DASHBOARD_ID, false); expect(url).toMatchInlineSnapshot( - `"/app/dashboards#/view/${DASHBOARD_ID}?_g=(refreshInterval:(pause:!f,value:300))"` + `"http://localhost/#/?_g=(refreshInterval:(pause:!f,value:300))"` ); }); }); @@ -128,15 +95,9 @@ describe('when global filters change', () => { }); test('propagates the filters on the query', async () => { - const url = getDashboardListItemLink( - application, - kbnUrlStateStorage, - false, - DASHBOARD_ID, - false - ); + const url = getDashboardListItemLink(kbnUrlStateStorage, false, DASHBOARD_ID, false); expect(url).toMatchInlineSnapshot( - `"/app/dashboards#/view/${DASHBOARD_ID}?_g=(filters:!((meta:(alias:!n,disabled:!f,negate:!f),query:(query:q1)),('$state':(store:globalState),meta:(alias:!n,disabled:!f,negate:!f),query:(query:q1))))"` + `"http://localhost/#/?_g=(filters:!((meta:(alias:!n,disabled:!f,negate:!f),query:(query:q1)),('$state':(store:globalState),meta:(alias:!n,disabled:!f,negate:!f),query:(query:q1))))"` ); }); }); diff --git a/src/plugins/dashboard/public/application/listing/get_dashboard_list_item_link.ts b/src/plugins/dashboard/public/application/listing/get_dashboard_list_item_link.ts index 1589c3dd3fa86..39106f12551bc 100644 --- a/src/plugins/dashboard/public/application/listing/get_dashboard_list_item_link.ts +++ b/src/plugins/dashboard/public/application/listing/get_dashboard_list_item_link.ts @@ -6,24 +6,27 @@ * Side Public License, v 1. */ -import { ApplicationStart } from '@kbn/core/public'; -import { QueryState } from '@kbn/data-plugin/public'; +import type { QueryState } from '@kbn/data-plugin/public'; import { setStateToKbnUrl } from '@kbn/kibana-utils-plugin/public'; +import { IKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public'; import { DashboardConstants, createDashboardEditUrl, GLOBAL_STATE_STORAGE_KEY, } from '../../dashboard_constants'; -import { IKbnUrlStateStorage } from '../../services/kibana_utils'; +import { pluginServices } from '../../services/plugin_services'; export const getDashboardListItemLink = ( - application: ApplicationStart, kbnUrlStateStorage: IKbnUrlStateStorage, useHash: boolean, id: string, timeRestore: boolean ) => { - let url = application.getUrlForApp(DashboardConstants.DASHBOARDS_ID, { + const { + application: { getUrlForApp }, + } = pluginServices.getServices(); + + let url = getUrlForApp(DashboardConstants.DASHBOARDS_ID, { path: `#${createDashboardEditUrl(id)}`, }); const globalStateInUrl = kbnUrlStateStorage.get(GLOBAL_STATE_STORAGE_KEY) || {}; diff --git a/src/plugins/dashboard/public/application/state/dashboard_state_slice.ts b/src/plugins/dashboard/public/application/state/dashboard_state_slice.ts index 07ae72acada70..f28d095c9b9b4 100644 --- a/src/plugins/dashboard/public/application/state/dashboard_state_slice.ts +++ b/src/plugins/dashboard/public/application/state/dashboard_state_slice.ts @@ -8,10 +8,10 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { PersistableControlGroupInput } from '@kbn/controls-plugin/common'; +import { ViewMode } from '@kbn/embeddable-plugin/public'; -import { Filter, Query, TimeRange } from '../../services/data'; -import { ViewMode } from '../../services/embeddable'; -import { DashboardOptions, DashboardPanelMap, DashboardState } from '../../types'; +import type { Filter, Query, TimeRange } from '@kbn/es-query'; +import type { DashboardOptions, DashboardPanelMap, DashboardState } from '../../types'; export const dashboardStateSlice = createSlice({ name: 'dashboardState', diff --git a/src/plugins/dashboard/public/application/test_helpers/get_sample_dashboard_input.ts b/src/plugins/dashboard/public/application/test_helpers/get_sample_dashboard_input.ts index 29e4e7aa8ee1a..e782bf8fe81cc 100644 --- a/src/plugins/dashboard/public/application/test_helpers/get_sample_dashboard_input.ts +++ b/src/plugins/dashboard/public/application/test_helpers/get_sample_dashboard_input.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ +import { ViewMode, EmbeddableInput } from '@kbn/embeddable-plugin/public'; import { DashboardContainerInput } from '../..'; import { DashboardPanelState } from '../embeddable'; -import { ViewMode, EmbeddableInput } from '../../services/embeddable'; export function getSampleDashboardInput( overrides?: Partial diff --git a/src/plugins/dashboard/public/application/test_helpers/make_default_services.ts b/src/plugins/dashboard/public/application/test_helpers/make_default_services.ts index 5d93cbcfa4f0d..2712c92888bf3 100644 --- a/src/plugins/dashboard/public/application/test_helpers/make_default_services.ts +++ b/src/plugins/dashboard/public/application/test_helpers/make_default_services.ts @@ -6,28 +6,15 @@ * Side Public License, v 1. */ -import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; -import { UrlForwardingStart } from '@kbn/url-forwarding-plugin/public'; -import { embeddablePluginMock } from '@kbn/embeddable-plugin/public/mocks'; -import { PluginInitializerContext, ScopedHistory } from '@kbn/core/public'; -import { savedObjectsPluginMock } from '@kbn/saved-objects-plugin/public/mocks'; -import { visualizationsPluginMock } from '@kbn/visualizations-plugin/public/mocks'; -import { screenshotModePluginMock } from '@kbn/screenshot-mode-plugin/public/mocks'; -import { indexPatternEditorPluginMock } from '@kbn/data-view-editor-plugin/public/mocks'; -import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; - -import { chromeServiceMock, coreMock, uiSettingsServiceMock } from '@kbn/core/public/mocks'; -import { SavedObjectLoader, SavedObjectLoaderFindOptions } from '../../services/saved_objects'; -import { SavedQueryService } from '../../services/data'; -import { DashboardAppServices, DashboardAppCapabilities } from '../../types'; -import { NavigationPublicPluginStart } from '../../services/navigation'; +import { + SavedObjectLoader, + type SavedObjectLoaderFindOptions, +} from '../../services/saved_object_loader'; +import { DashboardAppServices } from '../../types'; import { getSavedDashboardMock } from './get_saved_dashboard_mock'; -import { DashboardSessionStorage } from '../lib'; +// TODO: Remove as part of https://github.com/elastic/kibana/pull/138774 export function makeDefaultServices(): DashboardAppServices { - const core = coreMock.createStart(); - core.overlays.openConfirm = jest.fn().mockResolvedValue(true); - const savedDashboards = {} as SavedObjectLoader; savedDashboards.find = (search: string, sizeOrOptions: number | SavedObjectLoaderFindOptions) => { const size = typeof sizeOrOptions === 'number' ? sizeOrOptions : sizeOrOptions.size ?? 10; @@ -48,52 +35,7 @@ export function makeDefaultServices(): DashboardAppServices { .fn() .mockImplementation((id?: string) => Promise.resolve(getSavedDashboardMock({ id }))); - const dashboardSessionStorage = { - getDashboardIdsWithUnsavedChanges: jest - .fn() - .mockResolvedValue(['dashboardUnsavedOne', 'dashboardUnsavedTwo']), - getState: jest.fn().mockReturnValue(undefined), - setState: jest.fn(), - } as unknown as DashboardSessionStorage; - dashboardSessionStorage.clearState = jest.fn(); - - const defaultCapabilities: DashboardAppCapabilities = { - show: true, - createNew: true, - saveQuery: true, - createShortUrl: true, - showWriteControls: true, - storeSearchSession: true, - mapsCapabilities: { save: true }, - visualizeCapabilities: { save: true }, - }; - const initializerContext = { - env: { packageInfo: { version: '8.0.0' } }, - } as PluginInitializerContext; - return { - screenshotModeService: screenshotModePluginMock.createSetupContract(), - dataViewEditor: indexPatternEditorPluginMock.createStartContract(), - visualizations: visualizationsPluginMock.createStartContract(), - savedObjects: savedObjectsPluginMock.createStartContract(), - embeddable: embeddablePluginMock.createInstance().doStart(), - uiSettings: uiSettingsServiceMock.createStartContract(), - dataViews: dataViewPluginMocks.createStartContract(), - chrome: chromeServiceMock.createStartContract(), - navigation: {} as NavigationPublicPluginStart, - savedObjectsClient: core.savedObjects.client, - dashboardCapabilities: defaultCapabilities, - data: dataPluginMock.createStartContract(), - savedQueryService: {} as SavedQueryService, - scopedHistory: () => ({} as ScopedHistory), - setHeaderActionMenu: (mountPoint) => {}, - urlForwarding: {} as UrlForwardingStart, - allowByValueEmbeddables: true, - restorePreviousUrl: () => {}, - onAppLeave: (handler) => {}, - dashboardSessionStorage, - initializerContext, savedDashboards, - core, }; } diff --git a/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx b/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx index 031273ea06de3..86fd56c2ec6a0 100644 --- a/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx +++ b/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx @@ -12,9 +12,9 @@ import { EuiHorizontalRule } from '@elastic/eui'; import UseUnmount from 'react-use/lib/useUnmount'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { OverlayRef } from '@kbn/core/public'; -import { TopNavMenuProps } from '@kbn/navigation-plugin/public'; -import { BaseVisType, VisTypeAlias } from '@kbn/visualizations-plugin/public'; +import type { OverlayRef } from '@kbn/core/public'; +import type { TopNavMenuProps } from '@kbn/navigation-plugin/public'; +import type { BaseVisType, VisTypeAlias } from '@kbn/visualizations-plugin/public'; import { AddFromLibraryButton, LazyLabsFlyout, @@ -24,23 +24,27 @@ import { SolutionToolbar, withSuspense, } from '@kbn/presentation-util-plugin/public'; +import type { SavedQuery } from '@kbn/data-plugin/common'; +import { isErrorEmbeddable, openAddPanelFlyout, ViewMode } from '@kbn/embeddable-plugin/public'; +import { + getSavedObjectFinder, + type SaveResult, + showSaveModal, +} from '@kbn/saved-objects-plugin/public'; + import { saveDashboard } from '../lib'; import { TopNavIds } from './top_nav_ids'; import { EditorMenu } from './editor_menu'; import { UI_SETTINGS } from '../../../common'; -import { SavedQuery } from '../../services/data'; import { DashboardSaveModal } from './save_modal'; import { showCloneModal } from './show_clone_modal'; import { ShowShareModal } from './show_share_modal'; import { getTopNavConfig } from './get_top_nav_config'; -import { useKibana } from '../../services/kibana_react'; import { showOptionsPopover } from './show_options_popover'; import { DashboardConstants } from '../../dashboard_constants'; import { confirmDiscardUnsavedChanges } from '../listing/confirm_overlays'; -import { DashboardAppState, DashboardSaveOptions, NavAction } from '../../types'; -import { isErrorEmbeddable, openAddPanelFlyout, ViewMode } from '../../services/embeddable'; -import { DashboardAppServices, DashboardEmbedSettings, DashboardRedirect } from '../../types'; -import { getSavedObjectFinder, SaveResult, showSaveModal } from '../../services/saved_objects'; +import type { DashboardAppState, DashboardSaveOptions, NavAction } from '../../types'; +import type { DashboardEmbedSettings, DashboardRedirect } from '../../types'; import { getCreateVisualizationButtonTitle, unsavedChangesBadge } from '../../dashboard_strings'; import { setFullScreenMode, @@ -54,6 +58,8 @@ import { useDashboardDispatch, useDashboardSelector, } from '../state'; +import { pluginServices } from '../../services/plugin_services'; +import { useDashboardMountContext } from '../hooks/dashboard_mount_context'; export interface DashboardTopNavState { chromeIsVisible: boolean; @@ -93,28 +99,28 @@ export function DashboardTopNav({ redirectTo, printMode, }: DashboardTopNavProps) { + const { setHeaderActionMenu } = useDashboardMountContext(); const { - core, - data, + chrome: { + getIsVisible$: getChromeIsVisible$, + recentlyAccessed: chromeRecentlyAccessed, + docTitle, + }, + coreContext: { i18nContext }, + dashboardCapabilities, + data: { query, search }, + embeddable: { getEmbeddableFactory, getEmbeddableFactories, getStateTransfer }, + initializerContext: { allowByValueEmbeddables }, + navigation: { TopNavMenu }, + notifications, + overlays, + savedObjects, + savedObjectsTagging: { hasTagDecoration, hasApi }, + settings: { uiSettings, theme }, share, - chrome, - embeddable, - navigation, - uiSettings, - visualizations, usageCollection, - initializerContext, - savedObjectsTagging, - setHeaderActionMenu, - dashboardCapabilities, - dashboardSessionStorage, - allowByValueEmbeddables, - } = useKibana().services; - const { version: kibanaVersion } = initializerContext.env.packageInfo; - const timefilter = data.query.timefilter.timefilter; - const { notifications, theme } = core; - const { toasts } = notifications; - const { theme$ } = theme; + visualizations: { get: getVisualization, getAliases: getVisTypeAliases }, + } = pluginServices.getServices(); const dispatchDashboardStateChange = useDashboardDispatch(); const dashboardState = useDashboardSelector((state) => state.dashboardStateReducer); @@ -123,24 +129,24 @@ export function DashboardTopNav({ const [state, setState] = useState({ chromeIsVisible: false }); const [isLabsShown, setIsLabsShown] = useState(false); - const lensAlias = visualizations.getAliases().find(({ name }) => name === 'lens'); + const lensAlias = getVisTypeAliases().find(({ name }) => name === 'lens'); const quickButtonVisTypes = ['markdown', 'maps']; - const stateTransferService = embeddable.getStateTransfer(); + const stateTransferService = getStateTransfer(); const IS_DARK_THEME = uiSettings.get('theme:darkMode'); const isLabsEnabled = uiSettings.get(UI_SETTINGS.ENABLE_LABS_UI); - const trackUiMetric = usageCollection?.reportUiCounter.bind( + const trackUiMetric = usageCollection.reportUiCounter?.bind( usageCollection, DashboardConstants.DASHBOARD_ID ); useEffect(() => { - const visibleSubscription = chrome.getIsVisible$().subscribe((chromeIsVisible) => { + const visibleSubscription = getChromeIsVisible$().subscribe((chromeIsVisible) => { setState((s) => ({ ...s, chromeIsVisible })); }); const { id, title, getFullEditPath } = dashboardAppState.savedDashboard; if (id && title) { - chrome.recentlyAccessed.add( + chromeRecentlyAccessed.add( getFullEditPath(dashboardState.viewMode === ViewMode.EDIT), title, id @@ -149,7 +155,13 @@ export function DashboardTopNav({ return () => { visibleSubscription.unsubscribe(); }; - }, [chrome, allowByValueEmbeddables, dashboardState.viewMode, dashboardAppState.savedDashboard]); + }, [ + getChromeIsVisible$, + chromeRecentlyAccessed, + allowByValueEmbeddables, + dashboardState.viewMode, + dashboardAppState.savedDashboard, + ]); const addFromLibrary = useCallback(() => { if (!isErrorEmbeddable(dashboardAppState.dashboardContainer)) { @@ -157,24 +169,24 @@ export function DashboardTopNav({ ...s, addPanelOverlay: openAddPanelFlyout({ embeddable: dashboardAppState.dashboardContainer, - getAllFactories: embeddable.getEmbeddableFactories, - getFactory: embeddable.getEmbeddableFactory, - notifications: core.notifications, - overlays: core.overlays, - SavedObjectFinder: getSavedObjectFinder(core.savedObjects, uiSettings), - reportUiCounter: usageCollection?.reportUiCounter, - theme: core.theme, + getAllFactories: getEmbeddableFactories, + getFactory: getEmbeddableFactory, + notifications, + overlays, + SavedObjectFinder: getSavedObjectFinder(savedObjects, uiSettings), + reportUiCounter: usageCollection.reportUiCounter, + theme, }), })); } }, [ dashboardAppState.dashboardContainer, - embeddable.getEmbeddableFactories, - embeddable.getEmbeddableFactory, - core.notifications, - core.savedObjects, - core.overlays, - core.theme, + getEmbeddableFactories, + getEmbeddableFactory, + notifications, + savedObjects, + overlays, + theme, uiSettings, usageCollection, ]); @@ -205,11 +217,11 @@ export function DashboardTopNav({ path, state: { originatingApp: DashboardConstants.DASHBOARDS_ID, - searchSessionId: data.search.session.getSessionId(), + searchSessionId: search.session.getSessionId(), }, }); }, - [stateTransferService, data.search.session, trackUiMetric] + [stateTransferService, search.session, trackUiMetric] ); const closeAllFlyouts = useCallback(() => { @@ -230,11 +242,9 @@ export function DashboardTopNav({ return; } - confirmDiscardUnsavedChanges(core.overlays, () => - dashboardAppState.resetToLastSavedState?.() - ); + confirmDiscardUnsavedChanges(() => dashboardAppState.resetToLastSavedState?.()); }, - [closeAllFlyouts, core.overlays, dashboardAppState, dispatchDashboardStateChange] + [closeAllFlyouts, dashboardAppState, dispatchDashboardStateChange] ); const runSaveAs = useCallback(async () => { @@ -259,32 +269,28 @@ export function DashboardTopNav({ timeRestore: newTimeRestore, tags: [] as string[], }; - if (savedObjectsTagging && newTags) { + if (hasApi && newTags) { + // remove `hasAPI` once the savedObjectsTagging service is optional stateFromSaveModal.tags = newTags; } dashboardAppState.savedDashboard.copyOnSave = newCopyOnSave; const saveResult = await saveDashboard({ - toasts, - timefilter, redirectTo, saveOptions, - savedObjectsTagging, - version: kibanaVersion, - dashboardSessionStorage, savedDashboard: dashboardAppState.savedDashboard, currentState: { ...currentState, ...stateFromSaveModal }, }); if (saveResult.id && !saveResult.redirected) { dispatchDashboardStateChange(setStateFromSaveModal(stateFromSaveModal)); dashboardAppState.updateLastSavedState?.(); - chrome.docTitle.change(stateFromSaveModal.title); + docTitle.change(stateFromSaveModal.title); } return saveResult.id ? { id: saveResult.id } : { error: saveResult.error }; }; const lastDashboardId = dashboardAppState.savedDashboard.id; - const savedTags = savedObjectsTagging?.ui.hasTagDecoration(dashboardAppState.savedDashboard) + const savedTags = hasTagDecoration?.(dashboardAppState.savedDashboard) ? dashboardAppState.savedDashboard.getTags() : []; const currentTagsSet = new Set([...savedTags, ...currentState.tags]); @@ -297,38 +303,29 @@ export function DashboardTopNav({ title={currentState.title} timeRestore={currentState.timeRestore} description={currentState.description} - savedObjectsTagging={savedObjectsTagging} showCopyOnSave={lastDashboardId ? true : false} /> ); closeAllFlyouts(); - showSaveModal(dashboardSaveModal, core.i18n.Context); + showSaveModal(dashboardSaveModal, i18nContext); }, [ dispatchDashboardStateChange, - dashboardSessionStorage, - savedObjectsTagging, + hasApi, + hasTagDecoration, dashboardAppState, - core.i18n.Context, - chrome.docTitle, + i18nContext, + docTitle, closeAllFlyouts, - kibanaVersion, - timefilter, redirectTo, - toasts, ]); const runQuickSave = useCallback(async () => { setState((s) => ({ ...s, isSaveInProgress: true })); const currentState = dashboardAppState.getLatestDashboardState(); const saveResult = await saveDashboard({ - toasts, - timefilter, redirectTo, currentState, saveOptions: {}, - savedObjectsTagging, - version: kibanaVersion, - dashboardSessionStorage, savedDashboard: dashboardAppState.savedDashboard, }); if (saveResult.id && !saveResult.redirected) { @@ -339,16 +336,7 @@ export function DashboardTopNav({ if (!mounted) return; setState((s) => ({ ...s, isSaveInProgress: false })); }, DashboardConstants.CHANGE_CHECK_DEBOUNCE); - }, [ - dashboardSessionStorage, - savedObjectsTagging, - dashboardAppState, - kibanaVersion, - timefilter, - redirectTo, - mounted, - toasts, - ]); + }, [dashboardAppState, redirectTo, mounted]); const runClone = useCallback(() => { const currentState = dashboardAppState.getLatestDashboardState(); @@ -364,29 +352,15 @@ export function DashboardTopNav({ onTitleDuplicate, }; const saveResult = await saveDashboard({ - toasts, - timefilter, redirectTo, saveOptions, - savedObjectsTagging, - version: kibanaVersion, - dashboardSessionStorage, savedDashboard: dashboardAppState.savedDashboard, currentState: { ...currentState, title: newTitle }, }); return saveResult.id ? { id: saveResult.id } : { error: saveResult.error }; }; - showCloneModal({ onClone, title: currentState.title, theme$ }); - }, [ - dashboardSessionStorage, - savedObjectsTagging, - dashboardAppState, - kibanaVersion, - redirectTo, - timefilter, - theme$, - toasts, - ]); + showCloneModal({ onClone, title: currentState.title }); + }, [dashboardAppState, redirectTo]); const showOptions = useCallback( (anchorElement: HTMLElement) => { @@ -409,37 +383,22 @@ export function DashboardTopNav({ onHidePanelTitlesChange: (isChecked: boolean) => { dispatchDashboardStateChange(setHidePanelTitles(isChecked)); }, - theme$, }); }, - [dashboardAppState, dispatchDashboardStateChange, theme$] + [dashboardAppState, dispatchDashboardStateChange] ); const showShare = useCallback( (anchorElement: HTMLElement) => { - if (!share) return; const currentState = dashboardAppState.getLatestDashboardState(); - const timeRange = timefilter.getTime(); ShowShareModal({ - share, - timeRange, - kibanaVersion, anchorElement, - dashboardCapabilities, - dashboardSessionStorage, currentDashboardState: currentState, savedDashboard: dashboardAppState.savedDashboard, isDirty: Boolean(dashboardAppState.hasUnsavedChanges), }); }, - [ - share, - timefilter, - kibanaVersion, - dashboardAppState, - dashboardCapabilities, - dashboardSessionStorage, - ] + [dashboardAppState] ); const dashboardTopNavActions = useMemo(() => { @@ -453,7 +412,8 @@ export function DashboardTopNav({ [TopNavIds.CLONE]: runClone, } as { [key: string]: NavAction }; - if (share) { + if (share !== {}) { + // TODO: Clean up this logic once share is optional actions[TopNavIds.SHARE] = showShare; } @@ -487,8 +447,7 @@ export function DashboardTopNav({ (forceShow || state.chromeIsVisible) && !dashboardState.fullScreenMode; const shouldShowFilterBar = (forceHide: boolean): boolean => - !forceHide && - (data.query.filterManager.getFilters().length > 0 || !dashboardState.fullScreenMode); + !forceHide && (query.filterManager.getFilters().length > 0 || !dashboardState.fullScreenMode); const isFullScreenMode = dashboardState.fullScreenMode; const showTopNavMenu = shouldShowNavBarComponent(Boolean(embedSettings?.forceShowTopNavMenu)); @@ -553,12 +512,9 @@ export function DashboardTopNav({ }; }; - const { TopNavMenu } = navigation.ui; - const getVisTypeQuickButton = (visTypeName: string) => { const visType = - visualizations.get(visTypeName) || - visualizations.getAliases().find(({ name }) => name === visTypeName); + getVisualization(visTypeName) || getVisTypeAliases().find(({ name }) => name === visTypeName); if (visType) { if ('aliasPath' in visType) { diff --git a/src/plugins/dashboard/public/application/top_nav/editor_menu.tsx b/src/plugins/dashboard/public/application/top_nav/editor_menu.tsx index 941a45cc6b956..1f17e0b6ef6a4 100644 --- a/src/plugins/dashboard/public/application/top_nav/editor_menu.tsx +++ b/src/plugins/dashboard/public/application/top_nav/editor_menu.tsx @@ -17,18 +17,18 @@ import { } from '@elastic/eui'; import { METRIC_TYPE } from '@kbn/analytics'; import { i18n } from '@kbn/i18n'; -import { BaseVisType, VisGroups, VisTypeAlias } from '@kbn/visualizations-plugin/public'; +import { type BaseVisType, VisGroups, type VisTypeAlias } from '@kbn/visualizations-plugin/public'; import { SolutionToolbarPopover } from '@kbn/presentation-util-plugin/public'; -import { +import type { EmbeddableFactory, EmbeddableFactoryDefinition, EmbeddableInput, -} from '../../services/embeddable'; -import { useKibana } from '../../services/kibana_react'; -import { DashboardAppServices } from '../../types'; +} from '@kbn/embeddable-plugin/public'; + import { DashboardContainer } from '..'; import { DashboardConstants } from '../../dashboard_constants'; import { dashboardReplacePanelAction } from '../../dashboard_strings'; +import { pluginServices } from '../../services/plugin_services'; interface Props { /** Dashboard container */ @@ -51,11 +51,20 @@ interface UnwrappedEmbeddableFactory { } export const EditorMenu = ({ dashboardContainer, createNewVisType }: Props) => { - const { core, embeddable, visualizations, usageCollection, uiSettings } = - useKibana().services; + const { + embeddable, + notifications: { toasts }, + settings: { uiSettings }, + usageCollection, + visualizations: { + getAliases: getVisTypeAliases, + getByGroup: getVisTypesByGroup, + showNewVisModal, + }, + } = pluginServices.getServices(); const embeddableFactories = useMemo( - () => (embeddable ? Array.from(embeddable.getEmbeddableFactories()) : []), + () => Array.from(embeddable.getEmbeddableFactories()), [embeddable] ); const [unwrappedEmbeddableFactories, setUnwrappedEmbeddableFactories] = useState< @@ -73,28 +82,26 @@ export const EditorMenu = ({ dashboardContainer, createNewVisType }: Props) => { }); }, [embeddableFactories]); - const IS_DARK_THEME = uiSettings.get('theme:darkMode'); const LABS_ENABLED = uiSettings.get('visualize:enableLabs'); - const trackUiMetric = usageCollection?.reportUiCounter.bind( + const trackUiMetric = usageCollection.reportUiCounter?.bind( usageCollection, DashboardConstants.DASHBOARD_ID ); const createNewAggsBasedVis = useCallback( (visType?: BaseVisType) => () => - visualizations.showNewVisModal({ + showNewVisModal({ originatingApp: DashboardConstants.DASHBOARDS_ID, outsideVisualizeApp: true, showAggsSelection: true, selectedVisType: visType, }), - [visualizations] + [showNewVisModal] ); - const getVisTypesByGroup = (group: VisGroups) => - visualizations - .getByGroup(group) + const getSortedVisTypesByGroup = (group: VisGroups) => + getVisTypesByGroup(group) .sort(({ name: a }: BaseVisType | VisTypeAlias, { name: b }: BaseVisType | VisTypeAlias) => { if (a < b) { return -1; @@ -108,14 +115,13 @@ export const EditorMenu = ({ dashboardContainer, createNewVisType }: Props) => { ({ hidden, stage }: BaseVisType) => !(hidden || (!LABS_ENABLED && stage === 'experimental')) ); - const promotedVisTypes = getVisTypesByGroup(VisGroups.PROMOTED); - const aggsBasedVisTypes = getVisTypesByGroup(VisGroups.AGGBASED); - const toolVisTypes = getVisTypesByGroup(VisGroups.TOOLS); - const visTypeAliases = visualizations - .getAliases() - .sort(({ promotion: a = false }: VisTypeAlias, { promotion: b = false }: VisTypeAlias) => + const promotedVisTypes = getSortedVisTypesByGroup(VisGroups.PROMOTED); + const aggsBasedVisTypes = getSortedVisTypesByGroup(VisGroups.AGGBASED); + const toolVisTypes = getSortedVisTypesByGroup(VisGroups.TOOLS); + const visTypeAliases = getVisTypeAliases().sort( + ({ promotion: a = false }: VisTypeAlias, { promotion: b = false }: VisTypeAlias) => a === b ? 0 : a ? -1 : 1 - ); + ); const factories = unwrappedEmbeddableFactories.filter( ({ isEditable, factory: { type, canCreateNew, isContainerType } }) => @@ -230,7 +236,7 @@ export const EditorMenu = ({ dashboardContainer, createNewVisType }: Props) => { } if (newEmbeddable) { - core.notifications.toasts.addSuccess({ + toasts.addSuccess({ title: dashboardReplacePanelAction.getSuccessMessage( `'${newEmbeddable.getInput().title}'` || '' ), @@ -302,11 +308,7 @@ export const EditorMenu = ({ dashboardContainer, createNewVisType }: Props) => { )} diff --git a/src/plugins/dashboard/public/application/top_nav/get_top_nav_config.ts b/src/plugins/dashboard/public/application/top_nav/get_top_nav_config.ts index 90972758e5df1..5047ec02f9f60 100644 --- a/src/plugins/dashboard/public/application/top_nav/get_top_nav_config.ts +++ b/src/plugins/dashboard/public/application/top_nav/get_top_nav_config.ts @@ -7,8 +7,8 @@ */ import { i18n } from '@kbn/i18n'; +import { ViewMode } from '@kbn/embeddable-plugin/public'; import { TopNavMenuData } from '@kbn/navigation-plugin/public'; -import { ViewMode } from '../../services/embeddable'; import { TopNavIds } from './top_nav_ids'; import { NavAction } from '../../types'; diff --git a/src/plugins/dashboard/public/application/top_nav/save_modal.tsx b/src/plugins/dashboard/public/application/top_nav/save_modal.tsx index 575d57914e23f..790bacf2f9aab 100644 --- a/src/plugins/dashboard/public/application/top_nav/save_modal.tsx +++ b/src/plugins/dashboard/public/application/top_nav/save_modal.tsx @@ -10,10 +10,10 @@ import React, { Fragment } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import { EuiFormRow, EuiTextArea, EuiSwitch } from '@elastic/eui'; +import { SavedObjectSaveModal } from '@kbn/saved-objects-plugin/public'; -import type { SavedObjectsTaggingApi } from '../../services/saved_objects_tagging_oss'; -import { SavedObjectSaveModal } from '../../services/saved_objects'; -import { DashboardSaveOptions } from '../../types'; +import type { DashboardSaveOptions } from '../../types'; +import { pluginServices } from '../../services/plugin_services'; interface Props { onSave: ({ @@ -31,7 +31,6 @@ interface Props { tags?: string[]; timeRestore: boolean; showCopyOnSave: boolean; - savedObjectsTagging?: SavedObjectsTaggingApi; } interface State { @@ -86,9 +85,12 @@ export class DashboardSaveModal extends React.Component { }; renderDashboardSaveOptions() { - const { savedObjectsTagging } = this.props; - const tagSelector = savedObjectsTagging ? ( - { this.setState({ diff --git a/src/plugins/dashboard/public/application/top_nav/show_clone_modal.tsx b/src/plugins/dashboard/public/application/top_nav/show_clone_modal.tsx index fc060f965630a..59d71fd4b2fa4 100644 --- a/src/plugins/dashboard/public/application/top_nav/show_clone_modal.tsx +++ b/src/plugins/dashboard/public/application/top_nav/show_clone_modal.tsx @@ -8,11 +8,13 @@ import React from 'react'; import ReactDOM from 'react-dom'; + import { i18n } from '@kbn/i18n'; import { I18nProvider } from '@kbn/i18n-react'; -import { CoreStart } from '@kbn/core/public'; +import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; + import { DashboardCloneModal } from './clone_modal'; -import { KibanaThemeProvider } from '../../services/kibana_react'; +import { pluginServices } from '../../services/plugin_services'; export interface ShowCloneModalProps { onClone: ( @@ -21,10 +23,13 @@ export interface ShowCloneModalProps { onTitleDuplicate: () => void ) => Promise<{ id?: string } | { error: Error }>; title: string; - theme$: CoreStart['theme']['theme$']; } -export function showCloneModal({ onClone, title, theme$ }: ShowCloneModalProps) { +export function showCloneModal({ onClone, title }: ShowCloneModalProps) { + const { + settings: { theme }, + } = pluginServices.getServices(); + const container = document.createElement('div'); const closeModal = () => { ReactDOM.unmountComponentAtNode(container); @@ -49,7 +54,7 @@ export function showCloneModal({ onClone, title, theme$ }: ShowCloneModalProps) document.body.appendChild(container); const element = ( - + void; hidePanelTitles: boolean; onHidePanelTitlesChange: (hideTitles: boolean) => void; - theme$: CoreStart['theme']['theme$']; } export function showOptionsPopover({ @@ -46,8 +47,13 @@ export function showOptionsPopover({ onSyncColorsChange, syncTooltips, onSyncTooltipsChange, - theme$, }: ShowOptionsPopoverProps) { + const { + settings: { + theme: { theme$ }, + }, + } = pluginServices.getServices(); + if (isOpen) { onClose(); return; diff --git a/src/plugins/dashboard/public/application/top_nav/show_share_modal.test.tsx b/src/plugins/dashboard/public/application/top_nav/show_share_modal.test.tsx index 37c9a4c5027a9..ad9d85926a94b 100644 --- a/src/plugins/dashboard/public/application/top_nav/show_share_modal.test.tsx +++ b/src/plugins/dashboard/public/application/top_nav/show_share_modal.test.tsx @@ -6,13 +6,14 @@ * Side Public License, v 1. */ +import { Capabilities } from '@kbn/core/public'; + import { DashboardState } from '../../types'; import { DashboardAppLocatorParams } from '../..'; -import { Capabilities } from '../../services/core'; -import { SharePluginStart } from '../../services/share'; import { stateToRawDashboardState } from '../lib/convert_dashboard_state'; -import { getSavedDashboardMock, makeDefaultServices } from '../test_helpers'; +import { getSavedDashboardMock } from '../test_helpers'; import { showPublicUrlSwitch, ShowShareModal, ShowShareModalProps } from './show_share_modal'; +import { pluginServices } from '../../services/plugin_services'; describe('showPublicUrlSwitch', () => { test('returns false if "dashboard" app is not available', () => { @@ -59,38 +60,31 @@ describe('ShowShareModal', () => { const unsavedStateKeys = ['query', 'filters', 'options', 'savedQuery', 'panels'] as Array< keyof DashboardAppLocatorParams >; + const toggleShareMenuSpy = jest.spyOn( + pluginServices.getServices().share, + 'toggleShareContextMenu' + ); + + afterEach(() => { + jest.clearAllMocks(); + }); - const getPropsAndShare = ( - unsavedState?: Partial - ): { share: SharePluginStart; showModalProps: ShowShareModalProps } => { - const services = makeDefaultServices(); - const share = {} as unknown as SharePluginStart; - share.toggleShareContextMenu = jest.fn(); - services.dashboardSessionStorage.getState = jest.fn().mockReturnValue(unsavedState); + const getPropsAndShare = (unsavedState?: Partial): ShowShareModalProps => { + pluginServices.getServices().dashboardSessionStorage.getState = jest + .fn() + .mockReturnValue(unsavedState); return { - showModalProps: { - share, - isDirty: true, - kibanaVersion: 'testKibanaVersion', - savedDashboard: getSavedDashboardMock(), - anchorElement: document.createElement('div'), - dashboardCapabilities: services.dashboardCapabilities, - currentDashboardState: { panels: {} } as unknown as DashboardState, - dashboardSessionStorage: services.dashboardSessionStorage, - timeRange: { - from: '2021-10-07T00:00:00.000Z', - to: '2021-10-10T00:00:00.000Z', - }, - }, - share, + isDirty: true, + savedDashboard: getSavedDashboardMock(), + anchorElement: document.createElement('div'), + currentDashboardState: { panels: {} } as unknown as DashboardState, }; }; it('locatorParams is missing all unsaved state when none is given', () => { - const { share, showModalProps } = getPropsAndShare(); - const toggleShareMenuSpy = jest.spyOn(share, 'toggleShareContextMenu'); + const showModalProps = getPropsAndShare(); ShowShareModal(showModalProps); - expect(share.toggleShareContextMenu).toHaveBeenCalledTimes(1); + expect(toggleShareMenuSpy).toHaveBeenCalledTimes(1); const shareLocatorParams = ( toggleShareMenuSpy.mock.calls[0][0].sharingData as { locatorParams: { params: DashboardAppLocatorParams }; @@ -132,10 +126,9 @@ describe('ShowShareModal', () => { query: { query: 'bye', language: 'kuery' }, savedQuery: 'amazingSavedQuery', } as unknown as DashboardState; - const { share, showModalProps } = getPropsAndShare(unsavedDashboardState); - const toggleShareMenuSpy = jest.spyOn(share, 'toggleShareContextMenu'); + const showModalProps = getPropsAndShare(unsavedDashboardState); ShowShareModal(showModalProps); - expect(share.toggleShareContextMenu).toHaveBeenCalledTimes(1); + expect(toggleShareMenuSpy).toHaveBeenCalledTimes(1); const shareLocatorParams = ( toggleShareMenuSpy.mock.calls[0][0].sharingData as { locatorParams: { params: DashboardAppLocatorParams }; @@ -143,7 +136,6 @@ describe('ShowShareModal', () => { ).locatorParams.params; const rawDashboardState = stateToRawDashboardState({ state: unsavedDashboardState, - version: 'testKibanaVersion', }); unsavedStateKeys.forEach((key) => { expect(shareLocatorParams[key]).toStrictEqual( diff --git a/src/plugins/dashboard/public/application/top_nav/show_share_modal.tsx b/src/plugins/dashboard/public/application/top_nav/show_share_modal.tsx index 40028cb58e238..f93061f56d6ec 100644 --- a/src/plugins/dashboard/public/application/top_nav/show_share_modal.tsx +++ b/src/plugins/dashboard/public/application/top_nav/show_share_modal.tsx @@ -6,58 +6,63 @@ * Side Public License, v 1. */ -import { EuiCheckboxGroup } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; import moment from 'moment'; import React, { ReactElement, useState } from 'react'; + +import { EuiCheckboxGroup } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import type { Capabilities } from '@kbn/core/public'; -import { SerializableControlGroupInput } from '@kbn/controls-plugin/common'; -import { DashboardSavedObject } from '../..'; +import type { SerializableControlGroupInput } from '@kbn/controls-plugin/common'; +import { ViewMode } from '@kbn/embeddable-plugin/public'; +import { setStateToKbnUrl, unhashUrl } from '@kbn/kibana-utils-plugin/public'; + +import type { DashboardSavedObject } from '../..'; import { shareModalStrings } from '../../dashboard_strings'; import { DashboardAppLocatorParams, DASHBOARD_APP_LOCATOR } from '../../locator'; -import { TimeRange } from '../../services/data'; -import { ViewMode } from '../../services/embeddable'; -import { setStateToKbnUrl, unhashUrl } from '../../services/kibana_utils'; -import { SharePluginStart } from '../../services/share'; -import { DashboardAppCapabilities, DashboardState } from '../../types'; +import type { DashboardState } from '../../types'; import { dashboardUrlParams } from '../dashboard_router'; import { stateToRawDashboardState } from '../lib/convert_dashboard_state'; import { convertPanelMapToSavedPanels } from '../lib/convert_dashboard_panels'; -import { DashboardSessionStorage } from '../lib'; +import { pluginServices } from '../../services/plugin_services'; const showFilterBarId = 'showFilterBar'; export interface ShowShareModalProps { isDirty: boolean; - timeRange: TimeRange; - kibanaVersion: string; - share: SharePluginStart; anchorElement: HTMLElement; savedDashboard: DashboardSavedObject; currentDashboardState: DashboardState; - dashboardCapabilities: DashboardAppCapabilities; - dashboardSessionStorage: DashboardSessionStorage; } export const showPublicUrlSwitch = (anonymousUserCapabilities: Capabilities) => { if (!anonymousUserCapabilities.dashboard) return false; - const dashboard = anonymousUserCapabilities.dashboard as unknown as DashboardAppCapabilities; + const dashboard = anonymousUserCapabilities.dashboard; return !!dashboard.show; }; export function ShowShareModal({ - share, isDirty, - timeRange, - kibanaVersion, anchorElement, savedDashboard, - dashboardCapabilities, currentDashboardState, - dashboardSessionStorage, }: ShowShareModalProps) { + const { + dashboardCapabilities: { createShortUrl: allowShortUrl }, + dashboardSessionStorage, + data: { + query: { + timefilter: { + timefilter: { getTime }, + }, + }, + }, + share: { toggleShareContextMenu }, + } = pluginServices.getServices(); + + if (!toggleShareContextMenu) return; // TODO: Make this logic cleaner once share is an optional service + const EmbedUrlParamExtension = ({ setParamValue, }: { @@ -128,7 +133,7 @@ export function ShowShareModal({ savedQuery: unsavedDashboardState.savedQuery, controlGroupInput: unsavedDashboardState.controlGroupInput as SerializableControlGroupInput, panels: unsavedDashboardState.panels - ? convertPanelMapToSavedPanels(unsavedDashboardState.panels, kibanaVersion) + ? convertPanelMapToSavedPanels(unsavedDashboardState.panels) : undefined, }; } @@ -139,20 +144,19 @@ export function ShowShareModal({ refreshInterval: undefined, // We don't share refresh interval externally viewMode: ViewMode.VIEW, // For share locators we always load the dashboard in view mode useHash: false, - timeRange, + timeRange: getTime(), ...unsavedStateForLocator, }; - share.toggleShareContextMenu({ + toggleShareContextMenu({ isDirty, anchorElement, allowEmbed: true, - allowShortUrl: dashboardCapabilities.createShortUrl, + allowShortUrl, shareableUrl: setStateToKbnUrl( '_a', stateToRawDashboardState({ state: currentDashboardState, - version: kibanaVersion, }), { useHash: false, storeInHashQuery: true }, unhashUrl(window.location.href) @@ -168,7 +172,6 @@ export function ShowShareModal({ }), locatorParams: { id: DASHBOARD_APP_LOCATOR, - version: kibanaVersion, params: locatorParams, }, }, diff --git a/src/plugins/dashboard/public/dashboard_strings.ts b/src/plugins/dashboard/public/dashboard_strings.ts index a158df1013558..7cea0a45c0e25 100644 --- a/src/plugins/dashboard/public/dashboard_strings.ts +++ b/src/plugins/dashboard/public/dashboard_strings.ts @@ -7,7 +7,7 @@ */ import { i18n } from '@kbn/i18n'; -import { ViewMode } from './services/embeddable'; +import { ViewMode } from '@kbn/embeddable-plugin/public'; /** * @param title {string} the current title of the dashboard @@ -388,6 +388,11 @@ export const dashboardLoadingErrorStrings = { defaultMessage: 'Error encountered while loading saved dashboard: {message}', values: { message }, }), + getDashboardGridError: (message: string) => + i18n.translate('dashboard.loadingError.dashboardGridErrorMessage', { + defaultMessage: 'Unable to load dashboard: {message}', + values: { message }, + }), }; /* diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index c86ee5829eb44..9d1df9d2acb12 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -6,18 +6,17 @@ * Side Public License, v 1. */ -import * as React from 'react'; import { BehaviorSubject } from 'rxjs'; import { filter, map } from 'rxjs/operators'; import { Start as InspectorStartContract } from '@kbn/inspector-plugin/public'; -import { UrlForwardingSetup, UrlForwardingStart } from '@kbn/url-forwarding-plugin/public'; +import type { UrlForwardingSetup, UrlForwardingStart } from '@kbn/url-forwarding-plugin/public'; import { APP_WRAPPER_CLASS } from '@kbn/core/public'; import { App, Plugin, - CoreSetup, - CoreStart, + type CoreSetup, + type CoreStart, AppUpdater, ScopedHistory, AppMountParameters, @@ -25,40 +24,35 @@ import { PluginInitializerContext, SavedObjectsClientContract, } from '@kbn/core/public'; -import { VisualizationsStart } from '@kbn/visualizations-plugin/public'; -import { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public'; -import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; -import { replaceUrlHashQuery } from '@kbn/kibana-utils-plugin/public'; - -import { createKbnUrlTracker } from './services/kibana_utils'; -import { UsageCollectionSetup } from './services/usage_collection'; -import { UiActionsSetup, UiActionsStart } from './services/ui_actions'; -import { PresentationUtilPluginStart } from './services/presentation_util'; -import type { HomePublicPluginSetup } from './services/home'; -import { NavigationPublicPluginStart as NavigationStart } from './services/navigation'; -import { DataPublicPluginSetup, DataPublicPluginStart } from './services/data'; -import { SharePluginSetup, SharePluginStart } from './services/share'; -import type { SavedObjectTaggingOssPluginStart } from './services/saved_objects_tagging_oss'; -import type { - ScreenshotModePluginSetup, - ScreenshotModePluginStart, -} from './services/screenshot_mode'; -import { - getSavedObjectFinder, - SavedObjectLoader, - SavedObjectsStart, -} from './services/saved_objects'; import { CONTEXT_MENU_TRIGGER, EmbeddableSetup, EmbeddableStart, PANEL_BADGE_TRIGGER, PANEL_NOTIFICATION_TRIGGER, -} from './services/embeddable'; -import { - ExitFullScreenButton as ExitFullScreenButtonUi, - ExitFullScreenButtonProps, -} from './services/kibana_react'; +} from '@kbn/embeddable-plugin/public'; +import type { + ScreenshotModePluginSetup, + ScreenshotModePluginStart, +} from '@kbn/screenshot-mode-plugin/public'; +import type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; +import type { HomePublicPluginSetup } from '@kbn/home-plugin/public'; +import { replaceUrlHashQuery } from '@kbn/kibana-utils-plugin/public'; +import { createKbnUrlTracker } from '@kbn/kibana-utils-plugin/public'; +import type { VisualizationsStart } from '@kbn/visualizations-plugin/public'; +import type { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public'; +import type { + UsageCollectionSetup, + UsageCollectionStart, +} from '@kbn/usage-collection-plugin/public'; +import type { NavigationPublicPluginStart } from '@kbn/navigation-plugin/public'; +import type { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public'; +import type { UiActionsSetup, UiActionsStart } from '@kbn/ui-actions-plugin/public'; +import type { PresentationUtilPluginStart } from '@kbn/presentation-util-plugin/public'; +import type { DataPublicPluginSetup, DataPublicPluginStart } from '@kbn/data-plugin/public'; +import type { SavedObjectTaggingOssPluginStart } from '@kbn/saved-objects-tagging-oss-plugin/public'; +import { getSavedObjectFinder, type SavedObjectsStart } from '@kbn/saved-objects-plugin/public'; +import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import { ClonePanelAction, @@ -72,16 +66,16 @@ import { AddToLibraryAction, LibraryNotificationAction, CopyToDashboardAction, - DashboardCapabilities, } from './application'; +import { SavedObjectLoader } from './services/saved_object_loader'; import { DashboardAppLocatorDefinition, DashboardAppLocator } from './locator'; import { createSavedDashboardLoader } from './saved_dashboards'; import { DashboardConstants } from './dashboard_constants'; import { PlaceholderEmbeddableFactory } from './application/embeddable/placeholder'; import { ExportCSVAction } from './application/actions/export_csv_action'; import { dashboardFeatureCatalog } from './dashboard_strings'; -import { SpacesPluginStart } from './services/spaces'; import { FiltersNotificationBadge } from './application/actions/filters_notification_badge'; +import type { DashboardMountContextProps } from './types'; export interface DashboardFeatureFlagConfig { allowByValueEmbeddables: boolean; @@ -91,31 +85,32 @@ export interface DashboardSetupDependencies { data: DataPublicPluginSetup; embeddable: EmbeddableSetup; home?: HomePublicPluginSetup; - urlForwarding: UrlForwardingSetup; + screenshotMode: ScreenshotModePluginSetup; share?: SharePluginSetup; - uiActions: UiActionsSetup; usageCollection?: UsageCollectionSetup; - screenshotMode: ScreenshotModePluginSetup; + uiActions: UiActionsSetup; + urlForwarding: UrlForwardingSetup; unifiedSearch: UnifiedSearchPublicPluginStart; } export interface DashboardStartDependencies { data: DataPublicPluginStart; - urlForwarding: UrlForwardingStart; + dataViewEditor: DataViewEditorStart; embeddable: EmbeddableStart; inspector: InspectorStartContract; - navigation: NavigationStart; - savedObjectsClient: SavedObjectsClientContract; - share?: SharePluginStart; - uiActions: UiActionsStart; - savedObjects: SavedObjectsStart; + navigation: NavigationPublicPluginStart; presentationUtil: PresentationUtilPluginStart; + savedObjects: SavedObjectsStart; + savedObjectsClient: SavedObjectsClientContract; savedObjectsTaggingOss?: SavedObjectTaggingOssPluginStart; - spaces?: SpacesPluginStart; - visualizations: VisualizationsStart; screenshotMode: ScreenshotModePluginStart; - dataViewEditor: DataViewEditorStart; + share?: SharePluginStart; + spaces?: SpacesPluginStart; + uiActions: UiActionsStart; unifiedSearch: UnifiedSearchPublicPluginStart; + urlForwarding: UrlForwardingStart; + usageCollection?: UsageCollectionStart; + visualizations: VisualizationsStart; } export interface DashboardSetup { @@ -143,51 +138,22 @@ export class DashboardPlugin private dashboardFeatureFlagConfig?: DashboardFeatureFlagConfig; private locator?: DashboardAppLocator; + private async startDashboardKibanaServices( + coreStart: CoreStart, + startPlugins: DashboardStartDependencies, + initContext: PluginInitializerContext + ) { + const { registry, pluginServices } = await import('./services/plugin_services'); + pluginServices.setRegistry(registry.start({ coreStart, startPlugins, initContext })); + } + public setup( core: CoreSetup, - { - share, - embeddable, - home, - urlForwarding, - data, - usageCollection, - screenshotMode, - }: DashboardSetupDependencies + { share, embeddable, home, urlForwarding, data }: DashboardSetupDependencies ): DashboardSetup { this.dashboardFeatureFlagConfig = this.initializerContext.config.get(); - const getPlaceholderEmbeddableStartServices = async () => { - const [coreStart] = await core.getStartServices(); - return { theme: coreStart.theme }; - }; - - const getStartServices = async () => { - const [coreStart, deps] = await core.getStartServices(); - - const ExitFullScreenButton: React.FC = (props) => { - return ; - }; - return { - SavedObjectFinder: getSavedObjectFinder(coreStart.savedObjects, coreStart.uiSettings), - showWriteControls: Boolean(coreStart.application.capabilities.dashboard.showWriteControls), - notifications: coreStart.notifications, - screenshotMode: deps.screenshotMode, - application: coreStart.application, - uiSettings: coreStart.uiSettings, - overlays: coreStart.overlays, - analytics: coreStart.analytics, - embeddable: deps.embeddable, - uiActions: deps.uiActions, - inspector: deps.inspector, - theme: coreStart.theme, - http: coreStart.http, - ExitFullScreenButton, - presentationUtil: deps.presentationUtil, - }; - }; - if (share) { this.locator = share.url.locators.create( new DashboardAppLocatorDefinition({ @@ -245,19 +211,14 @@ export class DashboardPlugin }, }); - getStartServices().then((coreStart) => { - const dashboardContainerFactory = new DashboardContainerFactoryDefinition( - getStartServices, - coreStart.embeddable - ); + core.getStartServices().then(([, deps]) => { + const dashboardContainerFactory = new DashboardContainerFactoryDefinition(deps.embeddable); embeddable.registerEmbeddableFactory( dashboardContainerFactory.type, dashboardContainerFactory ); - const placeholderFactory = new PlaceholderEmbeddableFactory( - getPlaceholderEmbeddableStartServices - ); + const placeholderFactory = new PlaceholderEmbeddableFactory(); embeddable.registerEmbeddableFactory(placeholderFactory.type, placeholderFactory); }); @@ -278,16 +239,19 @@ export class DashboardPlugin params.element.classList.add(APP_WRAPPER_CLASS); const { mountApp } = await import('./application/dashboard_router'); appMounted(); + + const mountContext: DashboardMountContextProps = { + restorePreviousUrl, + scopedHistory: () => this.currentHistory!, + onAppLeave: params.onAppLeave, + setHeaderActionMenu: params.setHeaderActionMenu, + }; + return mountApp({ core, appUnMounted, - usageCollection, - restorePreviousUrl, element: params.element, - onAppLeave: params.onAppLeave, - scopedHistory: this.currentHistory!, - initializerContext: this.initializerContext, - setHeaderActionMenu: params.setHeaderActionMenu, + mountContext, }); }, }; @@ -341,78 +305,49 @@ export class DashboardPlugin } public start(core: CoreStart, plugins: DashboardStartDependencies): DashboardStart { - const { notifications, overlays, application, theme, uiSettings } = core; - const { uiActions, data, share, presentationUtil, embeddable } = plugins; - - const dashboardCapabilities: Readonly = application.capabilities - .dashboard as DashboardCapabilities; - - const SavedObjectFinder = getSavedObjectFinder(core.savedObjects, uiSettings); - - const expandPanelAction = new ExpandPanelAction(); - uiActions.registerAction(expandPanelAction); - uiActions.attachAction(CONTEXT_MENU_TRIGGER, expandPanelAction.id); + const { uiSettings } = core; + const { uiActions, share, presentationUtil } = plugins; + this.startDashboardKibanaServices(core, plugins, this.initializerContext).then(() => { + const clonePanelAction = new ClonePanelAction(core.savedObjects); + uiActions.registerAction(clonePanelAction); + uiActions.attachAction(CONTEXT_MENU_TRIGGER, clonePanelAction.id); + + const SavedObjectFinder = getSavedObjectFinder(core.savedObjects, uiSettings); + const changeViewAction = new ReplacePanelAction(SavedObjectFinder); + uiActions.registerAction(changeViewAction); + uiActions.attachAction(CONTEXT_MENU_TRIGGER, changeViewAction.id); + + const panelLevelFiltersNotification = new FiltersNotificationBadge(); + uiActions.registerAction(panelLevelFiltersNotification); + uiActions.attachAction(PANEL_BADGE_TRIGGER, panelLevelFiltersNotification.id); + + if (share) { + const ExportCSVPlugin = new ExportCSVAction(); + uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, ExportCSVPlugin); + } - const changeViewAction = new ReplacePanelAction( - core, - SavedObjectFinder, - notifications, - plugins.embeddable.getEmbeddableFactories - ); - uiActions.registerAction(changeViewAction); - uiActions.attachAction(CONTEXT_MENU_TRIGGER, changeViewAction.id); - - const clonePanelAction = new ClonePanelAction(core); - uiActions.registerAction(clonePanelAction); - uiActions.attachAction(CONTEXT_MENU_TRIGGER, clonePanelAction.id); - - const panelLevelFiltersNotification = new FiltersNotificationBadge( - application, - embeddable, - overlays, - theme, - uiSettings - ); - uiActions.registerAction(panelLevelFiltersNotification); - uiActions.attachAction(PANEL_BADGE_TRIGGER, panelLevelFiltersNotification.id); + if (this.dashboardFeatureFlagConfig?.allowByValueEmbeddables) { + const addToLibraryAction = new AddToLibraryAction(); + uiActions.registerAction(addToLibraryAction); + uiActions.attachAction(CONTEXT_MENU_TRIGGER, addToLibraryAction.id); - if (share) { - const ExportCSVPlugin = new ExportCSVAction({ core, data }); - uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, ExportCSVPlugin); - } - - if (this.dashboardFeatureFlagConfig?.allowByValueEmbeddables) { - const addToLibraryAction = new AddToLibraryAction({ - toasts: notifications.toasts, - capabilities: application.capabilities, - }); - uiActions.registerAction(addToLibraryAction); - uiActions.attachAction(CONTEXT_MENU_TRIGGER, addToLibraryAction.id); + const unlinkFromLibraryAction = new UnlinkFromLibraryAction(); + uiActions.registerAction(unlinkFromLibraryAction); + uiActions.attachAction(CONTEXT_MENU_TRIGGER, unlinkFromLibraryAction.id); - const unlinkFromLibraryAction = new UnlinkFromLibraryAction({ toasts: notifications.toasts }); - uiActions.registerAction(unlinkFromLibraryAction); - uiActions.attachAction(CONTEXT_MENU_TRIGGER, unlinkFromLibraryAction.id); + const libraryNotificationAction = new LibraryNotificationAction(unlinkFromLibraryAction); + uiActions.registerAction(libraryNotificationAction); + uiActions.attachAction(PANEL_NOTIFICATION_TRIGGER, libraryNotificationAction.id); - const libraryNotificationAction = new LibraryNotificationAction( - theme, - unlinkFromLibraryAction - ); - uiActions.registerAction(libraryNotificationAction); - uiActions.attachAction(PANEL_NOTIFICATION_TRIGGER, libraryNotificationAction.id); + const copyToDashboardAction = new CopyToDashboardAction(presentationUtil.ContextProvider); + uiActions.registerAction(copyToDashboardAction); + uiActions.attachAction(CONTEXT_MENU_TRIGGER, copyToDashboardAction.id); + } + }); - const copyToDashboardAction = new CopyToDashboardAction( - theme, - overlays, - embeddable.getStateTransfer(), - { - canCreateNew: Boolean(dashboardCapabilities.createNew), - canEditExisting: Boolean(dashboardCapabilities.showWriteControls), - }, - presentationUtil.ContextProvider - ); - uiActions.registerAction(copyToDashboardAction); - uiActions.attachAction(CONTEXT_MENU_TRIGGER, copyToDashboardAction.id); - } + const expandPanelAction = new ExpandPanelAction(); // this action does't rely on any services + uiActions.registerAction(expandPanelAction); + uiActions.attachAction(CONTEXT_MENU_TRIGGER, expandPanelAction.id); const savedDashboardLoader = createSavedDashboardLoader({ savedObjectsClient: core.savedObjects.client, diff --git a/src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts b/src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts index ed3a7fdf4b21f..c23b6eb2a87e0 100644 --- a/src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts +++ b/src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts @@ -11,9 +11,11 @@ import { SavedObjectsClientContract } from '@kbn/core/public'; import type { ResolvedSimpleSavedObject } from '@kbn/core/public'; import { SavedObjectAttributes, SavedObjectReference } from '@kbn/core/types'; import { RawControlGroupAttributes } from '@kbn/controls-plugin/common'; -import { EmbeddableStart } from '../services/embeddable'; -import { SavedObject, SavedObjectsStart } from '../services/saved_objects'; -import { Filter, ISearchSource, Query, RefreshInterval } from '../services/data'; +import { EmbeddableStart } from '@kbn/embeddable-plugin/public'; +import { ISearchSource } from '@kbn/data-plugin/common'; +import { RefreshInterval } from '@kbn/data-plugin/public'; +import { Query, Filter } from '@kbn/es-query'; +import type { SavedObject, SavedObjectsStart } from '@kbn/saved-objects-plugin/public'; import { createDashboardEditUrl } from '../dashboard_constants'; import { extractReferences, injectReferences } from '../../common/saved_dashboard_references'; diff --git a/src/plugins/dashboard/public/saved_dashboards/saved_dashboards.ts b/src/plugins/dashboard/public/saved_dashboards/saved_dashboards.ts index e5f919cd2a45a..a154fdad96562 100644 --- a/src/plugins/dashboard/public/saved_dashboards/saved_dashboards.ts +++ b/src/plugins/dashboard/public/saved_dashboards/saved_dashboards.ts @@ -7,10 +7,10 @@ */ import { SavedObjectsClientContract } from '@kbn/core/public'; +import { EmbeddableStart } from '@kbn/embeddable-plugin/public'; +import type { SavedObjectsStart } from '@kbn/saved-objects-plugin/public'; -import { EmbeddableStart } from '../services/embeddable'; -import { SavedObjectLoader, SavedObjectsStart } from '../services/saved_objects'; - +import { SavedObjectLoader } from '../services/saved_object_loader'; import { createSavedDashboardClass } from './saved_dashboard'; interface Services { diff --git a/src/plugins/dashboard/public/services/analytics/analytics.stub.ts b/src/plugins/dashboard/public/services/analytics/analytics.stub.ts new file mode 100644 index 0000000000000..05127dfd3980b --- /dev/null +++ b/src/plugins/dashboard/public/services/analytics/analytics.stub.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { analyticsServiceMock } from '@kbn/core-analytics-browser-mocks'; +import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import { DashboardAnalyticsService } from './types'; + +type AnalyticsServiceFactory = PluginServiceFactory; + +export const analyticsServiceFactory: AnalyticsServiceFactory = () => { + const pluginMock = analyticsServiceMock.createAnalyticsServiceStart(); + + return { + reportEvent: pluginMock.reportEvent, + }; +}; diff --git a/src/plugins/dashboard/public/services/analytics/analytics_service.ts b/src/plugins/dashboard/public/services/analytics/analytics_service.ts new file mode 100644 index 0000000000000..97a689c3cba54 --- /dev/null +++ b/src/plugins/dashboard/public/services/analytics/analytics_service.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import type { DashboardStartDependencies } from '../../plugin'; +import type { DashboardAnalyticsService } from './types'; + +export type AnalyticsServiceFactory = KibanaPluginServiceFactory< + DashboardAnalyticsService, + DashboardStartDependencies +>; + +export const analyticsServiceFactory: AnalyticsServiceFactory = ({ coreStart }) => { + const { + analytics: { reportEvent }, + } = coreStart; + + return { + reportEvent, + }; +}; diff --git a/src/plugins/dashboard/public/services/analytics/types.ts b/src/plugins/dashboard/public/services/analytics/types.ts new file mode 100644 index 0000000000000..697ad1620ce07 --- /dev/null +++ b/src/plugins/dashboard/public/services/analytics/types.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { CoreStart } from '@kbn/core/public'; + +export interface DashboardAnalyticsService { + reportEvent: CoreStart['analytics']['reportEvent']; +} diff --git a/src/plugins/dashboard/public/services/application/application.stub.ts b/src/plugins/dashboard/public/services/application/application.stub.ts new file mode 100644 index 0000000000000..9717e75ea7bc8 --- /dev/null +++ b/src/plugins/dashboard/public/services/application/application.stub.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { applicationServiceMock } from '@kbn/core-application-browser-mocks'; +import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import { DashboardApplicationService } from './types'; + +type ApplicationServiceFactory = PluginServiceFactory; + +export const applicationServiceFactory: ApplicationServiceFactory = () => { + const pluginMock = applicationServiceMock.createStartContract(); + + return { + currentAppId$: pluginMock.currentAppId$, + navigateToApp: pluginMock.navigateToApp, + navigateToUrl: pluginMock.navigateToUrl, + getUrlForApp: pluginMock.getUrlForApp, + capabilities: { + advancedSettings: pluginMock.capabilities.advancedSettings, + maps: pluginMock.capabilities.maps, + navLinks: pluginMock.capabilities.navLinks, + visualize: pluginMock.capabilities.visualize, + }, + }; +}; diff --git a/src/plugins/dashboard/public/services/application/application_service.ts b/src/plugins/dashboard/public/services/application/application_service.ts new file mode 100644 index 0000000000000..adc6d23f48461 --- /dev/null +++ b/src/plugins/dashboard/public/services/application/application_service.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import type { DashboardStartDependencies } from '../../plugin'; +import type { DashboardApplicationService } from './types'; + +export type ApplicationServiceFactory = KibanaPluginServiceFactory< + DashboardApplicationService, + DashboardStartDependencies +>; + +export const applicationServiceFactory: ApplicationServiceFactory = ({ coreStart }) => { + const { + application: { + currentAppId$, + navigateToApp, + navigateToUrl, + getUrlForApp, + capabilities: { advancedSettings, maps, navLinks, visualize }, + }, + } = coreStart; + + return { + currentAppId$, + navigateToApp, + navigateToUrl, + getUrlForApp, + capabilities: { + advancedSettings, + maps, + navLinks, + visualize, + }, + }; +}; diff --git a/src/plugins/dashboard/public/services/application/types.ts b/src/plugins/dashboard/public/services/application/types.ts new file mode 100644 index 0000000000000..66ef934e44bb3 --- /dev/null +++ b/src/plugins/dashboard/public/services/application/types.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { CoreStart } from '@kbn/core/public'; + +export interface DashboardApplicationService { + currentAppId$: CoreStart['application']['currentAppId$']; + navigateToApp: CoreStart['application']['navigateToApp']; + navigateToUrl: CoreStart['application']['navigateToUrl']; + getUrlForApp: CoreStart['application']['getUrlForApp']; + capabilities: { + advancedSettings: CoreStart['application']['capabilities']['advancedSettings']; + maps: CoreStart['application']['capabilities']['maps']; // only used in `add_to_library_action` + navLinks: CoreStart['application']['capabilities']['navLinks']; + visualize: CoreStart['application']['capabilities']['visualize']; // only used in `add_to_library_action` + }; +} diff --git a/src/plugins/dashboard/public/services/chrome/chrome.stub.ts b/src/plugins/dashboard/public/services/chrome/chrome.stub.ts new file mode 100644 index 0000000000000..74b534820ea8d --- /dev/null +++ b/src/plugins/dashboard/public/services/chrome/chrome.stub.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { chromeServiceMock } from '@kbn/core/public/mocks'; +import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import { DashboardChromeService } from './types'; + +type ChromeServiceFactory = PluginServiceFactory; + +export const chromeServiceFactory: ChromeServiceFactory = () => { + const pluginMock = chromeServiceMock.createStartContract(); + + return { + docTitle: pluginMock.docTitle, + setBadge: pluginMock.setBadge, + getIsVisible$: pluginMock.getIsVisible$, + recentlyAccessed: pluginMock.recentlyAccessed, + setBreadcrumbs: pluginMock.setBreadcrumbs, + setHelpExtension: pluginMock.setHelpExtension, + setIsVisible: pluginMock.setIsVisible, + }; +}; diff --git a/src/plugins/dashboard/public/services/chrome/chrome_service.ts b/src/plugins/dashboard/public/services/chrome/chrome_service.ts new file mode 100644 index 0000000000000..f069ac3f95bd2 --- /dev/null +++ b/src/plugins/dashboard/public/services/chrome/chrome_service.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import type { DashboardStartDependencies } from '../../plugin'; +import type { DashboardChromeService } from './types'; + +export type ChromeServiceFactory = KibanaPluginServiceFactory< + DashboardChromeService, + DashboardStartDependencies +>; + +export const chromeServiceFactory: ChromeServiceFactory = ({ coreStart }) => { + const { + chrome: { + docTitle, + setBadge, + getIsVisible$, + recentlyAccessed, + setBreadcrumbs, + setHelpExtension, + setIsVisible, + }, + } = coreStart; + + return { + docTitle, + setBadge, + getIsVisible$, + recentlyAccessed, + setBreadcrumbs, + setHelpExtension, + setIsVisible, + }; +}; diff --git a/src/plugins/dashboard/public/services/chrome/types.ts b/src/plugins/dashboard/public/services/chrome/types.ts new file mode 100644 index 0000000000000..2d3a1cffaae6a --- /dev/null +++ b/src/plugins/dashboard/public/services/chrome/types.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { CoreStart } from '@kbn/core/public'; + +export interface DashboardChromeService { + docTitle: CoreStart['chrome']['docTitle']; + setBadge: CoreStart['chrome']['setBadge']; + getIsVisible$: CoreStart['chrome']['getIsVisible$']; + recentlyAccessed: CoreStart['chrome']['recentlyAccessed']; + setBreadcrumbs: CoreStart['chrome']['setBreadcrumbs']; + setHelpExtension: CoreStart['chrome']['setHelpExtension']; + setIsVisible: CoreStart['chrome']['setIsVisible']; +} diff --git a/src/plugins/dashboard/public/services/core_context/core_context.stub.ts b/src/plugins/dashboard/public/services/core_context/core_context.stub.ts new file mode 100644 index 0000000000000..d10c212616123 --- /dev/null +++ b/src/plugins/dashboard/public/services/core_context/core_context.stub.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { coreMock } from '@kbn/core/public/mocks'; +import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import { DashboardCoreContextService } from './types'; + +type CoreContextServiceFactory = PluginServiceFactory; + +export const coreContextServiceFactory: CoreContextServiceFactory = () => { + const pluginMock = coreMock.createStart(); + return { + executionContext: pluginMock.executionContext, + i18nContext: pluginMock.i18n.Context, + }; +}; diff --git a/src/plugins/dashboard/public/services/core_context/core_context_service.ts b/src/plugins/dashboard/public/services/core_context/core_context_service.ts new file mode 100644 index 0000000000000..0019aa617d384 --- /dev/null +++ b/src/plugins/dashboard/public/services/core_context/core_context_service.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import type { DashboardStartDependencies } from '../../plugin'; +import type { DashboardCoreContextService } from './types'; + +export type CoreContextServiceFactory = KibanaPluginServiceFactory< + DashboardCoreContextService, + DashboardStartDependencies +>; + +export const coreContextServiceFactory: CoreContextServiceFactory = ({ coreStart }) => { + const { + executionContext, + i18n: { Context }, + } = coreStart; + + return { + executionContext, + i18nContext: Context, + }; +}; diff --git a/src/plugins/dashboard/public/services/core_context/types.ts b/src/plugins/dashboard/public/services/core_context/types.ts new file mode 100644 index 0000000000000..42e419056c680 --- /dev/null +++ b/src/plugins/dashboard/public/services/core_context/types.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { CoreStart } from '@kbn/core/public'; + +export interface DashboardCoreContextService { + executionContext: CoreStart['executionContext']; + i18nContext: CoreStart['i18n']['Context']; +} diff --git a/src/plugins/dashboard/public/services/dashboard_capabilities/dashboard_capabilities.stub.ts b/src/plugins/dashboard/public/services/dashboard_capabilities/dashboard_capabilities.stub.ts new file mode 100644 index 0000000000000..5615abe91f36f --- /dev/null +++ b/src/plugins/dashboard/public/services/dashboard_capabilities/dashboard_capabilities.stub.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import { DashboardCapabilitiesService } from './types'; + +const defaultDashboardCapabilities: DashboardCapabilitiesService = { + show: true, + createNew: true, + saveQuery: true, + createShortUrl: true, + showWriteControls: true, + storeSearchSession: true, + mapsCapabilities: { save: true }, + visualizeCapabilities: { save: true }, +}; + +type DashboardCapabilitiesServiceFactory = PluginServiceFactory; + +export const dashboardCapabilitiesServiceFactory: DashboardCapabilitiesServiceFactory = () => { + return { + ...defaultDashboardCapabilities, + }; +}; diff --git a/src/plugins/dashboard/public/services/dashboard_capabilities/dashboard_capabilities_service.ts b/src/plugins/dashboard/public/services/dashboard_capabilities/dashboard_capabilities_service.ts new file mode 100644 index 0000000000000..348a492a647a6 --- /dev/null +++ b/src/plugins/dashboard/public/services/dashboard_capabilities/dashboard_capabilities_service.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import type { DashboardStartDependencies } from '../../plugin'; +import type { DashboardCapabilitiesService } from './types'; + +export type DashboardCapabilitiesServiceFactory = KibanaPluginServiceFactory< + DashboardCapabilitiesService, + DashboardStartDependencies +>; +export const dashboardCapabilitiesServiceFactory: DashboardCapabilitiesServiceFactory = ({ + coreStart, +}) => { + const { + application: { + capabilities: { dashboard, maps, visualize }, + }, + } = coreStart; + + return { + show: Boolean(dashboard.show), + saveQuery: Boolean(dashboard.saveQuery), + createNew: Boolean(dashboard.createNew), + mapsCapabilities: { save: Boolean(maps?.save) }, + createShortUrl: Boolean(dashboard.createShortUrl), + showWriteControls: Boolean(dashboard.showWriteControls), + visualizeCapabilities: { save: Boolean(visualize?.save) }, + storeSearchSession: Boolean(dashboard.storeSearchSession), + }; +}; diff --git a/src/plugins/dashboard/public/services/dashboard_capabilities/types.ts b/src/plugins/dashboard/public/services/dashboard_capabilities/types.ts new file mode 100644 index 0000000000000..348656af8c2d9 --- /dev/null +++ b/src/plugins/dashboard/public/services/dashboard_capabilities/types.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export interface DashboardCapabilitiesService { + show: boolean; + saveQuery: boolean; + createNew: boolean; + mapsCapabilities: { save: boolean }; + createShortUrl: boolean; + showWriteControls: boolean; + visualizeCapabilities: { save: boolean }; + storeSearchSession: boolean; +} diff --git a/src/plugins/dashboard/public/services/dashboard_session_storage/dashboard_session_storage.stub.ts b/src/plugins/dashboard/public/services/dashboard_session_storage/dashboard_session_storage.stub.ts new file mode 100644 index 0000000000000..4ae1879122d2c --- /dev/null +++ b/src/plugins/dashboard/public/services/dashboard_session_storage/dashboard_session_storage.stub.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import { DashboardSessionStorageServiceType } from './types'; + +type DashboardSessionStorageServiceFactory = + PluginServiceFactory; + +export const dashboardSessionStorageServiceFactory: DashboardSessionStorageServiceFactory = () => { + return { + clearState: jest.fn(), + getState: jest.fn().mockReturnValue(undefined), + setState: jest.fn(), + getDashboardIdsWithUnsavedChanges: jest + .fn() + .mockReturnValue(['dashboardUnsavedOne', 'dashboardUnsavedTwo']), + dashboardHasUnsavedEdits: jest.fn(), + }; +}; diff --git a/src/plugins/dashboard/public/application/lib/dashboard_session_storage.ts b/src/plugins/dashboard/public/services/dashboard_session_storage/dashboard_session_storage_service.ts similarity index 62% rename from src/plugins/dashboard/public/application/lib/dashboard_session_storage.ts rename to src/plugins/dashboard/public/services/dashboard_session_storage/dashboard_session_storage_service.ts index 0c6aaba99b9ee..9b68eea95156d 100644 --- a/src/plugins/dashboard/public/application/lib/dashboard_session_storage.ts +++ b/src/plugins/dashboard/public/services/dashboard_session_storage/dashboard_session_storage_service.ts @@ -6,21 +6,50 @@ * Side Public License, v 1. */ +import { firstValueFrom } from 'rxjs'; + import { set } from '@kbn/safer-lodash-set'; -import { Storage } from '../../services/kibana_utils'; -import { NotificationsStart } from '../../services/core'; +import { ViewMode } from '@kbn/embeddable-plugin/public'; +import { Storage } from '@kbn/kibana-utils-plugin/public'; +import type { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public'; + +import type { DashboardStartDependencies } from '../../plugin'; +import type { DashboardSessionStorageServiceType } from './types'; import { panelStorageErrorStrings } from '../../dashboard_strings'; -import { DashboardState } from '../../types'; -import { ViewMode } from '../../services/embeddable'; +import type { DashboardState } from '../../types'; +import { DashboardNotificationsService } from '../notifications/types'; +import { DashboardSpacesService } from '../spaces/types'; export const DASHBOARD_PANELS_UNSAVED_ID = 'unsavedDashboard'; const DASHBOARD_PANELS_SESSION_KEY = 'dashboardStateManagerPanels'; -export class DashboardSessionStorage { +interface DashboardSessionStorageRequiredServices { + notifications: DashboardNotificationsService; + spaces: DashboardSpacesService; +} + +export type DashboardSessionStorageServiceFactory = KibanaPluginServiceFactory< + DashboardSessionStorageServiceType, + DashboardStartDependencies, + DashboardSessionStorageRequiredServices +>; + +class DashboardSessionStorageService implements DashboardSessionStorageServiceType { + private activeSpaceId: string; private sessionStorage: Storage; + private notifications: DashboardNotificationsService; + private spaces: DashboardSpacesService; - constructor(private toasts: NotificationsStart['toasts'], private activeSpaceId: string) { + constructor(requiredServices: DashboardSessionStorageRequiredServices) { + ({ notifications: this.notifications, spaces: this.spaces } = requiredServices); this.sessionStorage = new Storage(sessionStorage); + + this.activeSpaceId = 'default'; + if (this.spaces.getActiveSpace$) { + firstValueFrom(this.spaces.getActiveSpace$()).then((space) => { + this.activeSpaceId = space.id; + }); + } } public clearState(id = DASHBOARD_PANELS_UNSAVED_ID) { @@ -32,7 +61,7 @@ export class DashboardSessionStorage { this.sessionStorage.set(DASHBOARD_PANELS_SESSION_KEY, sessionStorage); } } catch (e) { - this.toasts.addDanger({ + this.notifications.toasts.addDanger({ title: panelStorageErrorStrings.getPanelsClearError(e.message), 'data-test-subj': 'dashboardPanelsClearFailure', }); @@ -43,7 +72,7 @@ export class DashboardSessionStorage { try { return this.sessionStorage.get(DASHBOARD_PANELS_SESSION_KEY)?.[this.activeSpaceId]?.[id]; } catch (e) { - this.toasts.addDanger({ + this.notifications.toasts.addDanger({ title: panelStorageErrorStrings.getPanelsGetError(e.message), 'data-test-subj': 'dashboardPanelsGetFailure', }); @@ -56,7 +85,7 @@ export class DashboardSessionStorage { set(sessionStateStorage, [this.activeSpaceId, id], newState); this.sessionStorage.set(DASHBOARD_PANELS_SESSION_KEY, sessionStateStorage); } catch (e) { - this.toasts.addDanger({ + this.notifications.toasts.addDanger({ title: panelStorageErrorStrings.getPanelsSetError(e.message), 'data-test-subj': 'dashboardPanelsSetFailure', }); @@ -68,6 +97,7 @@ export class DashboardSessionStorage { const dashboardStatesInSpace = this.sessionStorage.get(DASHBOARD_PANELS_SESSION_KEY)?.[this.activeSpaceId] || {}; const dashboardsWithUnsavedChanges: string[] = []; + Object.keys(dashboardStatesInSpace).map((dashboardId) => { if ( dashboardStatesInSpace[dashboardId].viewMode === ViewMode.EDIT && @@ -79,7 +109,7 @@ export class DashboardSessionStorage { }); return dashboardsWithUnsavedChanges; } catch (e) { - this.toasts.addDanger({ + this.notifications.toasts.addDanger({ title: panelStorageErrorStrings.getPanelsGetError(e.message), 'data-test-subj': 'dashboardPanelsGetFailure', }); @@ -91,3 +121,10 @@ export class DashboardSessionStorage { return this.getDashboardIdsWithUnsavedChanges().indexOf(id) !== -1; } } + +export const dashboardSessionStorageServiceFactory: DashboardSessionStorageServiceFactory = ( + core, + requiredServices +) => { + return new DashboardSessionStorageService(requiredServices); +}; diff --git a/src/plugins/dashboard/public/services/dashboard_session_storage/types.ts b/src/plugins/dashboard/public/services/dashboard_session_storage/types.ts new file mode 100644 index 0000000000000..dae0d2e5a66cb --- /dev/null +++ b/src/plugins/dashboard/public/services/dashboard_session_storage/types.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { DashboardState } from '../../types'; + +export interface DashboardSessionStorageServiceType { + clearState: (id?: string) => void; + getState: (id: string | undefined) => Partial | undefined; + setState: (id: string | undefined, newState: Partial) => void; + getDashboardIdsWithUnsavedChanges: () => string[]; + dashboardHasUnsavedEdits: (id?: string) => boolean; +} diff --git a/src/plugins/dashboard/public/services/data/data.stub.ts b/src/plugins/dashboard/public/services/data/data.stub.ts new file mode 100644 index 0000000000000..3c29c8d2b5557 --- /dev/null +++ b/src/plugins/dashboard/public/services/data/data.stub.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +// import { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; +import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import { DashboardDataService } from './types'; + +type DataServiceFactory = PluginServiceFactory; + +export const dataServiceFactory: DataServiceFactory = () => ({ + ...dataPluginMock.createStartContract(), +}); diff --git a/src/plugins/dashboard/public/services/data/data_service.ts b/src/plugins/dashboard/public/services/data/data_service.ts new file mode 100644 index 0000000000000..4fc67d193e479 --- /dev/null +++ b/src/plugins/dashboard/public/services/data/data_service.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import type { DashboardStartDependencies } from '../../plugin'; +import type { DashboardDataService } from './types'; + +export type DataServiceFactory = KibanaPluginServiceFactory< + DashboardDataService, + DashboardStartDependencies +>; + +export const dataServiceFactory: DataServiceFactory = ({ startPlugins }) => { + const { + data: { dataViews, fieldFormats, query, search }, + } = startPlugins; + + return { + dataViews, + fieldFormats, + query, + search, + }; +}; diff --git a/src/plugins/dashboard/public/services/home.ts b/src/plugins/dashboard/public/services/data/types.ts similarity index 55% rename from src/plugins/dashboard/public/services/home.ts rename to src/plugins/dashboard/public/services/data/types.ts index 07c1af0935658..b62d1d8454e33 100644 --- a/src/plugins/dashboard/public/services/home.ts +++ b/src/plugins/dashboard/public/services/data/types.ts @@ -6,5 +6,11 @@ * Side Public License, v 1. */ -export type { HomePublicPluginSetup } from '@kbn/home-plugin/public'; -export type { FeatureCatalogueCategory } from '@kbn/home-plugin/public'; +import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; + +export interface DashboardDataService { + dataViews: DataPublicPluginStart['dataViews']; + fieldFormats: DataPublicPluginStart['fieldFormats']; + query: DataPublicPluginStart['query']; + search: DataPublicPluginStart['search']; +} diff --git a/src/plugins/dashboard/public/services/data_view_editor/data_view_editor.stub.ts b/src/plugins/dashboard/public/services/data_view_editor/data_view_editor.stub.ts new file mode 100644 index 0000000000000..56fc23207daf3 --- /dev/null +++ b/src/plugins/dashboard/public/services/data_view_editor/data_view_editor.stub.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { indexPatternEditorPluginMock as dataViewEditorPluginMock } from '@kbn/data-view-editor-plugin/public/mocks'; +import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import { DashboardDataViewEditorService } from './types'; + +type DataViewEditorServiceFactory = PluginServiceFactory; + +export const dataViewEditorServiceFactory: DataViewEditorServiceFactory = () => { + const dataViewEditorMock = dataViewEditorPluginMock.createStartContract(); + + return { + openEditor: dataViewEditorMock.openEditor, + userPermissions: dataViewEditorMock.userPermissions, + }; +}; diff --git a/src/plugins/dashboard/public/services/data_view_editor/data_view_editor_service.ts b/src/plugins/dashboard/public/services/data_view_editor/data_view_editor_service.ts new file mode 100644 index 0000000000000..73f36432a27c7 --- /dev/null +++ b/src/plugins/dashboard/public/services/data_view_editor/data_view_editor_service.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import type { DashboardStartDependencies } from '../../plugin'; +import type { DashboardDataViewEditorService } from './types'; + +export type DataViewEditorServiceFactory = KibanaPluginServiceFactory< + DashboardDataViewEditorService, + DashboardStartDependencies +>; + +export const dataViewEditorServiceFactory: DataViewEditorServiceFactory = ({ startPlugins }) => { + const { + dataViewEditor: { openEditor, userPermissions }, + } = startPlugins; + + return { + openEditor, + userPermissions, + }; +}; diff --git a/src/plugins/dashboard/public/services/core.ts b/src/plugins/dashboard/public/services/data_view_editor/types.ts similarity index 60% rename from src/plugins/dashboard/public/services/core.ts rename to src/plugins/dashboard/public/services/data_view_editor/types.ts index 7b1aaeb1f990b..f4367beeddd01 100644 --- a/src/plugins/dashboard/public/services/core.ts +++ b/src/plugins/dashboard/public/services/data_view_editor/types.ts @@ -6,12 +6,8 @@ * Side Public License, v 1. */ -export type { - AppMountParameters, - CoreSetup, - Capabilities, - PluginInitializerContext, - NotificationsStart, - ApplicationStart, -} from '@kbn/core/public'; -export { ScopedHistory } from '@kbn/core/public'; +import { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public'; +export interface DashboardDataViewEditorService { + openEditor: DataViewEditorStart['openEditor']; + userPermissions: DataViewEditorStart['userPermissions']; +} diff --git a/src/plugins/dashboard/public/services/documentation_links/documentation_links.stub.ts b/src/plugins/dashboard/public/services/documentation_links/documentation_links.stub.ts new file mode 100644 index 0000000000000..3fab07396b4ff --- /dev/null +++ b/src/plugins/dashboard/public/services/documentation_links/documentation_links.stub.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { coreMock } from '@kbn/core/public/mocks'; +import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import { DashboardDocumentationLinksService } from './types'; + +type DocumentationLinksServiceFactory = PluginServiceFactory; + +export const documentationLinksServiceFactory: DocumentationLinksServiceFactory = () => { + const corePluginMock = coreMock.createStart(); + + return { + indexPatternsDocLink: corePluginMock.docLinks.links.indexPatterns.introduction, + kibanaGuideDocLink: corePluginMock.docLinks.links.kibana.guide, + }; +}; diff --git a/src/plugins/dashboard/public/services/documentation_links/documentation_links_service.ts b/src/plugins/dashboard/public/services/documentation_links/documentation_links_service.ts new file mode 100644 index 0000000000000..fe00d376675f6 --- /dev/null +++ b/src/plugins/dashboard/public/services/documentation_links/documentation_links_service.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import type { DashboardStartDependencies } from '../../plugin'; +import type { DashboardDocumentationLinksService } from './types'; + +export type DocumentationLinksServiceFactory = KibanaPluginServiceFactory< + DashboardDocumentationLinksService, + DashboardStartDependencies +>; + +export const documentationLinksServiceFactory: DocumentationLinksServiceFactory = ({ + coreStart, +}) => { + const { + docLinks: { + links: { + kibana: { guide }, + indexPatterns: { introduction }, + }, + }, + } = coreStart; + + return { + indexPatternsDocLink: introduction, + kibanaGuideDocLink: guide, + }; +}; diff --git a/src/plugins/dashboard/public/services/string_utils.ts b/src/plugins/dashboard/public/services/documentation_links/types.ts similarity index 57% rename from src/plugins/dashboard/public/services/string_utils.ts rename to src/plugins/dashboard/public/services/documentation_links/types.ts index 31a36b38155d7..ee7e520471651 100644 --- a/src/plugins/dashboard/public/services/string_utils.ts +++ b/src/plugins/dashboard/public/services/documentation_links/types.ts @@ -6,11 +6,9 @@ * Side Public License, v 1. */ -/** - * Returns a version of the string with the first letter capitalized. - * @param str {string} - * @returns {string} - */ -export function upperFirst(str: string = ''): string { - return str ? str.charAt(0).toUpperCase() + str.slice(1) : ''; +import { CoreStart } from '@kbn/core/public'; + +export interface DashboardDocumentationLinksService { + indexPatternsDocLink: CoreStart['docLinks']['links']['indexPatterns']['introduction']; + kibanaGuideDocLink: CoreStart['docLinks']['links']['kibana']['guide']; } diff --git a/src/plugins/dashboard/public/services/embeddable/embeddable.stub.ts b/src/plugins/dashboard/public/services/embeddable/embeddable.stub.ts new file mode 100644 index 0000000000000..18f952d4620e2 --- /dev/null +++ b/src/plugins/dashboard/public/services/embeddable/embeddable.stub.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { embeddablePluginMock } from '@kbn/embeddable-plugin/public/mocks'; +import type { PluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import type { DashboardEmbeddableService } from './types'; + +export type EmbeddableServiceFactory = PluginServiceFactory; + +export const embeddableServiceFactory: EmbeddableServiceFactory = () => { + const pluginMock = embeddablePluginMock.createStartContract(); + + return { + getEmbeddableFactory: pluginMock.getEmbeddableFactory, + getEmbeddableFactories: pluginMock.getEmbeddableFactories, + getStateTransfer: pluginMock.getStateTransfer, + EmbeddablePanel: pluginMock.EmbeddablePanel, + }; +}; diff --git a/src/plugins/dashboard/public/services/embeddable/embeddable_service.ts b/src/plugins/dashboard/public/services/embeddable/embeddable_service.ts new file mode 100644 index 0000000000000..258c11f697bc5 --- /dev/null +++ b/src/plugins/dashboard/public/services/embeddable/embeddable_service.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import type { DashboardStartDependencies } from '../../plugin'; +import type { DashboardEmbeddableService } from './types'; + +export type EmbeddableServiceFactory = KibanaPluginServiceFactory< + DashboardEmbeddableService, + DashboardStartDependencies +>; +export const embeddableServiceFactory: EmbeddableServiceFactory = ({ startPlugins }) => { + const { + embeddable: { getEmbeddableFactory, getEmbeddableFactories, getStateTransfer, EmbeddablePanel }, + } = startPlugins; + + return { + getEmbeddableFactory, + getEmbeddableFactories, + getStateTransfer, + EmbeddablePanel, + }; +}; diff --git a/src/plugins/dashboard/public/services/embeddable/types.ts b/src/plugins/dashboard/public/services/embeddable/types.ts new file mode 100644 index 0000000000000..ef24db7c2e624 --- /dev/null +++ b/src/plugins/dashboard/public/services/embeddable/types.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { EmbeddableStart } from '@kbn/embeddable-plugin/public'; + +export interface DashboardEmbeddableService { + getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory']; + getEmbeddableFactories: EmbeddableStart['getEmbeddableFactories']; + getStateTransfer: EmbeddableStart['getStateTransfer']; + EmbeddablePanel: EmbeddableStart['EmbeddablePanel']; +} diff --git a/src/plugins/dashboard/public/services/http/http.stub.ts b/src/plugins/dashboard/public/services/http/http.stub.ts new file mode 100644 index 0000000000000..37abd532f7d9d --- /dev/null +++ b/src/plugins/dashboard/public/services/http/http.stub.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { coreMock } from '@kbn/core/public/mocks'; +import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import { DashboardHTTPService } from './types'; + +type HttpServiceFactory = PluginServiceFactory; + +export const httpServiceFactory: HttpServiceFactory = () => { + const serviceMock = coreMock.createStart(); + + return { + basePath: serviceMock.http.basePath, + }; +}; diff --git a/src/plugins/dashboard/public/services/http/http_service.ts b/src/plugins/dashboard/public/services/http/http_service.ts new file mode 100644 index 0000000000000..26ccc3a943f95 --- /dev/null +++ b/src/plugins/dashboard/public/services/http/http_service.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import type { DashboardStartDependencies } from '../../plugin'; +import type { DashboardHTTPService } from './types'; + +export type HttpServiceFactory = KibanaPluginServiceFactory< + DashboardHTTPService, + DashboardStartDependencies +>; +export const httpServiceFactory: HttpServiceFactory = ({ coreStart }) => { + const { + http: { basePath }, + } = coreStart; + + return { + basePath, + }; +}; diff --git a/src/plugins/dashboard/public/services/data.ts b/src/plugins/dashboard/public/services/http/types.ts similarity index 72% rename from src/plugins/dashboard/public/services/data.ts rename to src/plugins/dashboard/public/services/http/types.ts index 1e0c02cc3d401..e92bac92f6286 100644 --- a/src/plugins/dashboard/public/services/data.ts +++ b/src/plugins/dashboard/public/services/http/types.ts @@ -6,5 +6,8 @@ * Side Public License, v 1. */ -export type { Query, TimeRange, Filter } from '@kbn/es-query'; -export * from '@kbn/data-plugin/public'; +import type { CoreSetup } from '@kbn/core/public'; + +export interface DashboardHTTPService { + basePath: CoreSetup['http']['basePath']; +} diff --git a/src/plugins/dashboard/public/services/initializer_context/initializer_context.stub.ts b/src/plugins/dashboard/public/services/initializer_context/initializer_context.stub.ts new file mode 100644 index 0000000000000..539b3f5e9a7a3 --- /dev/null +++ b/src/plugins/dashboard/public/services/initializer_context/initializer_context.stub.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import { DashboardInitializerContextService } from './types'; + +const defaultDashboardInitializerContext: DashboardInitializerContextService = { + kibanaVersion: 'test.kibana.version', + allowByValueEmbeddables: true, +}; + +type InitializerContextServiceFactory = PluginServiceFactory; + +export const initializerContextServiceFactory: InitializerContextServiceFactory = () => { + return { + ...defaultDashboardInitializerContext, + }; +}; diff --git a/src/plugins/dashboard/public/services/initializer_context/initializer_context_service.ts b/src/plugins/dashboard/public/services/initializer_context/initializer_context_service.ts new file mode 100644 index 0000000000000..fb0907d8e5b07 --- /dev/null +++ b/src/plugins/dashboard/public/services/initializer_context/initializer_context_service.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { DashboardFeatureFlagConfig } from '../..'; +import { DashboardPluginServiceParams } from '../types'; +import { DashboardInitializerContextService } from './types'; + +export type InitializerContextServiceFactory = ( + params: DashboardPluginServiceParams +) => DashboardInitializerContextService; + +export const initializerContextServiceFactory: InitializerContextServiceFactory = ({ + initContext, +}) => { + const { + env: { + packageInfo: { version }, + }, + config: { get }, + } = initContext; + + return { + kibanaVersion: version, + allowByValueEmbeddables: get().allowByValueEmbeddables, + }; +}; diff --git a/src/plugins/dashboard/public/services/embeddable_test_samples.ts b/src/plugins/dashboard/public/services/initializer_context/types.ts similarity index 75% rename from src/plugins/dashboard/public/services/embeddable_test_samples.ts rename to src/plugins/dashboard/public/services/initializer_context/types.ts index 0c314893230b6..e6a5e67b78eb4 100644 --- a/src/plugins/dashboard/public/services/embeddable_test_samples.ts +++ b/src/plugins/dashboard/public/services/initializer_context/types.ts @@ -6,4 +6,7 @@ * Side Public License, v 1. */ -export * from '@kbn/embeddable-plugin/public/lib/test_samples'; +export interface DashboardInitializerContextService { + kibanaVersion: string; + allowByValueEmbeddables: boolean; +} diff --git a/src/plugins/dashboard/public/services/kibana_react.ts b/src/plugins/dashboard/public/services/kibana_react.ts deleted file mode 100644 index 9ef6416ba44ea..0000000000000 --- a/src/plugins/dashboard/public/services/kibana_react.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export type { - KibanaReactContext, - KibanaReactContextValue, - ExitFullScreenButtonProps, -} from '@kbn/kibana-react-plugin/public'; -export { - context, - useKibana, - withKibana, - toMountPoint, - TableListView, - reactToUiComponent, - ExitFullScreenButton, - KibanaContextProvider, - KibanaThemeProvider, -} from '@kbn/kibana-react-plugin/public'; diff --git a/src/plugins/dashboard/public/services/kibana_utils.ts b/src/plugins/dashboard/public/services/kibana_utils.ts deleted file mode 100644 index 6e54cff517135..0000000000000 --- a/src/plugins/dashboard/public/services/kibana_utils.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export type { - ISyncStateRef, - IKbnUrlStateStorage, - ReduxLikeStateContainer, -} from '@kbn/kibana-utils-plugin/public'; -export { - Storage, - unhashUrl, - syncState, - getQueryParams, - setStateToKbnUrl, - removeQueryParam, - withNotifyOnErrors, - createKbnUrlTracker, - SavedObjectNotFound, - createStateContainer, - createKbnUrlStateStorage, -} from '@kbn/kibana-utils-plugin/public'; diff --git a/src/plugins/dashboard/public/services/navigation/navigation.stub.ts b/src/plugins/dashboard/public/services/navigation/navigation.stub.ts new file mode 100644 index 0000000000000..38c2ef848580d --- /dev/null +++ b/src/plugins/dashboard/public/services/navigation/navigation.stub.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import { navigationPluginMock } from '@kbn/navigation-plugin/public/mocks'; +import { DashboardNavigationService } from './types'; + +type NavigationServiceFactory = PluginServiceFactory; + +export const navigationServiceFactory: NavigationServiceFactory = () => { + const pluginMock = navigationPluginMock.createStartContract(); + + return { + TopNavMenu: pluginMock.ui.TopNavMenu, + }; +}; diff --git a/src/plugins/dashboard/public/services/navigation/navigation_service.ts b/src/plugins/dashboard/public/services/navigation/navigation_service.ts new file mode 100644 index 0000000000000..d2120f649650e --- /dev/null +++ b/src/plugins/dashboard/public/services/navigation/navigation_service.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import type { DashboardStartDependencies } from '../../plugin'; +import type { DashboardNavigationService } from './types'; + +export type NavigationServiceFactory = KibanaPluginServiceFactory< + DashboardNavigationService, + DashboardStartDependencies +>; +export const navigationServiceFactory: NavigationServiceFactory = ({ startPlugins }) => { + const { + navigation: { + ui: { TopNavMenu }, + }, + } = startPlugins; + + return { + TopNavMenu, + }; +}; diff --git a/src/plugins/dashboard/public/services/navigation.ts b/src/plugins/dashboard/public/services/navigation/types.ts similarity index 67% rename from src/plugins/dashboard/public/services/navigation.ts rename to src/plugins/dashboard/public/services/navigation/types.ts index 9b5b4d515d393..08e234af16bbb 100644 --- a/src/plugins/dashboard/public/services/navigation.ts +++ b/src/plugins/dashboard/public/services/navigation/types.ts @@ -6,4 +6,8 @@ * Side Public License, v 1. */ -export type { NavigationPublicPluginStart } from '@kbn/navigation-plugin/public'; +import type { NavigationPublicPluginStart } from '@kbn/navigation-plugin/public'; + +export interface DashboardNavigationService { + TopNavMenu: NavigationPublicPluginStart['ui']['TopNavMenu']; +} diff --git a/src/plugins/dashboard/public/services/notifications/notifications.stub.ts b/src/plugins/dashboard/public/services/notifications/notifications.stub.ts new file mode 100644 index 0000000000000..83381991fa6f6 --- /dev/null +++ b/src/plugins/dashboard/public/services/notifications/notifications.stub.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { notificationServiceMock } from '@kbn/core-notifications-browser-mocks'; +import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import { DashboardNotificationsService } from './types'; + +type NotificationsServiceFactory = PluginServiceFactory; + +export const notificationsServiceFactory: NotificationsServiceFactory = () => { + const pluginMock = notificationServiceMock.createStartContract(); + + return { + toasts: pluginMock.toasts, + }; +}; diff --git a/src/plugins/dashboard/public/services/notifications/notifications_service.ts b/src/plugins/dashboard/public/services/notifications/notifications_service.ts new file mode 100644 index 0000000000000..f3728c79cf64d --- /dev/null +++ b/src/plugins/dashboard/public/services/notifications/notifications_service.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import type { DashboardStartDependencies } from '../../plugin'; +import type { DashboardNotificationsService } from './types'; + +export type NotificationsServiceFactory = KibanaPluginServiceFactory< + DashboardNotificationsService, + DashboardStartDependencies +>; + +export const notificationsServiceFactory: NotificationsServiceFactory = ({ coreStart }) => { + const { + notifications: { toasts }, + } = coreStart; + + return { + toasts, + }; +}; diff --git a/src/plugins/dashboard/public/services/notifications/types.ts b/src/plugins/dashboard/public/services/notifications/types.ts new file mode 100644 index 0000000000000..4d439eaa73d6b --- /dev/null +++ b/src/plugins/dashboard/public/services/notifications/types.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { CoreStart } from '@kbn/core/public'; + +export interface DashboardNotificationsService { + toasts: CoreStart['notifications']['toasts']; +} diff --git a/src/plugins/dashboard/public/services/overlays/overlays.stub.ts b/src/plugins/dashboard/public/services/overlays/overlays.stub.ts new file mode 100644 index 0000000000000..9e4b9ffcc966d --- /dev/null +++ b/src/plugins/dashboard/public/services/overlays/overlays.stub.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { overlayServiceMock } from '@kbn/core-overlays-browser-mocks'; +import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import { DashboardOverlaysService } from './types'; + +type OverlaysServiceFactory = PluginServiceFactory; + +export const overlaysServiceFactory: OverlaysServiceFactory = () => { + const pluginMock = overlayServiceMock.createStartContract(); + + return { + banners: pluginMock.banners, + openConfirm: pluginMock.openConfirm, + openFlyout: pluginMock.openFlyout, + openModal: pluginMock.openModal, + }; +}; diff --git a/src/plugins/dashboard/public/services/overlays/overlays_service.ts b/src/plugins/dashboard/public/services/overlays/overlays_service.ts new file mode 100644 index 0000000000000..45ad800fe8f0a --- /dev/null +++ b/src/plugins/dashboard/public/services/overlays/overlays_service.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import type { DashboardStartDependencies } from '../../plugin'; +import type { DashboardOverlaysService } from './types'; + +export type OverlaysServiceFactory = KibanaPluginServiceFactory< + DashboardOverlaysService, + DashboardStartDependencies +>; + +export const overlaysServiceFactory: OverlaysServiceFactory = ({ coreStart }) => { + const { + overlays: { banners, openConfirm, openFlyout, openModal }, + } = coreStart; + + return { + banners, + openConfirm, + openFlyout, + openModal, + }; +}; diff --git a/src/plugins/dashboard/public/services/overlays/types.ts b/src/plugins/dashboard/public/services/overlays/types.ts new file mode 100644 index 0000000000000..3d29aa30e6117 --- /dev/null +++ b/src/plugins/dashboard/public/services/overlays/types.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { CoreStart } from '@kbn/core/public'; + +export interface DashboardOverlaysService { + banners: CoreStart['overlays']['banners']; + openConfirm: CoreStart['overlays']['openConfirm']; + openFlyout: CoreStart['overlays']['openFlyout']; + openModal: CoreStart['overlays']['openModal']; +} diff --git a/src/plugins/dashboard/public/services/plugin_services.stub.ts b/src/plugins/dashboard/public/services/plugin_services.stub.ts new file mode 100644 index 0000000000000..c703b8b6767ac --- /dev/null +++ b/src/plugins/dashboard/public/services/plugin_services.stub.ts @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { + PluginServiceProviders, + PluginServiceProvider, + PluginServiceRegistry, +} from '@kbn/presentation-util-plugin/public'; + +import { DashboardServices } from './types'; + +import { analyticsServiceFactory } from './analytics/analytics.stub'; +import { applicationServiceFactory } from './application/application.stub'; +import { chromeServiceFactory } from './chrome/chrome.stub'; +import { coreContextServiceFactory } from './core_context/core_context.stub'; +import { dashboardCapabilitiesServiceFactory } from './dashboard_capabilities/dashboard_capabilities.stub'; +import { dashboardSessionStorageServiceFactory } from './dashboard_session_storage/dashboard_session_storage.stub'; +import { dataServiceFactory } from './data/data.stub'; +import { dataViewEditorServiceFactory } from './data_view_editor/data_view_editor.stub'; +import { documentationLinksServiceFactory } from './documentation_links/documentation_links.stub'; +import { embeddableServiceFactory } from './embeddable/embeddable.stub'; +import { httpServiceFactory } from './http/http.stub'; +import { initializerContextServiceFactory } from './initializer_context/initializer_context.stub'; +import { navigationServiceFactory } from './navigation/navigation.stub'; +import { notificationsServiceFactory } from './notifications/notifications.stub'; +import { overlaysServiceFactory } from './overlays/overlays.stub'; +import { savedObjectsServiceFactory } from './saved_objects/saved_objects.stub'; +import { savedObjectsTaggingServiceFactory } from './saved_objects_tagging/saved_objects_tagging.stub'; +import { screenshotModeServiceFactory } from './screenshot_mode/screenshot_mode.stub'; +import { settingsServiceFactory } from './settings/settings.stub'; +import { shareServiceFactory } from './share/share.stub'; +import { usageCollectionServiceFactory } from './usage_collection/usage_collection.stub'; +import { spacesServiceFactory } from './spaces/spaces.stub'; +import { urlForwardingServiceFactory } from './url_forwarding/url_fowarding.stub'; +import { visualizationsServiceFactory } from './visualizations/visualizations.stub'; + +export const providers: PluginServiceProviders = { + analytics: new PluginServiceProvider(analyticsServiceFactory), + application: new PluginServiceProvider(applicationServiceFactory), + chrome: new PluginServiceProvider(chromeServiceFactory), + coreContext: new PluginServiceProvider(coreContextServiceFactory), + dashboardCapabilities: new PluginServiceProvider(dashboardCapabilitiesServiceFactory), + dashboardSessionStorage: new PluginServiceProvider(dashboardSessionStorageServiceFactory), + data: new PluginServiceProvider(dataServiceFactory), + dataViewEditor: new PluginServiceProvider(dataViewEditorServiceFactory), + documentationLinks: new PluginServiceProvider(documentationLinksServiceFactory), + embeddable: new PluginServiceProvider(embeddableServiceFactory), + http: new PluginServiceProvider(httpServiceFactory), + initializerContext: new PluginServiceProvider(initializerContextServiceFactory), + navigation: new PluginServiceProvider(navigationServiceFactory), + notifications: new PluginServiceProvider(notificationsServiceFactory), + overlays: new PluginServiceProvider(overlaysServiceFactory), + savedObjects: new PluginServiceProvider(savedObjectsServiceFactory), + savedObjectsTagging: new PluginServiceProvider(savedObjectsTaggingServiceFactory), + screenshotMode: new PluginServiceProvider(screenshotModeServiceFactory), + settings: new PluginServiceProvider(settingsServiceFactory), + share: new PluginServiceProvider(shareServiceFactory), + spaces: new PluginServiceProvider(spacesServiceFactory), + urlForwarding: new PluginServiceProvider(urlForwardingServiceFactory), + usageCollection: new PluginServiceProvider(usageCollectionServiceFactory), + visualizations: new PluginServiceProvider(visualizationsServiceFactory), +}; + +export const registry = new PluginServiceRegistry(providers); diff --git a/src/plugins/dashboard/public/services/plugin_services.ts b/src/plugins/dashboard/public/services/plugin_services.ts new file mode 100644 index 0000000000000..421b5e75c482c --- /dev/null +++ b/src/plugins/dashboard/public/services/plugin_services.ts @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { + PluginServiceProviders, + PluginServiceProvider, + PluginServiceRegistry, + PluginServices, +} from '@kbn/presentation-util-plugin/public'; + +import { DashboardPluginServiceParams, DashboardServices } from './types'; + +import { applicationServiceFactory } from './application/application_service'; +import { chromeServiceFactory } from './chrome/chrome_service'; +import { coreContextServiceFactory } from './core_context/core_context_service'; +import { dashboardCapabilitiesServiceFactory } from './dashboard_capabilities/dashboard_capabilities_service'; +import { dashboardSessionStorageServiceFactory } from './dashboard_session_storage/dashboard_session_storage_service'; +import { dataServiceFactory } from './data/data_service'; +import { dataViewEditorServiceFactory } from './data_view_editor/data_view_editor_service'; +import { documentationLinksServiceFactory } from './documentation_links/documentation_links_service'; +import { embeddableServiceFactory } from './embeddable/embeddable_service'; +import { httpServiceFactory } from './http/http_service'; +import { initializerContextServiceFactory } from './initializer_context/initializer_context_service'; +import { navigationServiceFactory } from './navigation/navigation_service'; +import { notificationsServiceFactory } from './notifications/notifications_service'; +import { overlaysServiceFactory } from './overlays/overlays_service'; +import { screenshotModeServiceFactory } from './screenshot_mode/screenshot_mode_service'; +import { savedObjectsServiceFactory } from './saved_objects/saved_objects_service'; +import { savedObjectsTaggingServiceFactory } from './saved_objects_tagging/saved_objects_tagging_service'; +import { settingsServiceFactory } from './settings/settings_service'; +import { shareServiceFactory } from './share/share_services'; +import { spacesServiceFactory } from './spaces/spaces_service'; +import { urlForwardingServiceFactory } from './url_forwarding/url_forwarding_service'; +import { visualizationsServiceFactory } from './visualizations/visualizations_service'; +import { usageCollectionServiceFactory } from './usage_collection/usage_collection_service'; +import { analyticsServiceFactory } from './analytics/analytics_service'; + +const providers: PluginServiceProviders = { + analytics: new PluginServiceProvider(analyticsServiceFactory), + application: new PluginServiceProvider(applicationServiceFactory), + chrome: new PluginServiceProvider(chromeServiceFactory), + coreContext: new PluginServiceProvider(coreContextServiceFactory), + dashboardCapabilities: new PluginServiceProvider(dashboardCapabilitiesServiceFactory), + dashboardSessionStorage: new PluginServiceProvider(dashboardSessionStorageServiceFactory, [ + 'notifications', + 'spaces', + ]), + data: new PluginServiceProvider(dataServiceFactory), + dataViewEditor: new PluginServiceProvider(dataViewEditorServiceFactory), + documentationLinks: new PluginServiceProvider(documentationLinksServiceFactory), + embeddable: new PluginServiceProvider(embeddableServiceFactory), + http: new PluginServiceProvider(httpServiceFactory), + initializerContext: new PluginServiceProvider(initializerContextServiceFactory), + navigation: new PluginServiceProvider(navigationServiceFactory), + notifications: new PluginServiceProvider(notificationsServiceFactory), + overlays: new PluginServiceProvider(overlaysServiceFactory), + savedObjects: new PluginServiceProvider(savedObjectsServiceFactory), + savedObjectsTagging: new PluginServiceProvider(savedObjectsTaggingServiceFactory), + screenshotMode: new PluginServiceProvider(screenshotModeServiceFactory), + settings: new PluginServiceProvider(settingsServiceFactory), + share: new PluginServiceProvider(shareServiceFactory), + spaces: new PluginServiceProvider(spacesServiceFactory), + urlForwarding: new PluginServiceProvider(urlForwardingServiceFactory), + usageCollection: new PluginServiceProvider(usageCollectionServiceFactory), + visualizations: new PluginServiceProvider(visualizationsServiceFactory), +}; + +export const pluginServices = new PluginServices(); + +export const registry = new PluginServiceRegistry( + providers +); diff --git a/src/plugins/dashboard/public/services/presentation_util.ts b/src/plugins/dashboard/public/services/presentation_util.ts deleted file mode 100644 index 0375a87387e08..0000000000000 --- a/src/plugins/dashboard/public/services/presentation_util.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export type { PresentationUtilPluginStart } from '@kbn/presentation-util-plugin/public'; -export { LazyDashboardPicker, withSuspense, useLabs } from '@kbn/presentation-util-plugin/public'; diff --git a/src/plugins/dashboard/public/services/saved_object_loader.ts b/src/plugins/dashboard/public/services/saved_object_loader.ts index 780daa2939aa4..cf4f1742e96e4 100644 --- a/src/plugins/dashboard/public/services/saved_object_loader.ts +++ b/src/plugins/dashboard/public/services/saved_object_loader.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { upperFirst } from 'lodash'; import { SavedObjectsClientContract, SavedObjectsFindOptions, @@ -13,7 +14,6 @@ import { SavedObjectReference, } from '@kbn/core/public'; import { SavedObject } from '@kbn/saved-objects-plugin/public'; -import { upperFirst } from './string_utils'; /** * @deprecated diff --git a/src/plugins/dashboard/public/services/saved_objects.ts b/src/plugins/dashboard/public/services/saved_objects.ts deleted file mode 100644 index 79be111f94371..0000000000000 --- a/src/plugins/dashboard/public/services/saved_objects.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export type { - SaveResult, - SavedObject, - SavedObjectsStart, - SavedObjectSaveOpts, -} from '@kbn/saved-objects-plugin/public'; -export { - showSaveModal, - SavedObjectSaveModal, - getSavedObjectFinder, -} from '@kbn/saved-objects-plugin/public'; -export { SavedObjectLoader } from './saved_object_loader'; -export type { SavedObjectLoaderFindOptions } from './saved_object_loader'; diff --git a/src/plugins/dashboard/public/services/saved_objects/saved_objects.stub.ts b/src/plugins/dashboard/public/services/saved_objects/saved_objects.stub.ts new file mode 100644 index 0000000000000..f26e36392603f --- /dev/null +++ b/src/plugins/dashboard/public/services/saved_objects/saved_objects.stub.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { savedObjectsServiceMock } from '@kbn/core-saved-objects-browser-mocks'; +import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import { DashboardSavedObjectsService } from './types'; + +type SavedObjectsServiceFactory = PluginServiceFactory; + +export const savedObjectsServiceFactory: SavedObjectsServiceFactory = () => { + const pluginMock = savedObjectsServiceMock.createStartContract(); + + return { + client: pluginMock.client, + }; +}; diff --git a/src/plugins/dashboard/public/services/saved_objects/saved_objects_service.ts b/src/plugins/dashboard/public/services/saved_objects/saved_objects_service.ts new file mode 100644 index 0000000000000..3fff4d9e1c361 --- /dev/null +++ b/src/plugins/dashboard/public/services/saved_objects/saved_objects_service.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import type { DashboardStartDependencies } from '../../plugin'; +import type { DashboardSavedObjectsService } from './types'; + +export type SavedObjectsServiceFactory = KibanaPluginServiceFactory< + DashboardSavedObjectsService, + DashboardStartDependencies +>; + +export const savedObjectsServiceFactory: SavedObjectsServiceFactory = ({ coreStart }) => { + const { + savedObjects: { client }, + } = coreStart; + + return { + client, + }; +}; diff --git a/src/plugins/dashboard/public/services/data_views.ts b/src/plugins/dashboard/public/services/saved_objects/types.ts similarity index 70% rename from src/plugins/dashboard/public/services/data_views.ts rename to src/plugins/dashboard/public/services/saved_objects/types.ts index 21e6a2e9e645c..d7d06131f32cb 100644 --- a/src/plugins/dashboard/public/services/data_views.ts +++ b/src/plugins/dashboard/public/services/saved_objects/types.ts @@ -6,4 +6,8 @@ * Side Public License, v 1. */ -export * from '@kbn/data-views-plugin/public'; +import type { CoreStart } from '@kbn/core/public'; + +export interface DashboardSavedObjectsService { + client: CoreStart['savedObjects']['client']; +} diff --git a/src/plugins/dashboard/public/services/saved_objects_tagging/saved_objects_tagging.stub.ts b/src/plugins/dashboard/public/services/saved_objects_tagging/saved_objects_tagging.stub.ts new file mode 100644 index 0000000000000..d526eedbc1e47 --- /dev/null +++ b/src/plugins/dashboard/public/services/saved_objects_tagging/saved_objects_tagging.stub.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import { taggingApiMock } from '@kbn/saved-objects-tagging-oss-plugin/public/mocks'; +import { DashboardSavedObjectsTaggingService } from './types'; + +type SavedObjectsTaggingServiceFactory = PluginServiceFactory; + +export const savedObjectsTaggingServiceFactory: SavedObjectsTaggingServiceFactory = () => { + const pluginMock = taggingApiMock.createUi(); + + return { + hasApi: true, + + // I'm not defining components so that I don't have to update the snapshot of `save_modal.test` + // However, if it's ever necessary, it can be done via: `components: pluginMock.components`, + getSearchBarFilter: pluginMock.getSearchBarFilter, + getTableColumnDefinition: pluginMock.getTableColumnDefinition, + hasTagDecoration: pluginMock.hasTagDecoration, + parseSearchQuery: pluginMock.parseSearchQuery, + }; +}; diff --git a/src/plugins/dashboard/public/services/saved_objects_tagging/saved_objects_tagging_service.ts b/src/plugins/dashboard/public/services/saved_objects_tagging/saved_objects_tagging_service.ts new file mode 100644 index 0000000000000..7e252ed79d7b7 --- /dev/null +++ b/src/plugins/dashboard/public/services/saved_objects_tagging/saved_objects_tagging_service.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import type { DashboardStartDependencies } from '../../plugin'; +import type { DashboardSavedObjectsTaggingService } from './types'; + +export type SavedObjectsTaggingServiceFactory = KibanaPluginServiceFactory< + DashboardSavedObjectsTaggingService, + DashboardStartDependencies +>; +export const savedObjectsTaggingServiceFactory: SavedObjectsTaggingServiceFactory = ({ + startPlugins, +}) => { + const { savedObjectsTaggingOss } = startPlugins; + if (!savedObjectsTaggingOss) return { hasApi: false }; + + const { getTaggingApi } = savedObjectsTaggingOss; + const taggingApi = getTaggingApi(); + if (!taggingApi) return { hasApi: false }; + + const { + ui: { + components, + getSearchBarFilter, + getTableColumnDefinition, + hasTagDecoration, + parseSearchQuery, + }, + } = taggingApi; + + return { + hasApi: true, + components, + getSearchBarFilter, + getTableColumnDefinition, + hasTagDecoration, + parseSearchQuery, + }; +}; diff --git a/src/plugins/dashboard/public/services/saved_objects_tagging/types.ts b/src/plugins/dashboard/public/services/saved_objects_tagging/types.ts new file mode 100644 index 0000000000000..067814f8f44ae --- /dev/null +++ b/src/plugins/dashboard/public/services/saved_objects_tagging/types.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { SavedObjectsTaggingApi } from '@kbn/saved-objects-tagging-oss-plugin/public'; + +export interface DashboardSavedObjectsTaggingService { + hasApi: boolean; // remove this once the entire service is optional + + components?: SavedObjectsTaggingApi['ui']['components']; + getSearchBarFilter?: SavedObjectsTaggingApi['ui']['getSearchBarFilter']; + getTableColumnDefinition?: SavedObjectsTaggingApi['ui']['getTableColumnDefinition']; + hasTagDecoration?: SavedObjectsTaggingApi['ui']['hasTagDecoration']; + parseSearchQuery?: SavedObjectsTaggingApi['ui']['parseSearchQuery']; +} diff --git a/src/plugins/dashboard/public/services/saved_objects_tagging_oss.ts b/src/plugins/dashboard/public/services/saved_objects_tagging_oss.ts deleted file mode 100644 index b660cae8a2ac5..0000000000000 --- a/src/plugins/dashboard/public/services/saved_objects_tagging_oss.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export type { - SavedObjectsTaggingApi, - TagDecoratedSavedObject, - SavedObjectTagDecoratorTypeGuard, - SavedObjectTaggingOssPluginStart, -} from '@kbn/saved-objects-tagging-oss-plugin/public'; diff --git a/src/plugins/dashboard/public/services/screenshot_mode.ts b/src/plugins/dashboard/public/services/screenshot_mode.ts deleted file mode 100644 index 7447737d1c8bf..0000000000000 --- a/src/plugins/dashboard/public/services/screenshot_mode.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export type { - ScreenshotModePluginStart, - ScreenshotModePluginSetup, -} from '@kbn/screenshot-mode-plugin/public'; diff --git a/src/plugins/dashboard/public/services/screenshot_mode/screenshot_mode.stub.ts b/src/plugins/dashboard/public/services/screenshot_mode/screenshot_mode.stub.ts new file mode 100644 index 0000000000000..2e731a63d77a3 --- /dev/null +++ b/src/plugins/dashboard/public/services/screenshot_mode/screenshot_mode.stub.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import { screenshotModePluginMock } from '@kbn/screenshot-mode-plugin/public/mocks'; +import { DashboardScreenshotModeService } from './types'; + +type ScreenshotModeServiceFactory = PluginServiceFactory; + +export const screenshotModeServiceFactory: ScreenshotModeServiceFactory = () => { + const pluginMock = screenshotModePluginMock.createStartContract(); + + return { + isScreenshotMode: pluginMock.isScreenshotMode, + getScreenshotContext: pluginMock.getScreenshotContext, + }; +}; diff --git a/src/plugins/dashboard/public/services/screenshot_mode/screenshot_mode_service.ts b/src/plugins/dashboard/public/services/screenshot_mode/screenshot_mode_service.ts new file mode 100644 index 0000000000000..22e74ba6b5e7f --- /dev/null +++ b/src/plugins/dashboard/public/services/screenshot_mode/screenshot_mode_service.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import type { DashboardStartDependencies } from '../../plugin'; +import type { DashboardScreenshotModeService } from './types'; + +export type ScreenshotModeServiceFactory = KibanaPluginServiceFactory< + DashboardScreenshotModeService, + DashboardStartDependencies +>; +export const screenshotModeServiceFactory: ScreenshotModeServiceFactory = ({ startPlugins }) => { + const { + screenshotMode: { isScreenshotMode, getScreenshotContext }, + } = startPlugins; + + return { + isScreenshotMode, + getScreenshotContext, + }; +}; diff --git a/src/plugins/dashboard/public/services/screenshot_mode/types.ts b/src/plugins/dashboard/public/services/screenshot_mode/types.ts new file mode 100644 index 0000000000000..6066d27a01b95 --- /dev/null +++ b/src/plugins/dashboard/public/services/screenshot_mode/types.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { ScreenshotModePluginStart } from '@kbn/screenshot-mode-plugin/public'; + +export interface DashboardScreenshotModeService { + isScreenshotMode: ScreenshotModePluginStart['isScreenshotMode']; + getScreenshotContext: ScreenshotModePluginStart['getScreenshotContext']; +} diff --git a/src/plugins/dashboard/public/services/settings/settings.stub.ts b/src/plugins/dashboard/public/services/settings/settings.stub.ts new file mode 100644 index 0000000000000..3b93cb65b4a73 --- /dev/null +++ b/src/plugins/dashboard/public/services/settings/settings.stub.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { themeServiceMock } from '@kbn/core-theme-browser-mocks'; +import { uiSettingsServiceMock } from '@kbn/core-ui-settings-browser-mocks'; +import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import { DashboardSettingsService } from './types'; + +type SettingsServiceFactory = PluginServiceFactory; + +export const settingsServiceFactory: SettingsServiceFactory = () => { + return { + uiSettings: uiSettingsServiceMock.createStartContract(), + theme: themeServiceMock.createStartContract(), + isProjectEnabledInLabs: jest.fn().mockReturnValue(true), + }; +}; diff --git a/src/plugins/dashboard/public/services/settings/settings_service.ts b/src/plugins/dashboard/public/services/settings/settings_service.ts new file mode 100644 index 0000000000000..71064991d0278 --- /dev/null +++ b/src/plugins/dashboard/public/services/settings/settings_service.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import type { DashboardStartDependencies } from '../../plugin'; +import type { DashboardSettingsService } from './types'; + +export type SettingsServiceFactory = KibanaPluginServiceFactory< + DashboardSettingsService, + DashboardStartDependencies +>; + +export const settingsServiceFactory: SettingsServiceFactory = ({ coreStart, startPlugins }) => { + const { uiSettings, theme } = coreStart; + + const { + presentationUtil: { + labsService: { isProjectEnabled }, + }, + } = startPlugins; + + return { + uiSettings, + theme, + isProjectEnabledInLabs: isProjectEnabled, + }; +}; diff --git a/src/plugins/dashboard/public/services/settings/types.ts b/src/plugins/dashboard/public/services/settings/types.ts new file mode 100644 index 0000000000000..299981ff6093d --- /dev/null +++ b/src/plugins/dashboard/public/services/settings/types.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { CoreStart } from '@kbn/core/public'; +import type { PresentationLabsService } from '@kbn/presentation-util-plugin/public'; + +export interface DashboardSettingsService { + uiSettings: CoreStart['uiSettings']; + theme: CoreStart['theme']; + isProjectEnabledInLabs: PresentationLabsService['isProjectEnabled']; +} diff --git a/src/plugins/dashboard/public/services/share.ts b/src/plugins/dashboard/public/services/share.ts deleted file mode 100644 index 21bbe9e7a9c83..0000000000000 --- a/src/plugins/dashboard/public/services/share.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export type { SharePluginStart, SharePluginSetup } from '@kbn/share-plugin/public'; -export { downloadMultipleAs } from '@kbn/share-plugin/public'; diff --git a/src/plugins/dashboard/public/services/share/share.stub.ts b/src/plugins/dashboard/public/services/share/share.stub.ts new file mode 100644 index 0000000000000..70c7e374a393e --- /dev/null +++ b/src/plugins/dashboard/public/services/share/share.stub.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { sharePluginMock } from '@kbn/share-plugin/public/mocks'; +import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import { DashboardShareService } from './types'; + +type ShareServiceFactory = PluginServiceFactory; + +export const shareServiceFactory: ShareServiceFactory = () => { + const pluginMock = sharePluginMock.createStartContract(); + + return { + toggleShareContextMenu: pluginMock.toggleShareContextMenu, + }; +}; diff --git a/src/plugins/dashboard/public/services/share/share_services.ts b/src/plugins/dashboard/public/services/share/share_services.ts new file mode 100644 index 0000000000000..b96ba0d8f412e --- /dev/null +++ b/src/plugins/dashboard/public/services/share/share_services.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import type { DashboardStartDependencies } from '../../plugin'; +import type { DashboardShareService } from './types'; + +export type ShareServiceFactory = KibanaPluginServiceFactory< + DashboardShareService, + DashboardStartDependencies +>; + +export const shareServiceFactory: ShareServiceFactory = ({ startPlugins }) => { + const { share } = startPlugins; + if (!share) return {}; + + const { toggleShareContextMenu } = share; + + return { + toggleShareContextMenu, + }; +}; diff --git a/src/plugins/dashboard/public/services/share/types.ts b/src/plugins/dashboard/public/services/share/types.ts new file mode 100644 index 0000000000000..5920b4b3bcbc4 --- /dev/null +++ b/src/plugins/dashboard/public/services/share/types.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { SharePluginStart } from '@kbn/share-plugin/public'; + +export interface DashboardShareService { + toggleShareContextMenu?: SharePluginStart['toggleShareContextMenu']; +} diff --git a/src/plugins/dashboard/public/services/spaces/spaces.stub.ts b/src/plugins/dashboard/public/services/spaces/spaces.stub.ts new file mode 100644 index 0000000000000..d89d5ea6b8356 --- /dev/null +++ b/src/plugins/dashboard/public/services/spaces/spaces.stub.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import { spacesPluginMock } from '@kbn/spaces-plugin/public/mocks'; +import { DashboardSpacesService } from './types'; + +type SpacesServiceFactory = PluginServiceFactory; + +export const spacesServiceFactory: SpacesServiceFactory = () => { + const pluginMock = spacesPluginMock.createStartContract(); + + return { + getActiveSpace$: pluginMock.getActiveSpace$, + getLegacyUrlConflict: pluginMock.ui.components.getLegacyUrlConflict, + redirectLegacyUrl: pluginMock.ui.redirectLegacyUrl, + }; +}; diff --git a/src/plugins/dashboard/public/services/spaces/spaces_service.ts b/src/plugins/dashboard/public/services/spaces/spaces_service.ts new file mode 100644 index 0000000000000..35f1d6671ee1d --- /dev/null +++ b/src/plugins/dashboard/public/services/spaces/spaces_service.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import type { DashboardStartDependencies } from '../../plugin'; +import type { DashboardSpacesService } from './types'; + +export type SpacesServiceFactory = KibanaPluginServiceFactory< + DashboardSpacesService, + DashboardStartDependencies +>; +export const spacesServiceFactory: SpacesServiceFactory = ({ startPlugins }) => { + const { spaces } = startPlugins; + if (!spaces || !spaces.ui) return {}; + + const { + getActiveSpace$, + ui: { + components: { getLegacyUrlConflict }, + redirectLegacyUrl, + }, + } = spaces; + return { + getActiveSpace$, + getLegacyUrlConflict, + redirectLegacyUrl, + }; +}; diff --git a/src/plugins/dashboard/public/services/spaces.ts b/src/plugins/dashboard/public/services/spaces/types.ts similarity index 52% rename from src/plugins/dashboard/public/services/spaces.ts rename to src/plugins/dashboard/public/services/spaces/types.ts index 57b987be5cbaa..2124300cb6633 100644 --- a/src/plugins/dashboard/public/services/spaces.ts +++ b/src/plugins/dashboard/public/services/spaces/types.ts @@ -6,4 +6,10 @@ * Side Public License, v 1. */ -export type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; +import type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; + +export interface DashboardSpacesService { + getActiveSpace$?: SpacesPluginStart['getActiveSpace$']; + getLegacyUrlConflict?: SpacesPluginStart['ui']['components']['getLegacyUrlConflict']; + redirectLegacyUrl?: SpacesPluginStart['ui']['redirectLegacyUrl']; +} diff --git a/src/plugins/dashboard/public/services/string_utils.test.ts b/src/plugins/dashboard/public/services/string_utils.test.ts deleted file mode 100644 index ed96cb4f1a0a1..0000000000000 --- a/src/plugins/dashboard/public/services/string_utils.test.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { upperFirst } from './string_utils'; - -describe('StringUtils', () => { - describe('upperFirst', () => { - test('should converts the first character of string to upper case', () => { - expect(upperFirst()).toBe(''); - expect(upperFirst('')).toBe(''); - - expect(upperFirst('Fred')).toBe('Fred'); - expect(upperFirst('fred')).toBe('Fred'); - expect(upperFirst('FRED')).toBe('FRED'); - }); - }); -}); diff --git a/src/plugins/dashboard/public/services/types.ts b/src/plugins/dashboard/public/services/types.ts new file mode 100644 index 0000000000000..3309ce0575971 --- /dev/null +++ b/src/plugins/dashboard/public/services/types.ts @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { PluginInitializerContext } from '@kbn/core/public'; +import { KibanaPluginServiceParams } from '@kbn/presentation-util-plugin/public'; + +import { DashboardStartDependencies } from '../plugin'; +import { DashboardAnalyticsService } from './analytics/types'; +import { DashboardApplicationService } from './application/types'; +import { DashboardChromeService } from './chrome/types'; +import { DashboardCoreContextService } from './core_context/types'; +import { DashboardCapabilitiesService } from './dashboard_capabilities/types'; +import { DashboardSessionStorageServiceType } from './dashboard_session_storage/types'; +import { DashboardDataService } from './data/types'; +import { DashboardDataViewEditorService } from './data_view_editor/types'; +import { DashboardDocumentationLinksService } from './documentation_links/types'; +import { DashboardEmbeddableService } from './embeddable/types'; +import { DashboardHTTPService } from './http/types'; +import { DashboardInitializerContextService } from './initializer_context/types'; +import { DashboardNavigationService } from './navigation/types'; +import { DashboardNotificationsService } from './notifications/types'; +import { DashboardOverlaysService } from './overlays/types'; +import { DashboardSavedObjectsService } from './saved_objects/types'; +import { DashboardSavedObjectsTaggingService } from './saved_objects_tagging/types'; +import { DashboardScreenshotModeService } from './screenshot_mode/types'; +import { DashboardSettingsService } from './settings/types'; +import { DashboardShareService } from './share/types'; +import { DashboardSpacesService } from './spaces/types'; +import { DashboardUrlForwardingService } from './url_forwarding/types'; +import { DashboardUsageCollectionService } from './usage_collection/types'; +import { DashboardVisualizationsService } from './visualizations/types'; + +export type DashboardPluginServiceParams = KibanaPluginServiceParams & { + initContext: PluginInitializerContext; // need a custom type so that initContext is a required parameter for initializerContext +}; +export interface DashboardServices { + analytics: DashboardAnalyticsService; + application: DashboardApplicationService; + chrome: DashboardChromeService; + coreContext: DashboardCoreContextService; + dashboardCapabilities: DashboardCapabilitiesService; + dashboardSessionStorage: DashboardSessionStorageServiceType; + data: DashboardDataService; + dataViewEditor: DashboardDataViewEditorService; // this service is used only for the no data state + documentationLinks: DashboardDocumentationLinksService; + embeddable: DashboardEmbeddableService; + http: DashboardHTTPService; + initializerContext: DashboardInitializerContextService; + navigation: DashboardNavigationService; + notifications: DashboardNotificationsService; + overlays: DashboardOverlaysService; + savedObjects: DashboardSavedObjectsService; + savedObjectsTagging: DashboardSavedObjectsTaggingService; // TODO: make this optional in follow up + screenshotMode: DashboardScreenshotModeService; + settings: DashboardSettingsService; + share: DashboardShareService; // TODO: make this optional in follow up + spaces: DashboardSpacesService; // TODO: make this optional in follow up + urlForwarding: DashboardUrlForwardingService; + usageCollection: DashboardUsageCollectionService; // TODO: make this optional in follow up + visualizations: DashboardVisualizationsService; +} diff --git a/src/plugins/dashboard/public/services/ui_actions.ts b/src/plugins/dashboard/public/services/ui_actions.ts deleted file mode 100644 index 79eef5302d556..0000000000000 --- a/src/plugins/dashboard/public/services/ui_actions.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export type { Action, UiActionsSetup, UiActionsStart } from '@kbn/ui-actions-plugin/public'; -export { IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; diff --git a/src/plugins/dashboard/public/services/url_forwarding/types.ts b/src/plugins/dashboard/public/services/url_forwarding/types.ts new file mode 100644 index 0000000000000..9df5dfc868aa7 --- /dev/null +++ b/src/plugins/dashboard/public/services/url_forwarding/types.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { UrlForwardingStart } from '@kbn/url-forwarding-plugin/public'; + +export interface DashboardUrlForwardingService { + navigateToLegacyKibanaUrl: UrlForwardingStart['navigateToLegacyKibanaUrl']; +} diff --git a/src/plugins/dashboard/public/services/url_forwarding/url_forwarding_service.ts b/src/plugins/dashboard/public/services/url_forwarding/url_forwarding_service.ts new file mode 100644 index 0000000000000..99dd8e9243392 --- /dev/null +++ b/src/plugins/dashboard/public/services/url_forwarding/url_forwarding_service.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import type { DashboardStartDependencies } from '../../plugin'; +import type { DashboardUrlForwardingService } from './types'; + +export type UrlForwardingServiceFactory = KibanaPluginServiceFactory< + DashboardUrlForwardingService, + DashboardStartDependencies +>; +export const urlForwardingServiceFactory: UrlForwardingServiceFactory = ({ startPlugins }) => { + const { + urlForwarding: { navigateToLegacyKibanaUrl }, + } = startPlugins; + + return { + navigateToLegacyKibanaUrl, + }; +}; diff --git a/src/plugins/dashboard/public/services/url_forwarding/url_fowarding.stub.ts b/src/plugins/dashboard/public/services/url_forwarding/url_fowarding.stub.ts new file mode 100644 index 0000000000000..26e406d4d3bbe --- /dev/null +++ b/src/plugins/dashboard/public/services/url_forwarding/url_fowarding.stub.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import { urlForwardingPluginMock } from '@kbn/url-forwarding-plugin/public/mocks'; +import { DashboardUrlForwardingService } from './types'; + +type UrlForwardingServiceFactory = PluginServiceFactory; + +export const urlForwardingServiceFactory: UrlForwardingServiceFactory = () => { + const pluginMock = urlForwardingPluginMock.createStartContract(); + + return { + navigateToLegacyKibanaUrl: pluginMock.navigateToLegacyKibanaUrl, + }; +}; diff --git a/src/plugins/dashboard/public/services/usage_collection.ts b/src/plugins/dashboard/public/services/usage_collection.ts deleted file mode 100644 index c8fec98b3b5db..0000000000000 --- a/src/plugins/dashboard/public/services/usage_collection.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; diff --git a/src/plugins/dashboard/public/services/usage_collection/types.ts b/src/plugins/dashboard/public/services/usage_collection/types.ts new file mode 100644 index 0000000000000..c7a06722f1705 --- /dev/null +++ b/src/plugins/dashboard/public/services/usage_collection/types.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; + +export interface DashboardUsageCollectionService { + reportUiCounter?: UsageCollectionStart['reportUiCounter']; +} diff --git a/src/plugins/dashboard/public/services/usage_collection/usage_collection.stub.ts b/src/plugins/dashboard/public/services/usage_collection/usage_collection.stub.ts new file mode 100644 index 0000000000000..ce0655ea3c189 --- /dev/null +++ b/src/plugins/dashboard/public/services/usage_collection/usage_collection.stub.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { usageCollectionPluginMock } from '@kbn/usage-collection-plugin/public/mocks'; +import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import { DashboardUsageCollectionService } from './types'; + +type UsageCollectionServiceFactory = PluginServiceFactory; + +export const usageCollectionServiceFactory: UsageCollectionServiceFactory = () => { + const pluginMock = usageCollectionPluginMock.createSetupContract(); + + return { + reportUiCounter: pluginMock.reportUiCounter, + }; +}; diff --git a/src/plugins/dashboard/public/services/usage_collection/usage_collection_service.ts b/src/plugins/dashboard/public/services/usage_collection/usage_collection_service.ts new file mode 100644 index 0000000000000..08ff6d2c2e4bf --- /dev/null +++ b/src/plugins/dashboard/public/services/usage_collection/usage_collection_service.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import type { DashboardStartDependencies } from '../../plugin'; +import type { DashboardUsageCollectionService } from './types'; + +export type UsageCollectionServiceFactory = KibanaPluginServiceFactory< + DashboardUsageCollectionService, + DashboardStartDependencies +>; +export const usageCollectionServiceFactory: UsageCollectionServiceFactory = ({ startPlugins }) => { + const { usageCollection } = startPlugins; + if (!usageCollection) return {}; + + const { reportUiCounter } = usageCollection; + return { + reportUiCounter, + }; +}; diff --git a/src/plugins/dashboard/public/services/visualizations/types.ts b/src/plugins/dashboard/public/services/visualizations/types.ts new file mode 100644 index 0000000000000..8bee13488fb60 --- /dev/null +++ b/src/plugins/dashboard/public/services/visualizations/types.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { VisualizationsStart } from '@kbn/visualizations-plugin/public'; + +export interface DashboardVisualizationsService { + get: VisualizationsStart['get']; + getAliases: VisualizationsStart['getAliases']; + getByGroup: VisualizationsStart['getByGroup']; + showNewVisModal: VisualizationsStart['showNewVisModal']; +} diff --git a/src/plugins/dashboard/public/services/visualizations/visualizations.stub.ts b/src/plugins/dashboard/public/services/visualizations/visualizations.stub.ts new file mode 100644 index 0000000000000..c5fb3db7251c8 --- /dev/null +++ b/src/plugins/dashboard/public/services/visualizations/visualizations.stub.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import { visualizationsPluginMock } from '@kbn/visualizations-plugin/public/mocks'; +import { DashboardVisualizationsService } from './types'; + +type HttpServiceFactory = PluginServiceFactory; + +export const visualizationsServiceFactory: HttpServiceFactory = () => { + const pluginMock = visualizationsPluginMock.createStartContract(); + + return { + get: pluginMock.get, + getAliases: pluginMock.getAliases, + getByGroup: pluginMock.getByGroup, + showNewVisModal: pluginMock.showNewVisModal, + }; +}; diff --git a/src/plugins/dashboard/public/services/visualizations/visualizations_service.ts b/src/plugins/dashboard/public/services/visualizations/visualizations_service.ts new file mode 100644 index 0000000000000..f7b4d470b7bcb --- /dev/null +++ b/src/plugins/dashboard/public/services/visualizations/visualizations_service.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import type { DashboardStartDependencies } from '../../plugin'; +import type { DashboardVisualizationsService } from './types'; + +export type VisualizationsServiceFactory = KibanaPluginServiceFactory< + DashboardVisualizationsService, + DashboardStartDependencies +>; + +export const visualizationsServiceFactory: VisualizationsServiceFactory = ({ startPlugins }) => { + const { + visualizations: { get, getAliases, getByGroup, showNewVisModal }, + } = startPlugins; + + return { + get, + getAliases, + getByGroup, + showNewVisModal, + }; +}; diff --git a/src/plugins/dashboard/public/types.ts b/src/plugins/dashboard/public/types.ts index f0e9485d20e09..d6b77d265bf0b 100644 --- a/src/plugins/dashboard/public/types.ts +++ b/src/plugins/dashboard/public/types.ts @@ -6,42 +6,24 @@ * Side Public License, v 1. */ -import type { - AppMountParameters, - CoreStart, - SavedObjectsClientContract, - ScopedHistory, - ChromeStart, - IUiSettingsClient, - PluginInitializerContext, - KibanaExecutionContext, -} from '@kbn/core/public'; import { History } from 'history'; -import type { Filter } from '@kbn/es-query'; import { AnyAction, Dispatch } from 'redux'; import { BehaviorSubject, Subject } from 'rxjs'; -import { UrlForwardingStart } from '@kbn/url-forwarding-plugin/public'; -import { VisualizationsStart } from '@kbn/visualizations-plugin/public'; -import { PersistableControlGroupInput } from '@kbn/controls-plugin/common'; -import { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public'; -import { DataView } from './services/data_views'; -import { SharePluginStart } from './services/share'; -import { EmbeddableStart } from './services/embeddable'; -import { DashboardSessionStorage } from './application/lib'; -import { UsageCollectionSetup } from './services/usage_collection'; -import { NavigationPublicPluginStart } from './services/navigation'; -import { Query, RefreshInterval, TimeRange } from './services/data'; -import { DashboardPanelState, SavedDashboardPanel } from '../common/types'; -import { SavedObjectsTaggingApi } from './services/saved_objects_tagging_oss'; -import { DataPublicPluginStart, DataViewsContract } from './services/data'; -import { ContainerInput, EmbeddableInput, ViewMode } from './services/embeddable'; -import { SavedObjectLoader, SavedObjectsStart } from './services/saved_objects'; -import type { ScreenshotModePluginStart } from './services/screenshot_mode'; -import { IKbnUrlStateStorage } from './services/kibana_utils'; +import type { AppMountParameters, ScopedHistory, KibanaExecutionContext } from '@kbn/core/public'; +import type { Filter } from '@kbn/es-query'; +import type { PersistableControlGroupInput } from '@kbn/controls-plugin/common'; +import { type EmbeddableInput, ViewMode } from '@kbn/embeddable-plugin/common'; +import type { ContainerInput } from '@kbn/embeddable-plugin/public'; +import type { DataView } from '@kbn/data-views-plugin/public'; +import type { IKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public'; +import type { RefreshInterval } from '@kbn/data-plugin/public'; +import type { Query, TimeRange } from '@kbn/es-query'; + import type { DashboardContainer, DashboardSavedObject } from '.'; -import { DashboardAppLocatorParams } from './locator'; -import { SpacesPluginStart } from './services/spaces'; +import type { DashboardAppLocatorParams } from './locator'; +import { DashboardPanelState, SavedDashboardPanel } from '../common/types'; +import { SavedObjectLoader } from './services/saved_object_loader'; export type { SavedDashboardPanel }; @@ -82,7 +64,6 @@ export interface DashboardState { export type RawDashboardState = Omit & { panels: SavedDashboardPanel[] }; export interface DashboardContainerInput extends ContainerInput { - dashboardCapabilities?: DashboardAppCapabilities; controlGroupInput?: PersistableControlGroupInput; refreshConfig?: RefreshInterval; isEmbeddedExternally?: boolean; @@ -125,24 +106,10 @@ export interface DashboardAppState { /** * The shared services and tools used to build a dashboard from a saved object ID. */ -export type DashboardBuildContext = Pick< - DashboardAppServices, - | 'embeddable' - | 'dataViews' - | 'savedDashboards' - | 'usageCollection' - | 'initializerContext' - | 'savedObjectsTagging' - | 'dashboardCapabilities' -> & { - query: DashboardAppServices['data']['query']; - search: DashboardAppServices['data']['search']; - notifications: DashboardAppServices['core']['notifications']; - +// TODO: Remove reference to DashboardAppServices as part of https://github.com/elastic/kibana/pull/138774 +export type DashboardBuildContext = Pick & { locatorState?: DashboardAppLocatorParams; - history: History; - kibanaVersion: string; isEmbeddedExternally: boolean; kbnUrlStateStorage: IKbnUrlStateStorage; $checkForUnsavedChanges: Subject; @@ -183,43 +150,14 @@ export interface DashboardSaveOptions { isTitleDuplicateConfirmed: boolean; } -export interface DashboardAppCapabilities { - show: boolean; - createNew: boolean; - saveQuery: boolean; - createShortUrl: boolean; - showWriteControls: boolean; - storeSearchSession: boolean; - mapsCapabilities: { save: boolean }; - visualizeCapabilities: { save: boolean }; -} - -export interface DashboardAppServices { - core: CoreStart; - chrome: ChromeStart; - share?: SharePluginStart; - embeddable: EmbeddableStart; - data: DataPublicPluginStart; - uiSettings: IUiSettingsClient; +export interface DashboardMountContextProps { restorePreviousUrl: () => void; - savedObjects: SavedObjectsStart; - allowByValueEmbeddables: boolean; - urlForwarding: UrlForwardingStart; - savedDashboards: SavedObjectLoader; scopedHistory: () => ScopedHistory; - visualizations: VisualizationsStart; - dataViewEditor: DataViewEditorStart; - dataViews: DataViewsContract; - usageCollection?: UsageCollectionSetup; - navigation: NavigationPublicPluginStart; - dashboardCapabilities: DashboardAppCapabilities; - initializerContext: PluginInitializerContext; onAppLeave: AppMountParameters['onAppLeave']; - savedObjectsTagging?: SavedObjectsTaggingApi; - savedObjectsClient: SavedObjectsClientContract; - screenshotModeService: ScreenshotModePluginStart; - dashboardSessionStorage: DashboardSessionStorage; setHeaderActionMenu: AppMountParameters['setHeaderActionMenu']; - savedQueryService: DataPublicPluginStart['query']['savedQueries']; - spacesService?: SpacesPluginStart; +} + +// TODO: Remove DashboardAppServices as part of https://github.com/elastic/kibana/pull/138774 +export interface DashboardAppServices { + savedDashboards: SavedObjectLoader; } diff --git a/src/plugins/dashboard/public/services/embeddable.ts b/src/plugins/data/public/data_views/mocks.ts similarity index 87% rename from src/plugins/dashboard/public/services/embeddable.ts rename to src/plugins/data/public/data_views/mocks.ts index 1fa960f11aed7..5ba55fe61ee1b 100644 --- a/src/plugins/dashboard/public/services/embeddable.ts +++ b/src/plugins/data/public/data_views/mocks.ts @@ -6,4 +6,4 @@ * Side Public License, v 1. */ -export * from '@kbn/embeddable-plugin/public'; +export * from '@kbn/data-views-plugin/public/mocks'; diff --git a/src/plugins/data/public/mocks.ts b/src/plugins/data/public/mocks.ts index e1b42b7c193e2..fadc844af4b58 100644 --- a/src/plugins/data/public/mocks.ts +++ b/src/plugins/data/public/mocks.ts @@ -8,10 +8,11 @@ import { fieldFormatsServiceMock } from '@kbn/field-formats-plugin/public/mocks'; import { createDatatableUtilitiesMock } from '../common/mocks'; -import { DataPlugin, DataViewsContract } from '.'; +import { DataPlugin } from '.'; import { searchServiceMock } from './search/mocks'; import { queryServiceMock } from './query/mocks'; import { createNowProviderMock } from './now_provider/mocks'; +import { dataViewPluginMocks } from './data_views/mocks'; export type Setup = jest.Mocked>; export type Start = jest.Mocked>; @@ -26,20 +27,7 @@ const createSetupContract = (): Setup => { const createStartContract = (): Start => { const queryStartMock = queryServiceMock.createStartContract(); - const dataViews = { - find: jest.fn((search) => [{ id: search, title: search }]), - createField: jest.fn(() => {}), - createFieldList: jest.fn(() => []), - ensureDefaultIndexPattern: jest.fn(), - make: () => ({ - fieldsFetcher: { - fetchForWildcard: jest.fn(), - }, - }), - get: jest.fn().mockReturnValue(Promise.resolve({})), - clearCache: jest.fn(), - getIdsWithTitle: jest.fn(), - } as unknown as DataViewsContract; + const dataViews = dataViewPluginMocks.createStartContract(); return { actions: { diff --git a/src/plugins/data/public/query/timefilter/timefilter_service.mock.ts b/src/plugins/data/public/query/timefilter/timefilter_service.mock.ts index 523b03d6f06c1..03e7bed0545d1 100644 --- a/src/plugins/data/public/query/timefilter/timefilter_service.mock.ts +++ b/src/plugins/data/public/query/timefilter/timefilter_service.mock.ts @@ -19,10 +19,10 @@ const createSetupContractMock = () => { isTimeRangeSelectorEnabled: jest.fn(), isTimeTouched: jest.fn(), isRefreshIntervalTouched: jest.fn(), - getEnabledUpdated$: jest.fn(), - getTimeUpdate$: jest.fn(), - getRefreshIntervalUpdate$: jest.fn(), - getAutoRefreshFetch$: jest.fn(() => new Observable<() => void>()), + getEnabledUpdated$: jest.fn().mockImplementation(() => new Observable<() => void>()), + getTimeUpdate$: jest.fn().mockImplementation(() => new Observable<() => void>()), + getRefreshIntervalUpdate$: jest.fn().mockImplementation(() => new Observable<() => void>()), + getAutoRefreshFetch$: jest.fn().mockImplementation(() => new Observable<() => void>()), getFetch$: jest.fn().mockImplementation(() => new Observable<() => void>()), getTime: jest.fn(), setTime: jest.fn(), diff --git a/src/plugins/data_views/public/mocks.ts b/src/plugins/data_views/public/mocks.ts index e9bcd6726611a..61db42e18a9be 100644 --- a/src/plugins/data_views/public/mocks.ts +++ b/src/plugins/data_views/public/mocks.ts @@ -31,6 +31,7 @@ const createStartContract = (): Start => { hasDataView: jest.fn().mockReturnValue(Promise.resolve(true)), }, getDefaultDataView: jest.fn().mockReturnValue(Promise.resolve({})), + getDefaultId: jest.fn().mockReturnValue(Promise.resolve('')), get: jest.fn().mockReturnValue(Promise.resolve({})), clearCache: jest.fn(), getCanSaveSync: jest.fn(), diff --git a/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable_factory.tsx b/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable_factory.tsx index 47fb58c1fbbd9..282f6a8c627c2 100644 --- a/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable_factory.tsx +++ b/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable_factory.tsx @@ -39,6 +39,10 @@ export class ContactCardEmbeddableFactory }); } + public getDefaultInput() { + return {}; + } + public getExplicitInput = (): Promise> => { return new Promise((resolve) => { const modalSession = this.overlays.openModal( diff --git a/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_exportable_embeddable_factory.tsx b/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_exportable_embeddable_factory.tsx index cf6ffdbe84e9f..c7b81cd0f2250 100644 --- a/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_exportable_embeddable_factory.tsx +++ b/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_exportable_embeddable_factory.tsx @@ -34,6 +34,10 @@ export class ContactCardExportableEmbeddableFactory return true; } + public getDefaultInput() { + return {}; + } + public getDisplayName() { return i18n.translate('embeddableApi.samples.contactCard.displayName', { defaultMessage: 'contact card', diff --git a/test/functional/apps/dashboard/group1/empty_dashboard.ts b/test/functional/apps/dashboard/group1/empty_dashboard.ts deleted file mode 100644 index e559c0ef81f60..0000000000000 --- a/test/functional/apps/dashboard/group1/empty_dashboard.ts +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import expect from '@kbn/expect'; - -import { FtrProviderContext } from '../../../ftr_provider_context'; - -export default function ({ getService, getPageObjects }: FtrProviderContext) { - const testSubjects = getService('testSubjects'); - const kibanaServer = getService('kibanaServer'); - const dashboardAddPanel = getService('dashboardAddPanel'); - const dashboardVisualizations = getService('dashboardVisualizations'); - const dashboardExpect = getService('dashboardExpect'); - const PageObjects = getPageObjects(['common', 'dashboard']); - - describe('empty dashboard', () => { - before(async () => { - await kibanaServer.savedObjects.cleanStandardList(); - await kibanaServer.importExport.load( - 'test/functional/fixtures/kbn_archiver/dashboard/current/kibana' - ); - await kibanaServer.uiSettings.replace({ - defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', - }); - await PageObjects.common.navigateToApp('dashboard'); - await PageObjects.dashboard.preserveCrossAppState(); - await PageObjects.dashboard.clickNewDashboard(); - }); - - after(async () => { - await dashboardAddPanel.closeAddPanel(); - await PageObjects.dashboard.gotoDashboardLandingPage(); - await kibanaServer.savedObjects.cleanStandardList(); - }); - - it('should display empty widget', async () => { - const emptyWidgetExists = await testSubjects.exists('emptyDashboardWidget'); - expect(emptyWidgetExists).to.be(true); - }); - - it('should open add panel when add button is clicked', async () => { - await dashboardAddPanel.clickOpenAddPanel(); - const isAddPanelOpen = await dashboardAddPanel.isAddPanelOpen(); - expect(isAddPanelOpen).to.be(true); - await testSubjects.click('euiFlyoutCloseButton'); - }); - - it('should add new visualization from dashboard', async () => { - await dashboardVisualizations.createAndAddMarkdown({ - name: 'Dashboard Test Markdown', - markdown: 'Markdown text', - }); - await PageObjects.dashboard.waitForRenderComplete(); - await dashboardExpect.markdownWithValuesExists(['Markdown text']); - }); - - it('should open editor menu when editor button is clicked', async () => { - await dashboardAddPanel.clickEditorMenuButton(); - await testSubjects.existOrFail('dashboardEditorContextMenu'); - }); - }); -} diff --git a/test/functional/apps/dashboard/group1/index.ts b/test/functional/apps/dashboard/group1/index.ts index 736dfd6f577f8..c6eeaa6f8c598 100644 --- a/test/functional/apps/dashboard/group1/index.ts +++ b/test/functional/apps/dashboard/group1/index.ts @@ -28,7 +28,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { // This has to be first since the other tests create some embeddables as side affects and our counting assumes // a fresh index. - loadTestFile(require.resolve('./empty_dashboard')); loadTestFile(require.resolve('./url_field_formatter')); loadTestFile(require.resolve('./embeddable_rendering')); loadTestFile(require.resolve('./embeddable_data_grid')); diff --git a/test/functional/page_objects/dashboard_page.ts b/test/functional/page_objects/dashboard_page.ts index d36ba21513b0a..7a579f4e4f84b 100644 --- a/test/functional/page_objects/dashboard_page.ts +++ b/test/functional/page_objects/dashboard_page.ts @@ -98,6 +98,8 @@ export class DashboardPageObject extends FtrService { } public async exitFullScreenLogoButtonExists() { + // TODO: Replace every instance of `exitFullScreenModeLogo` with `exitFullScreenModeButton` once the new Shared UX + // full screen button can be used (i.e. after https://github.com/elastic/kibana/issues/140311 is resolved) return await this.testSubjects.exists('exitFullScreenModeLogo'); } diff --git a/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/__snapshots__/geo_containment_alert_type_expression.test.tsx.snap b/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/__snapshots__/geo_containment_alert_type_expression.test.tsx.snap index fe53610caa316..0f1db835a8281 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/__snapshots__/geo_containment_alert_type_expression.test.tsx.snap +++ b/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/__snapshots__/geo_containment_alert_type_expression.test.tsx.snap @@ -27,10 +27,20 @@ exports[`should render BoundaryIndexExpression 1`] = ` "clearCache": [MockFunction], "createField": [MockFunction], "createFieldList": [MockFunction], + "ensureDefaultDataView": [MockFunction], "ensureDefaultIndexPattern": [MockFunction], "find": [MockFunction], "get": [MockFunction], + "getCanSaveSync": [MockFunction], + "getDefaultDataView": [MockFunction], + "getDefaultId": [MockFunction], + "getFieldsForIndexPattern": [MockFunction], "getIdsWithTitle": [MockFunction], + "hasData": Object { + "hasDataView": [MockFunction], + "hasESData": [MockFunction], + "hasUserDataView": [MockFunction], + }, "make": [Function], } } @@ -104,10 +114,20 @@ exports[`should render EntityIndexExpression 1`] = ` "clearCache": [MockFunction], "createField": [MockFunction], "createFieldList": [MockFunction], + "ensureDefaultDataView": [MockFunction], "ensureDefaultIndexPattern": [MockFunction], "find": [MockFunction], "get": [MockFunction], + "getCanSaveSync": [MockFunction], + "getDefaultDataView": [MockFunction], + "getDefaultId": [MockFunction], + "getFieldsForIndexPattern": [MockFunction], "getIdsWithTitle": [MockFunction], + "hasData": Object { + "hasDataView": [MockFunction], + "hasESData": [MockFunction], + "hasUserDataView": [MockFunction], + }, "make": [Function], } } @@ -187,10 +207,20 @@ exports[`should render EntityIndexExpression w/ invalid flag if invalid 1`] = ` "clearCache": [MockFunction], "createField": [MockFunction], "createFieldList": [MockFunction], + "ensureDefaultDataView": [MockFunction], "ensureDefaultIndexPattern": [MockFunction], "find": [MockFunction], "get": [MockFunction], + "getCanSaveSync": [MockFunction], + "getDefaultDataView": [MockFunction], + "getDefaultId": [MockFunction], + "getFieldsForIndexPattern": [MockFunction], "getIdsWithTitle": [MockFunction], + "hasData": Object { + "hasDataView": [MockFunction], + "hasESData": [MockFunction], + "hasUserDataView": [MockFunction], + }, "make": [Function], } } diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index f2e44d679c602..c0897818f6513 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -938,7 +938,6 @@ "dashboard.createConfirmModal.unsavedChangesSubtitle": "Poursuivez les modifications ou utilisez un tableau de bord vierge.", "dashboard.createConfirmModal.unsavedChangesTitle": "Nouveau tableau de bord déjà en cours", "dashboard.dashboardAppBreadcrumbsTitle": "Tableau de bord", - "dashboard.dashboardGrid.toast.unableToLoadDashboardDangerMessage": "Impossible de charger le tableau de bord.", "dashboard.dashboardPageTitle": "Tableaux de bord", "dashboard.dashboardWasSavedSuccessMessage": "Le tableau de bord \"{dashTitle}\" a été enregistré.", "dashboard.discardChangesConfirmModal.cancelButtonLabel": "Annuler", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 5d2c7d42e4301..10b70be09fccd 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -936,7 +936,6 @@ "dashboard.createConfirmModal.unsavedChangesSubtitle": "編集を続行するか、空のダッシュボードで始めてください。", "dashboard.createConfirmModal.unsavedChangesTitle": "新しいダッシュボードはすでに実行中です", "dashboard.dashboardAppBreadcrumbsTitle": "ダッシュボード", - "dashboard.dashboardGrid.toast.unableToLoadDashboardDangerMessage": "ダッシュボードが読み込めません。", "dashboard.dashboardPageTitle": "ダッシュボード", "dashboard.dashboardWasSavedSuccessMessage": "ダッシュボード「{dashTitle}」が保存されました。", "dashboard.discardChangesConfirmModal.cancelButtonLabel": "キャンセル", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 2bab4fcae474e..55813d9216574 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -938,7 +938,6 @@ "dashboard.createConfirmModal.unsavedChangesSubtitle": "继续编辑或使用空白仪表板从头开始。", "dashboard.createConfirmModal.unsavedChangesTitle": "新仪表板已在创建中", "dashboard.dashboardAppBreadcrumbsTitle": "仪表板", - "dashboard.dashboardGrid.toast.unableToLoadDashboardDangerMessage": "无法加载仪表板。", "dashboard.dashboardPageTitle": "仪表板", "dashboard.dashboardWasSavedSuccessMessage": "仪表板“{dashTitle}”已保存", "dashboard.discardChangesConfirmModal.cancelButtonLabel": "取消", From b0e6a41220b18c224419c696ede6eeb7be8d4c6b Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Wed, 14 Sep 2022 16:15:53 +0200 Subject: [PATCH 2/4] [Lens] Performance journey (#140195) * add Lens performance journey * cleanup * always wait * Update utils.ts * gzip mappings Co-authored-by: Liza Katz --- .buildkite/ftr_configs.yml | 1 + .../es_archiver/stress_test/data.json.gz | Bin 0 -> 52381 bytes .../es_archiver/stress_test/mappings.json.gz | Bin 0 -> 108979 bytes .../fixtures/kbn_archiver/stress_test.json | 54 ++++++++++++++++++ x-pack/plugins/lens/readme.md | 3 + .../journeys/data_stress_test_lens/config.ts | 47 +++++++++++++++ .../data_stress_test_lens.ts | 37 ++++++++++++ 7 files changed, 142 insertions(+) create mode 100644 test/functional/fixtures/es_archiver/stress_test/data.json.gz create mode 100644 test/functional/fixtures/es_archiver/stress_test/mappings.json.gz create mode 100644 test/functional/fixtures/kbn_archiver/stress_test.json create mode 100644 x-pack/test/performance/journeys/data_stress_test_lens/config.ts create mode 100644 x-pack/test/performance/journeys/data_stress_test_lens/data_stress_test_lens.ts diff --git a/.buildkite/ftr_configs.yml b/.buildkite/ftr_configs.yml index 561f783e917dd..f0c7f96d791fe 100644 --- a/.buildkite/ftr_configs.yml +++ b/.buildkite/ftr_configs.yml @@ -273,3 +273,4 @@ enabled: - x-pack/test/performance/journeys/many_fields_discover/config.ts - x-pack/test/performance/journeys/promotion_tracking_dashboard/config.ts - x-pack/test/performance/journeys/web_logs_dashboard/config.ts + - x-pack/test/performance/journeys/data_stress_test_lens/config.ts diff --git a/test/functional/fixtures/es_archiver/stress_test/data.json.gz b/test/functional/fixtures/es_archiver/stress_test/data.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..76086d5d7803fdb0b654bb9eb1572f60da27593e GIT binary patch literal 52381 zcmeHQeOMD$zUHG6l?uJLYpJy{?XydJdBm`46+f<~Z12-u4MnfGeuB{=ZIuRm#DE|& z`0!MBFI9J03sSV;J{6U=QfUQ6A@sV4h=P}jh-fHKK|xGo6oN@+?(dv4O+W+#lh9P= zpH7{;zjF@ro-=dio%eT+C~4%#S({%Bad2L{X44w+`pAfIhi`ApD;LXS_uoEAy}hgb z-9L&B{Pz*He%13K-eX7J$vyVtKSv*6?!G=|+V9)G`?^N)FYm~2w8ks{aU$UEe-?Gh z;x&~_J!RY>Go1IKX{Di5W~h@HBuseTJfQGL+xY%WA03rmkqoQRT9D!}(*z z^BqmhvdVV#TqcQW6QP=nyDRIvs+A9K$P8^tBf}hF=v3w^WvrpJ5%f(eKO|;NX^!!M z+IWB0gO7wt+E>YZKReGO%5b)x{%(Q#s_d#Jo$A~nDQw!jrZmFXDgvMufW|P&@crzg z0IbMi$`+_k0PvJja|nP?qT1M;VKhYF+3DV@X0Bu~hQIk19j1P&qcqyLRJzaU2DRa4 zrF%?M`olje^-Cp^A9wC<6)`Fq24++D^pq9>zoc)A4DASb>f!s8E*b&d+cKE{%b`(; zUs9SoBAMvHsr_0pF==n zQaw$8cYUZ943s-XY;7uEF;)e?zoxkYCBM@ohoaKY)v0v3(?7{C)P%~XL~Lm)zqU!$ zaW?h%sKWN0K4F2ebr6tsKMw`m#sO70;9$NI0_I0h5U@u@Lx5MI5eMjTKnD(JM>SN{ z)k6(E12j0G2?vzofL%C1hy!M$CLN51zwfKhw5I=;&sT58wRPcOEe>wM!4)`oKMBUQ zMHivwspEPD)Zl=lI3O4YoFX-#3ou_Divu3u8rpCT-2-lr08#_K0P1)gV8j7>2zbEU zAeEX=1;%#bNV;2>9vWei(hligdYxgXTlE$ za~;azhjJ(J#MC!ZjZUkVh}Q+^N1Ow|jgEx(ZYDR6d*5+7{HC7aTD<(^=P~D&CsdsG zyeO@3+Wf-$H-GqMp|exwM=w1$a?<65gbxn5JoC2ev{&9-aoBNN)gQ);+>0RJ0^~42 z{)8aw0kQ-j#Rzf*ASV#yI6%Gz$X^iTLx8-8N?8ezYXG^5Ab9{e0g!VD5)P0kR0@Kq z0HQ;Xe1H_Awuk|;0U+B^DM0{f1jvgX0677Wa{&1hf@}mx3MvIbngKEcK~4hX56oJFOq0?0jp z>_w2%{?12>W4`(^Pqs~a-m_YnD4dZKw8$vEivD>~u5@boMEYr6{D=y;`uWDu`<9B= zh3m%xrs{gf#0{%Xetxoj=?|wj&Wo(eDUJc6NIBgD+ft<+8p{iMpwCZ0E%Eum3F_RW z+*TeJ!qj+H=iq-YRwlaQ2L8Jk)%sxN2?YMLGiqkG%QFzMc+5y>vw^XVO`07ucF7Kvk$``9?QFKDsHufS*msk0#`+S@PLCg)<=^Gh@p@6ZyC`Kt8TtUxAyd zijLyR=Xj$Eed#BMindqsjgEwTXF|S!knc{&7ZLI$WaLv0n0(rSjC|V#n#jkkiK(B- zj(l9BD%y=FpF8=_RJ8pr-#Chp|2!f8bwd6ELVgBIJ{kEGnIq8X=?im&!34q_VV6e! z|MfkfhO{CYgEzU>|FuZ*tvf^Pd>*tmNb{R z1a0g&Im15Y9SQl)gnR)Z-<^;zBIHX5`4k!XHVib8k6WXPo=UW@KB z<3Cwe7a7+^W$n#rpEc)L%dSdev|ks~cYa=-8}%U;KW_ zj>s3zwr;rlzsrObYY^yJ03AZ0=?Js~Ky3�fEi{Xd41Ohe~=EKyM?^Uja06GJsw~ zpnn1A00R96m4rY`5GV;iVF)Bfpb`Lmhd?i)l0pFV9s=z|Z9<^w2y_-e4G8oyDhYw2 z5NJ1Q69Uaepi%%ijswsusHC+3+KxbfM{Sw{ptlj|9DrU$px>dA5GcjJy1~i2_FXYO zVp;CKMZpHwuh-5zSfKM*UAA$)LgQ1Md~;80qb$L*+^f1o=^#xB%ub>lQ@jr?_F$a6 zmj@iNW#EkxcT|Nc@debH2NS-<^>=3CrWOh&6(=8=^$h$4uYSEY+ija?pkkzVTJ~G3 z%N7frq*<-cEt~S^9XzDJ>+)=M!)Wi?^k9hb4a-A>WCRKZ1}ynvie1 zKoj{(Sn_fGote1CLcv!&v;8q^!Jq*0UA$}G6Vqb|`Qr)szJ&Y;LVi6v@*N5J&V+my zLcZ++P2^8!$;b6~X5tzP1#+Hz?&Q09*M^H}At8S@AwQasuV%?-N4_&5UqHxrBjgV# z@J?#r(XJAur3FQ6FtRffS5hcw$L*Q(0d9fX4>$G;xY2K%zZCB9H}@QcyMH;%8PF}F z03He6hc>~pV0pl8cnIX~t3nTmnFlQm7~W3}xGOk%*RB!M&ZJ`qNXKv|9YaJqhJ@S| zC^8>l97xA-Bpt(cfu_5H8@VfNV&?<6#zKLRzbkORD~$54T_>iWC*;3Q$X`In&tSf2N*QkU@|z0kZ-#{6Zruw`RsfE*H|dn&y&xc{1M)@p<>#Vkncgr_aWrV2>D8O z>Mf_d_}lRw70RxYN;6Y_lt`4NQtdX{{4##Kf}KCI}^4A zq?@>tZXzPxL_)d=MeY#{J3FvlplO{QgX^Dj3)iUjgU0c)04FVEs^SE_cV%vq z;G|9xcve;i@?a7(HPmq2IOU)&V|s^a9}=Eh%0 zJ_Pdn6fDi;V=+_hhjq_b_X2k0kKoDYOuni(`38{Bj(n@jhn0KUN)3Z8{%#}tRS`>` zz&ktWv8<^|#G0d3zcTU`75s(uz^u3g&n*{9Y%dckGy@eTPwCiBfYnY_;shc*-)Fx! z3OEi3PMX|P;c`lwbv@pHQY}4E68|@(DC&+IXJJKgyR0sUh>ToM<%Lz6W^=Sse#G%qcQ z8meq&AfMO|o?N4ed~>EVCLcFd?FTv9Jo#M72Mc0)62&2#c|eLEkWUN`Pp;8KexGU2 zJoy~S-|Qfbip%;?n>1ue%l)|yK`QQ^Uv1*woY0I0e>Y1a&p%iGoupBa!!%S$%l)|y zf&A|I)h6=I3C);%vn7!ypDX!}(x|kgAxm2B&vgjoch9djk#A0D#^jqVi9GpS$#<4U z!JGR~B`pK_miin`Z^A$MSvlj}F7AJoNI6qf3I3XRnlSbS8H zG?U+b%|t$#*?P+Z^U2J^`2Mj_$R{&fZz$Q*J>7z~EEMv|%)^*`8z(cPMfh z_-SpyJ>3F`zwk3jbION$zrlG6nU*~vagdDsu`-Wc@lY4055@V(KcDIX6CfP8y5B{7-Ulw?G^EMW4nDTzVrr19kU&8!9AJ%D_BHzhHd*py^MyDVVxv9!SO zrXLmJps5>^*%0JRVr zAi0%(FR3Q4%l%=Nq4Bt!PAu|LZkDGrnGxrGR`DCHSbG~8fBWBVfleelxQf1IpA|T- z(a7)M%K2vbD_G@>P9&?GiO4Ev4B5`rh_-X(SG=&`{jhJyuV9rkI?)X;aVH|Xv@)4E zsnN*q(rS&7Z||#|(TM>p`FLL1T|eYUe&55|0=p}We0yKzj7}7@2rT2V&vb-ujbE${m>E+kA`G0c1NWGhpb77x`dlbqwN-W@;Tiqx3_M9&&?M~ z*!_0w=fJLohsDCjDv1NRzh$*P$1&CG>FZ_-g+;~5gau~dot*`g@9W!4N) zABe?Gr7pSh7DbNT*AZ{7B`iy7Mfcx90;55&6{(d`{8r3X-i zA176Imku%c0-k)1Ps_t@?yJk(A~hoN9Z^COnET!#6*%m^R+++vfhO{MONZEl;_xK$ zjlQAVo|cz*JGh06%RU4Czwd*3tXHPcWUxJl%ryJnUKM&y_l3@^8(5GeLSM%pXH7(N z%URnzc`1NX7XtD@zbg;wv0j-%BO~OqZ+rds`E+rj)>w zFXc);`2XsH2APovD(UL$D`Ms@)n0$WZb9BGM$Or#lX^;V{bOkyQ8Nd=U@+mT5 z_=UD_HIY9Dx2C%_5lagE$mdQzd}o@%n-GVcAjz8$hq09rOo(*)irhAeq5qB?hH-Lc zA9ffvgy~zb!|+*f7#_k7L)|rcCUzK}sp+UBX2qgBDgc`me>!fA#Ae0qIqez5tjHFn zRvz)xli#?Di28{2Wi)9ZsD_Z@e0(i5;1S>9Beq0XEs@`Y*{YWy$m)xr-BEg}4c1vy z%`{VxlbHhOhOk%`gSJvYGX=aFTCfn=R0;+!84SCJ#1mesd1diQ5*4S!7>`+Nkq;uDIMP1EK_H*a zoJ09(X7YQP5;6HsJo%hIC`GI;3y&oBzSc%1`&q!!T@YAbpiKhJ@xT`%?DGz`pFmG(VF4o%YZ}0uh$hNm%D)Wf7vfUj0_S?USe>2H1E-4}2>iFf)vo@c5r}>+* zyd&*J6MVd%|M~O&-N)9IZmwG$85dvjmgC{%#!q$M#fZEr1CF0gHh=uNGO(aK_C$}G zZZF77wX{a14qR{3((P?g^zB-$sWy+kr8s@3nZ7fbHhozuF`L8a{_Q<=bZWn` z>~B`kt^1ql@062M6U^7cwC1}V5=&z$?^}9nTSDp|By_tnD%Fx(V)@o!dG8yossBrfsjr$II7MGtKzBJ# zKP#~HpT4>-J$U-Uy7WU%P3=05oiBtXHpP4p`$6JvagF#)@R@~Yf9m#h>$9;<8*_5? zeTM49y)VT-U%iYfJ?9y4pmkOfvwu8suYdff6#bvd!R47{UzMJ75BPQKtj5xF{_*eZ z&Ru7uU(Lx~H*BMIR&VLK;P`ixxlL$ks?AN@yRGS(S3p_yvRjFJ-)y?}bb$QKvO{=z zfIc4_-~B;uQ0uJP(sNz`U$@TM*0fQX+nKmGIR4W%yTyb5*0jztl%87@-<^|tZ&?%O zQ@!j;;@*U&YaRi8XO@+7m#G__YV>V8DG618Ue)N;hF*4U63Pd?r0De~dhJB7Ee`*C zpmE{~yF#Z8(^~?q-b?GWx_IEya($u?b@}VxeB;DFo)?4ZMevIEQ#6Eed+%WzB_)s@plJ4#*8>sY5 zjq#I+3-VN#!t}R&sD76u%dRrI(laf_?`5&iM=FmMdgs}c!_=e+S!K3Wp1+OpTP!a4 zNVRr_UOby}pPJ;9Rc2S^nGxgnirD9{DtCoGd^R<-^chZ5f>a*U0R_} zoJ|EzP4dbrn_T6Y8RNH9>~lor5w71en+lzpG%KsjrOGoa#xF!%a748>yynF0wpD*f z^8KRB^?Fm*JMkeel^pr=sN11$PoGjc9=fA`ZXlYEbsxi&Oq#l?sCmmK-i+VJxy3aM59uWg%TrEQ7QwpZyA@L_J_#Q2$Z zw`SVKd)wXewu^rz;Cp(L@8u^Q{hO>w@_h_nEeDpg9A47$@sgIJC0EOq7z01wyHl6H zQ}>UZx_|DZR(W|?bGQ`Xk_kl>Odht`JIo+xZv z1>1vnk{llbUt_$1g!mBn8siPzj?D>aK2uto5bJrPMC^Cx(ocQ*H-F$^e(}tnRFBqB z?e4_CHBY$lZp|;5>Lg|Aa$Iw8t-;mlS9l-S99(N~b$S)=GxeOz;Jt-;l48Q#Y=2iF>0otEQ$Tyt=(!PO}Y@8g<-YYnbWEAT$9 zIk?u~>J*Omam~TCCcOEKrq0UCRhMs*veHWF&=PJPG&fB;zN?V9&Cr(ci9vJIrO$O0 zj`P~5%YQ0mrHzujO&hNWUA{xgN^9lZmhkaGN7JOPU4_JLuF>UB>na={6lbl3C3_DS zb@?xL6;29@vsJ>9y@xfcp3!K*=+@1C;KR!QEb zW6(_-UsoY4*?X`Hx@oQ4s>}E4DugAvhbv9_-PKL`<+~FL5Mr%Mh;3>w5#K_H zH5nn+MF_DTM2K~{cG7>hqlcifH?Uh?KrBFY7!Jfb-%s>HL;($CP>- zFIW!VL%MWxOSnf${RFRKSmJxwk|v$r5^k4LZ|wz3_8!Ous2p(amQp|7YfHLxeM>kj z*?X9rQg7?klO_#r35O-Rhtp-!24$IaI|E4{hFCwUV;+La-hgqrE$Or>i&}}NRWWKM zo>s-E6*yHPw+x&rpIZh_RlqF+t%^}AaH;}s88}rQx16M0BcZ%{h7+kHq1LqJZRjDW z><#D_O&U%yx)Li2;82XjiUK$kV+66H01yGJ;foex&yQaMWrYco01VmAwJ6;RP~1RFB~_@(1L*>05y(~^vk2HVptsN%dkZ;-9xPgAP%E-lp@*Qd zHvl%PK{h!QY*viLj5tRXAE;t2#2P-c2xRL;KC=j9YapLl1hO>_Y*viLjL$5d1hSRO zEP~?-*{u4(X7z|BGO$?<(yL&z!Wjr)vtkf?G1#mG9y9P(@jYhXtrB?5z-EOr5Wrg{ z@R)(Oit8~$Hml1sl9c^J?*HNK|KZ^PFBdQmL1k}%IyiyAz$yo!SeL}WDxMAiC3yXN z1FL!r$1lH{{;YV)xW%ElU%YlT?5$0r&z!p=O|MITsQ(=R=NUK;s05=D+n~5K>7=eg zYcGFY{)CizE2Vo&IB}cBs1t$mGn8vkjz&;ix^zBDfAb=56Cf%;)KLNtPB#bNG!I

-;{RHaw|N_ zw)0N1vfq6}fp%Mcqtzla`lciuY3&8v>dMFhAdyCp%tK`M?Bj;=5kFLu8;KE`JY^y> zzY`Bq{V_l%9;9M`4p~)#%r3~P;$?QF_7;BEYe@dEKij0K*Pg9?!;BUUXc5aSzCw#6 zX3>ZizcXiNR#}$6p>Qs`(42U4f%mk5MPcTU${2;+QElexPQ%h3H|K2g^p7pxF-6*+ zH$*L+cQ?7PA0LXqhlnk1oqJ1@u5ogP#OD9jrg5z zu!q*OpNTz`%pOwXL)UuTtg|OY%Vz3*Gvqr&>M4|Kh2|Aiq`lFBxXowu9rnh+3Qdkk z4NLYOrc(PVH2&Xj+J=9j8Qdn0+9u2gY_W|gYZ z!MG+`=BwYdLw-V}CT{a&h34-fwXeRz!3azC9=29!4vEx0`i_Z4STgruZOl^WH05Uv z*=BF;g1gzK@(T;P2V%5>jm*{aL}~;E9rYauiP{+Z5jaH$AJ?|FR=aqdsdH%ph! zn})v0>8`MW8(N4RBO|#R)~AeDqBy0_CG` zG7%^reG`g6`RJQW1j07nH_9N;Jqi|aHsk`1z`YrR#edz#$_LQ zLrf_GeTY0wmv6_A4Ka%d(h;%^?C2mf43uafpBg5P2<5|2%nZ*3c9da;i6cV!FcdSx zb!;;)qZ_48_baP@)2A7${L5HEeDC;z!AjJ`SGNW|{}F&3O(rDpfT)Q(H3R z)1zguWbc94<~#?JlG~i;P@}{)=Q)^^+~zz7lM>sU=U~z;JLK!5Ws#~zR|=NwJ@jPA zgQI0(szw(ImdriaQW12TeEY*WQHC`oj|LTRz~knje~h+963~Y#MzMtSv5G_=2MsDv$l9-d zi$uQ?hZ4mGF5p82yfBG=B@QLJ4;#2h^eb^FQEcEM(XYgzL>m|b7mrKd`lAKrcXuQB{SowXZ%aC$W>pBOifC&_f=#=k6P-9&GfO1 zbtw_M>vOP|3H$07Vi!|2W3+$L9@Z5>pc4~j#gbnnf-H_V3DS$05qIb!IoWhh{8{dzb*(Ly_Lj^TaO>;4 zpv_!}9!DdIVio9l_{Ye1kQTdy%=t@v-7J~IhWH{;hZP#;fXauoC zFF9r<2%8mXX!-I<2v-!yCqYq4?zC7A4)rH?S}e!q2Z6LA^>}I@8#RiYN_^ls9GMd3 zS`t9f7Vy&JNvIqwwvX6}>>MmctfxsJp9DpzKt2hIQh|ID6r~(2*1+_!iS#sauo$tP zrqL1?ND!FL7peR0(6IRjdgzCcX(n+7`e9Jiy88EwmE4v1(M!bJ=rI>eVph>yfptqD zp9DoIOqLN&*nuQPzJ``a$SJgLkr?tdf^~~<8$I?krOOY3jcHUzJ=mCJU}F-ghoS@^ z&s~q^D*@;f;PCY@)c7$tzBK+Qmfjuw`RsZf+f{A&8M z;-6S84n6kIKkD-?e6``m&puR}LSP1y-WNPeOy(+hmM~#TMmYCq4ij){3CwFS?@?f0 z1A`72bmT*ZdcKK``01cJ%`FLvv*!fd4>jk9ckfBfyT|Q%`-L!)9_tif{(7`*4#N*Z zubx-`Ud#{kRKo$H8Fu$PYy>QS&XOC6EBd2)LmYD#s=8{ zj9waTaw&{n()+^brF-K~h5j8PaVU=y9IbKvg!+RaKR_^p3-SZh{KNzf7V{@IxQGNB z_)yn<)jjHS6E7ze!X(t(_zY{~R)}vJH535in=}yL#I)jBSuDgisSxaj6dEjxh0rPi z4(CHKK&5EhIJwaC{%41*B8*455DdEWR}54HZ868o9x!c*GmyzSvC}~HxH(r_(DU$* zk?#Pao(5yCAnJ+1tRNr7RxU@ttcWUxtO3Xx5C&ZFvj!m72vfzxx9xGpJp@niqBhdx zRbXRUjf`9m^J#&NX)taT^{@Qb=PriJbCP3|a}TKub7wnv?>+VWHwV_^VGlS+!gY+lBcqRK z8xkUu{o6$DFAhh5K3|VS9mOK`kMFnX5qky?{h0p5I=k-6YmwCrM$$7Q>0Px9D=3NT zbnEMj(Vne!8wg1)zp$X&Se3dbZ^7Mc)B8nH*V?kp-Z6Mw(dv03^#bo{I5fVBIZ4pA z{pwCd4GP<705*acBe6Ss4?d z`s5?l(f#;Oz9DRyD$?S>jHVxgoDW8&nWSOQOabuTP((WN9081vnzKjV1JH%xbz!2} zp|amI&-kz4F8{UPbKiZX2gxKUs9Z$qrzniYAgmW+SVbs*p&0ZF#x)ycJ-&$R6cV8V zR7xlvIvkRwRe6C#jE_fw32o$)!@z_J$YJ7fs4MRElgx+S|Ll-H0qGO>k~bh%0CEKw z9W4?nq|1BMI!*aQ+VexGbT}#_lqI#9wk-pqTU5O0mK~!9nGgn81-u|or7RXBzFAK) zAA0|@M-Z#F2GOXB&jtH~Xw(RzQJl#-p7ICL=vYZ#5RHzcnC0_=gdxu@BY<%i3h*e` zG76$mEC2w}C>AP!XjFeJ1I$YeAR1+GC>AP!XtY9u?BpOC9W&_*qEUX6etPuisU{#A z1!xMQQ7ly02cl79XtWGOqaYeZ<|URS1<@#oMm2bl>P0%r*neJYG4H>&Krvu$rY$=q zbfebXJ;RdFv0q~9lh780sbjC9GfX@1WmLHb{cXF%ay!prIVG{&RY>ZZEzQ}@*Cm#3 z!W7>PNNN=4qO`Tz-j`|0oZ4qp&~*xvIZ81QMqe*c3?yg=IwS##m$ar+$)=uOMPKU- fOP#^ecZjaD>~}Mrmz2}S0NSx6F05Z}oMZKW { + const performance = getService('performance'); + + it(JOURNEY_DATA_STRESS_TEST_LENS, async () => { + await performance.runUserJourney( + JOURNEY_DATA_STRESS_TEST_LENS, + [ + { + name: 'Go to dashboard', + handler: async ({ page, kibanaUrl }: StepCtx) => { + await page.goto( + `${kibanaUrl}/app/dashboards#/view/92b143a0-2e9c-11ed-b1b6-a504560b392c` + ); + + await waitForVisualizations(page, 1); + }, + }, + ], + { + requireAuth: false, + } + ); + }); + }); +} From 5ad7802cd81c5478d8a2b3d580094f6f4184511e Mon Sep 17 00:00:00 2001 From: Lisa Cawley Date: Wed, 14 Sep 2022 07:26:19 -0700 Subject: [PATCH 3/4] [DOCS] Clarify rule upgrade known issue (#140674) --- .../alerting/troubleshooting/alerting-common-issues.asciidoc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/user/alerting/troubleshooting/alerting-common-issues.asciidoc b/docs/user/alerting/troubleshooting/alerting-common-issues.asciidoc index e33641af8b7f0..85e95d4c935c3 100644 --- a/docs/user/alerting/troubleshooting/alerting-common-issues.asciidoc +++ b/docs/user/alerting/troubleshooting/alerting-common-issues.asciidoc @@ -274,7 +274,7 @@ This error happens when the `xpack.encryptedSavedObjects.encryptionKey` value us *Problem*: Alerting rules that were created or edited in 8.2 stop running after you upgrade -to later releases. The following error occurs: +to 8.3.0 or 8.3.1. The following error occurs: [source,text] ---- @@ -283,6 +283,7 @@ to later releases. The following error occurs: *Solution*: -Upgrade to 8.3.2 or later releases, then go to *{stack-manage-app} > {rules-ui}* and multi-select the failed rules. Choose +Upgrade to 8.3.2 or later releases to avoid the problem. To fix failing rules, +go to *{stack-manage-app} > {rules-ui}* and multi-select the rules. Choose **Manage rules > Update API Keys** to generate new API keys. For more details about API key authorization, refer to <>. From 02e1d7f2e05f09c82a9f5cd4b918f9ebd24dcd38 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Wed, 14 Sep 2022 16:54:51 +0200 Subject: [PATCH 4/4] [Fleet] Set package policy compile streams on bulkCreate (#140694) --- .../fleet/server/services/package_policy.ts | 52 +++++++++++++++++-- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/fleet/server/services/package_policy.ts b/x-pack/plugins/fleet/server/services/package_policy.ts index 8366434480e49..aaf158e56dba5 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.ts @@ -242,31 +242,47 @@ class PackagePolicyClientImpl implements PackagePolicyClient { force?: true; } ): Promise { - const agentPolicyIds = new Set(packagePolicies.map((pkgPol) => pkgPol.policy_id)); + const agentPolicyIds = new Set(packagePolicies.map((pkgPolicy) => pkgPolicy.policy_id)); for (const agentPolicyId of agentPolicyIds) { await validateIsNotHostedPolicy(soClient, agentPolicyId, options?.force); } + const packageInfos = await getPackageInfoForPackagePolicies(packagePolicies, soClient); + const isoDate = new Date().toISOString(); // eslint-disable-next-line @typescript-eslint/naming-convention const { saved_objects } = await soClient.bulkCreate( - packagePolicies.map((packagePolicy) => { + await pMap(packagePolicies, async (packagePolicy) => { const packagePolicyId = packagePolicy.id ?? uuid.v4(); const agentPolicyId = packagePolicy.policy_id; - const inputs = packagePolicy.inputs.map((input) => + let inputs = packagePolicy.inputs.map((input) => assignStreamIdToInput(packagePolicyId, input) ); const { id, ...pkgPolicyWithoutId } = packagePolicy; + let elasticsearch: PackagePolicy['elasticsearch']; + if (packagePolicy.package) { + const pkgInfo = packageInfos.get( + `${packagePolicy.package.name}-${packagePolicy.package.version}` + ); + + inputs = pkgInfo + ? await _compilePackagePolicyInputs(pkgInfo, packagePolicy.vars || {}, inputs) + : inputs; + + elasticsearch = pkgInfo?.elasticsearch; + } + return { type: SAVED_OBJECT_TYPE, id: packagePolicyId, attributes: { ...pkgPolicyWithoutId, inputs, + elasticsearch, policy_id: agentPolicyId, revision: 1, created_at: isoDate, @@ -1457,6 +1473,36 @@ export interface NewPackagePolicyWithId extends NewPackagePolicy { export const packagePolicyService: PackagePolicyClient = new PackagePolicyClientImpl(); +async function getPackageInfoForPackagePolicies( + packagePolicies: NewPackagePolicyWithId[], + soClient: SavedObjectsClientContract +) { + const pkgInfoMap = new Map(); + + packagePolicies.forEach(({ package: pkg }) => { + if (pkg) { + pkgInfoMap.set(`${pkg.name}-${pkg.version}`, pkg); + } + }); + + const resultMap = new Map(); + + await pMap(pkgInfoMap.keys(), async (pkgKey) => { + const pkgInfo = pkgInfoMap.get(pkgKey); + if (pkgInfo) { + const pkgInfoData = await getPackageInfo({ + savedObjectsClient: soClient, + pkgName: pkgInfo.name, + pkgVersion: pkgInfo.version, + }); + + resultMap.set(pkgKey, pkgInfoData); + } + }); + + return resultMap; +} + export function updatePackageInputs( basePackagePolicy: NewPackagePolicy, packageInfo: PackageInfo,